Пользовательские генераторы путей в D3.js опираются на единый
принцип: преобразование произвольных данных в SVG path-строку или
последовательность команд рисования через контекст. Внутри экосистемы
d3-shape генератор пути — это функция с расширяемым поведением, которая
либо возвращает строку атрибута d, либо выполняет отрисовку
через переданный контекст (SVG или Canvas).
Базовая идея генератора пути заключается в разделении двух этапов:
В D3 это реализовано через интерфейс, где генератор может работать в двух режимах:
moveTo, lineTo,
bezierCurveTo и другие.Генератор, реализующий такой интерфейс, может быть использован в
attr("d", generator) или в
context(generator).
Базовый строительный блок пользовательских путей — объект, реализующий интерфейс контекста рисования:
moveTo(x, y)lineTo(x, y)quadraticCurveTo(x1, y1, x, y)bezierCurveTo(x1, y1, x2, y2, x, y)closePath()D3 предоставляет реализацию d3.path(), которая
аккумулирует команды и преобразует их в SVG path-строку.
const path = d3.path();
path.moveTo(10, 10);
path.lineTo(100, 10);
path.lineTo(100, 100);
path.closePath();
console.log(path.toString());
Этот механизм позволяет строить собственные генераторы, не зависящие от конкретного представления.
Пользовательский генератор в D3 обычно представляет собой функцию, принимающую данные и возвращающую path-строку или использующую контекст:
function customPath(data) {
const path = d3.path();
data.forEach((d, i) => {
const x = d.x;
const y = d.y;
if (i === 0) {
path.moveTo(x, y);
} else {
path.lineTo(x, y);
}
});
return path.toString();
}
Такой подход повторяет структуру d3.line, но полностью
контролируется разработчиком.
Более гибкая форма — поддержка внешнего контекста. Генератор
становится функцией, принимающей context:
function customGenerator(context) {
return function(data) {
let ctx = context || d3.path();
data.forEach((d, i) => {
const x = d.x;
const y = d.y;
if (i === 0) {
ctx.moveTo(x, y);
} else {
ctx.lineTo(x, y);
}
});
if (!context) return ctx.toString();
};
}
Такой паттерн позволяет использовать генератор и в SVG, и в Canvas без изменений логики.
Пользовательские генераторы часто включают преобразование координат. Например, полярная система координат:
function polarGenerator(radiusAccessor, angleAccessor) {
return function(data) {
const path = d3.path();
data.forEach((d, i) => {
const r = radiusAccessor(d);
const a = angleAccessor(d);
const x = r * Math.cos(a);
const y = r * Math.sin(a);
if (i === 0) {
path.moveTo(x, y);
} else {
path.lineTo(x, y);
}
});
return path.toString();
};
}
Здесь генератор не просто соединяет точки, а преобразует пространство данных.
Одним из распространённых расширений является построение сглаженных линий. В D3 это реализуется через интерполяцию, но пользовательский генератор может полностью заменить стандартную логику.
Простейшая реализация квадратичного сглаживания:
function smoothPath(data) {
const path = d3.path();
for (let i = 0; i < data.length - 1; i++) {
const p0 = data[i];
const p1 = data[i + 1];
const mx = (p0.x + p1.x) / 2;
const my = (p0.y + p1.y) / 2;
if (i === 0) {
path.moveTo(p0.x, p0.y);
}
path.quadraticCurveTo(p0.x, p0.y, mx, my);
}
return path.toString();
}
Такой подход заменяет стандартные интерполяции
d3.curve*, предоставляя полный контроль над формой
линии.
Внутренне d3.line() реализует тот же принцип: генератор
с доступом к:
x(d)y(d)defined(d)curveПользовательский генератор может имитировать этот интерфейс:
function lineGenerator() {
let xAccessor = d => d.x;
let yAccessor = d => d.y;
function line(data) {
const path = d3.path();
data.forEach((d, i) => {
const x = xAccessor(d);
const y = yAccessor(d);
if (i === 0) path.moveTo(x, y);
else path.lineTo(x, y);
});
return path.toString();
}
line.x = fn => (xAccessor = fn, line);
line.y = fn => (yAccessor = fn, line);
return line;
}
Этот паттерн делает генератор конфигурируемым, повторяя архитектуру D3.
Генераторы путей часто используются для визуализации функций:
function functionPath(f, xStart, xEnd, step) {
return function() {
const path = d3.path();
let first = true;
for (let x = xStart; x <= xEnd; x += step) {
const y = f(x);
if (first) {
path.moveTo(x, y);
first = false;
} else {
path.lineTo(x, y);
}
}
return path.toString();
};
}
Такая реализация позволяет строить графики без использования
d3.line.
Пользовательские генераторы часто применяются для:
Пример спирали:
function spiral(turns, step) {
return function() {
const path = d3.path();
let angle = 0;
let radius = 0;
for (let i = 0; i < turns * 100; i++) {
angle += 0.1;
radius += step;
const x = radius * Math.cos(angle);
const y = radius * Math.sin(angle);
if (i === 0) path.moveTo(x, y);
else path.lineTo(x, y);
}
return path.toString();
};
}
Сложные генераторы часто состоят из нескольких независимых сегментов:
function compositePath(parts) {
const path = d3.path();
parts.forEach(part => {
if (part.type === "line") {
path.lineTo(part.x, part.y);
}
if (part.type === "move") {
path.moveTo(part.x, part.y);
}
if (part.type === "curve") {
path.bezierCurveTo(
part.x1, part.y1,
part.x2, part.y2,
part.x, part.y
);
}
});
return path.toString();
}
Такой подход позволяет описывать графику декларативно через структуру данных.
Генераторы путей часто используются в больших визуализациях, где важно:
d3.path;Оптимизированный вариант:
function reusableLine() {
const path = d3.path();
let x, y;
function line(data) {
path.clear?.();
for (let i = 0; i < data.length; i++) {
x = data[i].x;
y = data[i].y;
if (i === 0) path.moveTo(x, y);
else path.lineTo(x, y);
}
return path.toString();
}
return line;
}
Пользовательские генераторы путей формируют слой абстракции между данными и визуальной геометрией. Они позволяют:
Эта модель делает D3 не просто библиотекой визуализации, а системой композиции геометрических преобразований.