Линейный генератор: d3.line()

Линейный генератор в D3.js представляет собой инструмент преобразования набора координатных данных в строку пути SVG. Основная задача d3.line() заключается в создании значения атрибута d для элемента <path>, на основе массива точек. Каждая точка интерпретируется как пара координат, а результатом становится последовательность команд SVG Path Data.

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

Базовая структура:

const line = d3.line();

При вызове генератора с массивом данных:

const data = [
  [0, 10],
  [100, 50],
  [200, 30]
];

const pathData = line(data);

результатом будет строка вида:

M0,10L100,50L200,30

SVG интерпретирует M как перемещение пера в начальную точку и L как проведение линии к следующей точке.


Формат входных данных

По умолчанию d3.line() ожидает массив массивов из двух элементов:

[x, y]

Однако структура данных может быть произвольной. Для этого используются аксессоры.

const data = [
  { time: 0, value: 10 },
  { time: 1, value: 40 },
  { time: 2, value: 25 }
];

Определение доступа к координатам:

const line = d3.line()
  .x(d => d.time * 100)
  .y(d => d.value);

Аксессоры x и y

Ключевой механизм работы линейного генератора — функции-аксессоры.

x-аксессор

.x(d => d[0])

или

.x(d => xScale(d.time))

Определяет горизонтальную координату точки.

y-аксессор

.y(d => d[1])

или

.y(d => yScale(d.value))

Определяет вертикальную координату.

Важно учитывать, что в SVG ось Y направлена вниз, поэтому часто применяется инвертированное масштабирование:

.y(d => height - yScale(d.value))

Работа со шкалами

Линейный генератор часто используется вместе со шкалами D3:

const xScale = d3.scaleLinear()
  .domain([0, 10])
  .range([0, 500]);

const yScale = d3.scaleLinear()
  .domain([0, 100])
  .range([300, 0]);

const line = d3.line()
  .x(d => xScale(d.x))
  .y(d => yScale(d.y));

Шкалы обеспечивают преобразование данных в координаты SVG, сохраняя пропорции и масштаб графика.


Генерация path-строки

Результат работы d3.line() — строка SVG Path Data.

Основные команды:

  • M x,y — перемещение в первую точку
  • L x,y — линия к следующей точке

Пример:

const data = [
  { x: 0, y: 0 },
  { x: 50, y: 80 },
  { x: 100, y: 20 }
];

const line = d3.line()
  .x(d => d.x)
  .y(d => d.y);

console.log(line(data));

Результат:

M0,0L50,80L100,20

Пропуск значений: null и undefined

d3.line() поддерживает разрывы линии при наличии отсутствующих данных.

const data = [
  { x: 0, y: 10 },
  { x: 1, y: 20 },
  { x: 2, y: null },
  { x: 3, y: 40 }
];

По умолчанию null может нарушить построение. Для управления используется defined():

const line = d3.line()
  .defined(d => d.y != null)
  .x(d => xScale(d.x))
  .y(d => yScale(d.y));

При встрече с false генератор разрывает путь и начинает новый сегмент.

Результат будет содержать несколько независимых линий.


Кривые и интерполяция

Хотя d3.line() по умолчанию создаёт ломаную линию, форма может быть изменена через интерполяцию (curve).

const line = d3.line()
  .curve(d3.curveLinear);

Основные варианты кривых:

  • d3.curveLinear — прямая ломаная
  • d3.curveBasis — сглаженная кривая
  • d3.curveCardinal — контролируемая гладкость
  • d3.curveStep — ступенчатая линия

Пример сглаживания:

const line = d3.line()
  .x(d => xScale(d.x))
  .y(d => yScale(d.y))
  .curve(d3.curveBasis);

Это преобразует резкие углы в плавную кривую Безье.


Замыкание линии

Линейный генератор может замыкать контур, превращая линию в замкнутую фигуру.

const line = d3.line()
  .x(d => xScale(d.x))
  .y(d => yScale(d.y))
  .closed(true);

Замкнутые линии полезны при построении многоугольников и радарных диаграмм.


Контекст и трансформации координат

В реальных визуализациях данные редко совпадают с координатами SVG напрямую. Часто используется система отступов (margin convention).

const width = 600;
const height = 400;

const svg = d3.select("svg")
  .attr("width", width)
  .attr("height", height);

const g = svg.append("g")
  .attr("transform", "translate(40,20)");

Линейный генератор остаётся независимым от DOM:

const line = d3.line()
  .x(d => xScale(d.x))
  .y(d => yScale(d.y));

Трансформация применяется на уровне контейнера <g>.


Использование с path в SVG

Создание линии в DOM:

svg.append("path")
  .datum(data)
  .attr("fill", "none")
  .attr("stroke", "steelblue")
  .attr("stroke-width", 2)
  .attr("d", line);

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


Обновление данных и реактивность

При изменении данных генератор пересчитывает путь:

function update(data) {
  svg.select("path")
    .datum(data)
    .attr("d", line);
}

Таким образом, визуализация отделена от логики данных.


Работа с временными рядами

Линейный генератор часто применяется для временных данных:

const data = [
  { date: new Date(2020, 0, 1), value: 30 },
  { date: new Date(2020, 1, 1), value: 50 },
  { date: new Date(2020, 2, 1), value: 40 }
];

const xScale = d3.scaleTime()
  .domain(d3.extent(data, d => d.date))
  .range([0, width]);

const line = d3.line()
  .x(d => xScale(d.date))
  .y(d => yScale(d.value));

Здесь используется временная шкала, обеспечивающая корректное распределение точек по оси X.


Множественные линии на одном графике

При работе с несколькими наборами данных создаются независимые генераторы или используется один универсальный:

const line = d3.line()
  .x(d => xScale(d.x))
  .y(d => yScale(d.y));

svg.selectAll(".series")
  .data(series)
  .enter()
  .append("path")
  .attr("class", "series")
  .attr("d", d => line(d.values));

Каждая серия данных преобразуется в отдельный SVG path.


Производительность генерации

d3.line() оптимизирован для быстрого преобразования массивов в строку. Однако при больших наборах данных важны следующие аспекты:

  • минимизация вычислений внутри аксессоров
  • предварительное масштабирование данных
  • избегание лишних пересозданий генератора
  • повторное использование функции line

Взаимодействие с другими компонентами D3

Линейный генератор часто комбинируется с:

  • d3.axisBottom, d3.axisLeft для осей
  • d3.scaleLinear, d3.scaleTime для шкал
  • d3.brush для интерактивного выделения
  • d3.zoom для масштабирования графика

Такая связка формирует основу большинства линейных графиков в D3.js.


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

Частые проблемы:

  • отсутствие масштабирования приводит к выходу графика за пределы SVG
  • неправильная ориентация оси Y инвертирует график
  • несоответствие структуры данных ожидаемому формату
  • использование строк вместо чисел в координатах
  • забытый fill="none" для path, из-за чего линия перекрывает график

Корректная настройка генератора и данных критична для стабильного результата.