Диаграмма рассеяния (scatter plot)

Диаграмма рассеяния (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 — по вертикали.

Масштабирование данных (Scales)

Масштабы в 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 — позиция по оси X
  • cy — позиция по оси Y
  • r — радиус точки

Механизм 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]);

Логарифмическая шкала изменяет геометрию распределения точек, делая возможным анализ экспоненциальных данных.

Обновление данных (Update pattern)

Диаграмма рассеяния в 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));

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

Сетка координат (grid lines)

Дополнительные линии сетки улучшают читаемость распределения точек.

const xGrid = d3.axisBottom(xScale)
  .tickSize(-height)
  .tickFormat("");

g.append("g")
  .attr("transform", `translate(0,${height})`)
  .call(xGrid)
  .attr("stroke-opacity", 0.1);

Сетка формируется на основе осей, но без текстовых меток.

Производительность при больших данных

При работе с тысячами или миллионами точек DOM-рендеринг становится узким местом. Используются стратегии оптимизации:

  • уменьшение количества DOM-элементов
  • использование Canvas вместо SVG
  • агрегация данных (binning)
  • предварительная фильтрация

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, обеспечивая гибкость дальнейшего расширения визуализации под многомерные и динамические данные.