.datum(): привязка одного значения к элементу

Метод selection.datum() в D3.js используется для привязки одного значения данных ко всем выбранным DOM-элементам или для извлечения уже привязанного значения. В отличие от selection.data(), который работает с массивами данных и реализует полноценный data join (enter/update/exit), datum() оперирует единичным значением и не создаёт механизм сопоставления элементов и массива.

При вызове selection.datum(value) указанное значение сохраняется в каждом элементе выборки как скрытое свойство _data_ (внутренняя механика может меняться, но концептуально данные привязываются напрямую к DOM-узлу). Это значение становится доступным через аргумент функций-колбэков или через повторный вызов datum() без параметров.

d3.select("#box")
  .datum(42);

После выполнения этого кода элемент #box содержит привязанное значение 42.

Если вызвать datum() без аргумента:

const value = d3.select("#box").datum();
console.log(value); // 42

Происходит извлечение ранее привязанного значения.


Отличие datum() от data()

Ключевое различие заключается в уровне абстракции и сценариях использования.

data():

  • работает с массивами
  • создаёт соответствие “элемент ↔︎ элемент массива”
  • поддерживает enter/update/exit
  • используется для построения списков, графиков, таблиц

datum():

  • работает с одним значением
  • не выполняет join
  • не создаёт enter/exit
  • применяется для фиксированных или контекстных данных

Пример различия:

// data(): массив значений
d3.selectAll("circle")
  .data([10, 20, 30]);

// datum(): одно значение на все элементы
d3.selectAll("circle")
  .datum(10);

Во втором случае все circle получают одно и то же значение 10.


Передача константного контекста

datum() часто используется для задания общего контекста, который не зависит от количества элементов.

const config = {
  color: "steelblue",
  radius: 8
};

d3.select("svg")
  .datum(config)
  .append("circle")
  .attr("r", d => d.radius)
  .attr("fill", d => d.color);

Здесь объект config доступен как d в последующих цепочках. Несмотря на то, что добавляется один элемент, использование datum() позволяет передавать структурированные настройки.


Наследование данных в иерархии DOM

Одно из ключевых свойств datum() — данные наследуются дочерними элементами при создании внутри текущего контекста.

d3.select("svg")
  .datum({ scale: 2 })
  .append("g")
  .append("rect")
  .attr("width", d => 50 * d.scale)
  .attr("height", d => 50 * d.scale);

В этом примере объект { scale: 2 } доступен внутри rect, несмотря на то что он был задан на уровне svg.


Использование в callback-функциях

Во многих методах D3 доступ к данным осуществляется через первый аргумент функции:

d3.select("#label")
  .datum("Hello D3")
  .text(d => d);

Значение "Hello D3" передаётся в функцию как d.

Это особенно полезно, когда требуется унифицированный доступ к данным без необходимости работать с массивами.


Переопределение данных на разных уровнях

Каждый вызов datum() может перезаписывать ранее установленное значение:

const svg = d3.select("svg");

svg.datum(100);

svg.append("g")
   .datum(200)
   .append("text")
   .text(d => d);

Результат: текст будет содержать 200, так как дочерний элемент переопределил значение родителя.


Работа с существующими DOM-узлами

Если данные уже привязаны, повторный вызов datum() заменяет их без участия join-логики.

const node = d3.select("#item");

node.datum({ id: 1, value: 10 });

node.datum({ id: 2, value: 20 });

После второго вызова старое значение полностью теряется.


Использование для хранения состояния

datum() часто применяется как лёгкий механизм хранения состояния внутри DOM-структуры.

d3.select("#toggle")
  .datum({ active: false })
  .on("click", function(event, d) {
    d.active = !d.active;
    d3.select(this).classed("active", d.active);
  });

Здесь объект состояния хранится прямо в элементе и изменяется без внешних структур данных.


Поведение при множественном выборе

Если выборка содержит несколько элементов, одно и то же значение применяется ко всем:

d3.selectAll(".point")
  .datum({ highlighted: true });

Каждый элемент .point получает одинаковый объект. Важно учитывать, что это одна и та же ссылка, а не копия.


Ссылочная природа объектов

При передаче объектов через datum() сохраняется ссылка, а не клонированное значение:

const state = { value: 1 };

d3.selectAll("circle")
  .datum(state);

state.value = 999;

Все элементы будут видеть обновлённое значение 999, так как используется общий объект.


Использование с динамическими обновлениями

datum() может применяться для простых обновлений без data join:

function updateLabel(value) {
  d3.select("#label")
    .datum(value)
    .text(d => `Value: ${d}`);
}

Каждый вызов функции заменяет данные и обновляет отображение.


Встраивание в сложные цепочки

Метод хорошо комбинируется с переходами и модификациями атрибутов:

d3.select("circle")
  .datum({ r: 10 })
  .transition()
  .duration(500)
  .attr("r", d => d.r * 2);

Здесь значение используется как источник параметров анимации.


Ограничения метода

Использование datum() имеет ряд особенностей:

  • отсутствует механизм сопоставления массивов
  • нет enter/exit обработки
  • одинаковое значение применяется ко всей выборке
  • при работе с объектами легко получить нежелательное разделение состояния между элементами

Практический паттерн: глобальная конфигурация

const chartConfig = {
  width: 800,
  height: 400,
  margin: 20
};

const svg = d3.select("svg")
  .datum(chartConfig);

svg.append("g")
  .attr("transform", `translate(${chartConfig.margin},${chartConfig.margin})`)
  .append("text")
  .text(d => `Размер: ${d.width}×${d.height}`);

Здесь datum() используется как контейнер конфигурации для всего графика.


Практический паттерн: метаданные элемента

d3.selectAll("rect")
  .datum(function(_, i) {
    return { index: i, selected: false };
  })
  .on("click", function(event, d) {
    d.selected = !d.selected;
    d3.select(this)
      .attr("opacity", d.selected ? 1 : 0.5);
  });

Каждый элемент получает собственный объект состояния без необходимости data join.


Поведение при повторном связывании

При повторном вызове datum() данные полностью заменяются:

const el = d3.select("#node");

el.datum({ a: 1 });
el.datum({ b: 2 });

После второго вызова информация { a: 1 } недоступна.


Контраст с .text() и .attr()

Хотя datum() не изменяет визуальное представление напрямую, он становится источником данных для всех последующих операций:

d3.select("text")
  .datum(5)
  .text(d => d * 10);

Значение используется как промежуточный слой между логикой и отображением.


Типичные ошибки при использовании

Часто возникающие проблемы связаны с неправильным пониманием ссылочной модели:

  • ожидание копирования объектов
  • попытка использовать datum() как замену data() для массивов
  • отсутствие разделения состояния между элементами при необходимости независимости
// все элементы разделяют один объект
d3.selectAll("circle")
  .datum({ active: false });

Комбинация с data join

Хотя методы различаются, они могут использоваться совместно:

const dataset = [1, 2, 3];

d3.select("svg")
  .datum({ scale: 2 })
  .selectAll("circle")
  .data(dataset)
  .enter()
  .append("circle")
  .attr("r", d => d * 5);

Здесь datum() задаёт глобальный контекст, а data() — локальные значения элементов.


Поведение при пустом вызове

Если вызвать datum() на пустой выборке, результатом будет undefined, так как данных нет.

d3.select(null).datum(); // undefined