Диаграмма рассеяния (scatter plot) в D3.js строится на основе отображения набора точек в двумерной системе координат, где каждая точка соответствует отдельному наблюдению данных. Основой построения служит связка SVG-элементов, масштабируемых функций (scales), осей (axes) и механизма связывания данных (data binding), характерного для D3.js.
Визуализация начинается с создания SVG-области, которая становится холстом для всех графических элементов. Обычно используется паттерн с учетом отступов (margin convention), позволяющий корректно разместить оси и подписи.
const margin = { top: 20, right: 30, bottom: 40, left: 50 };
const width = 800 - margin.left - margin.right;
const height = 500 - margin.top - margin.bottom;
const svg = d3.select("body")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom);
const g = svg.append("g")
.attr("transform", `translate(${margin.left},${margin.top})`);
Группа g смещается внутрь SVG, создавая рабочую область,
в которой строится диаграмма.
Диаграмма рассеяния опирается на массив объектов, каждый из которых содержит минимум два числовых значения:
const data = [
{ x: 10, y: 20 },
{ x: 30, y: 90 },
{ x: 50, y: 40 },
{ x: 70, y: 80 }
];
Каждый объект интерпретируется как точка в декартовой системе
координат, где x определяет положение по горизонтали, а
y — по вертикали.
Масштабы в D3.js преобразуют значения данных в пиксельные координаты. Для scatter plot используются линейные шкалы.
const xScale = d3.scaleLinear()
.domain([0, d3.max(data, d => d.x)])
.range([0, width]);
const yScale = d3.scaleLinear()
.domain([0, d3.max(data, d => d.y)])
.range([height, 0]);
Особенность оси Y заключается в инверсии диапазона: верхняя граница
имеет меньшее значение пикселей, поэтому range([height, 0])
обеспечивает корректное отображение вверх по графику.
Оси формируются на основе масштабов и добавляются в SVG как отдельные группы.
const xAxis = d3.axisBottom(xScale);
const yAxis = d3.axisLeft(yScale);
g.append("g")
.attr("transform", `translate(0,${height})`)
.call(xAxis);
g.append("g")
.call(yAxis);
Ось X размещается внизу области, ось Y — слева. Каждая ось автоматически генерирует деления и подписи на основе переданного масштаба.
Ключевой механизм D3.js — data join, позволяющий связать массив
данных с DOM-элементами. Для scatter plot используются SVG-элементы
circle.
g.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("cx", d => xScale(d.x))
.attr("cy", d => yScale(d.y))
.attr("r", 5);
Каждая точка данных преобразуется в круг, где:
cx — позиция по оси Xcy — позиция по оси Yr — радиус точкиМеханизм enter() создаёт недостающие DOM-элементы для
каждого объекта данных.
Добавление третьей переменной может осуществляться через цветовую шкалу. Это позволяет кодировать дополнительное измерение данных.
const colorScale = d3.scaleSequential()
.domain([0, d3.max(data, d => d.y)])
.interpolator(d3.interpolateViridis);
g.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("cx", d => xScale(d.x))
.attr("cy", d => yScale(d.y))
.attr("r", 6)
.attr("fill", d => colorScale(d.y));
Цвет становится функцией значения y, формируя визуальную
градацию интенсивности.
При увеличении количества точек возникает проблема перекрытия элементов. В таких случаях используются уменьшение радиуса, прозрачность или jitter-распределение.
Прозрачность задается через opacity:
.attr("opacity", 0.7)
Это позволяет визуально оценивать плотность распределения точек.
Текстовые метки строятся через SVG-элемент text,
синхронизированный с координатами точек.
g.selectAll("text")
.data(data)
.enter()
.append("text")
.attr("x", d => xScale(d.x) + 8)
.attr("y", d => yScale(d.y))
.text(d => `${d.x}, ${d.y}`)
.attr("font-size", "10px");
Смещение по оси X предотвращает наложение текста на круги.
Для данных с широким диапазоном значений используется логарифмическое масштабирование:
const xScale = d3.scaleLog()
.domain([1, d3.max(data, d => d.x)])
.range([0, width]);
Логарифмическая шкала изменяет геометрию распределения точек, делая возможным анализ экспоненциальных данных.
Диаграмма рассеяния в D3.js поддерживает динамическое обновление. Используется общий паттерн enter–update–exit.
const circles = g.selectAll("circle")
.data(data);
circles.enter()
.append("circle")
.merge(circles)
.attr("cx", d => xScale(d.x))
.attr("cy", d => yScale(d.y))
.attr("r", 5);
circles.exit().remove();
Этот механизм обеспечивает синхронизацию DOM с изменяющимся набором данных.
SVG позволяет применять трансформации, такие как масштабирование и перенос, к группам элементов.
g.attr("transform", `translate(${margin.left},${margin.top}) scale(1.1)`);
Комбинация трансформаций влияет на всю систему координат диаграммы.
Хотя scatter plot ориентирован на числовые данные, категориальные переменные могут быть визуализированы через цвет, форму или размер.
const sizeScale = d3.scaleSqrt()
.domain([0, d3.max(data, d => d.value)])
.range([2, 10]);
g.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("r", d => sizeScale(d.value));
Квадратичная шкала позволяет сгладить восприятие больших различий.
Дополнительные линии сетки улучшают читаемость распределения точек.
const xGrid = d3.axisBottom(xScale)
.tickSize(-height)
.tickFormat("");
g.append("g")
.attr("transform", `translate(0,${height})`)
.call(xGrid)
.attr("stroke-opacity", 0.1);
Сетка формируется на основе осей, но без текстовых меток.
При работе с тысячами или миллионами точек DOM-рендеринг становится узким местом. Используются стратегии оптимизации:
SVG сохраняет удобство манипуляции элементами, но уступает Canvas в производительности.
Scatter plot позволяет визуально выявлять:
Геометрическая плотность точек отражает статистическую структуру данных без применения дополнительных аналитических моделей.
Поверх scatter plot могут накладываться линии регрессии или области доверия.
const line = d3.line()
.x(d => xScale(d.x))
.y(d => yScale(d.y));
g.append("path")
.datum(lineData)
.attr("d", line)
.attr("fill", "none")
.attr("stroke", "black");
Такое комбинирование расширяет интерпретационные возможности диаграммы.
Перед визуализацией данные часто нормализуются:
const normalized = data.map(d => ({
x: (d.x - minX) / (maxX - minX),
y: (d.y - minY) / (maxY - minY)
}));
Нормализация приводит данные к единому масштабу, облегчая сравнение распределений.
В scatter plot расстояние между точками может интерпретироваться как мера сходства:
Евклидова метрика:
d = Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2);
используется как базовая мера пространственной близости.
Правильная структура включает:
Эта композиция формирует стандартный каркас scatter plot в D3.js, обеспечивая гибкость дальнейшего расширения визуализации под многомерные и динамические данные.