Гистограмма в D3.js строится на основе преобразования непрерывных
данных в дискретные интервалы (бины). Основной инструмент —
d3.bin, который принимает массив чисел и возвращает набор
корзин, каждая из которых содержит элементы, попавшие в соответствующий
диапазон.
Ключевая особенность гистограммы в D3 заключается в том, что библиотека не строит визуализацию автоматически — она формирует структуру данных, которую затем можно отрисовать через SVG.
Основные этапы:
Функция d3.bin() создаёт генератор разбиения:
const binGenerator = d3.bin()
.domain([0, 100])
.thresholds(10);
Параметры:
Применение:
const bins = binGenerator(data);
Результат представляет собой массив объектов:
[
{ x0: 0, x1: 10, length: 5, ... },
{ x0: 10, x1: 20, length: 12, ... }
]
Каждый элемент содержит:
x0 — левая граница;x1 — правая граница;Для корректного отображения требуется линейная шкала:
const xScale = d3.scaleLinear()
.domain([0, 100])
.range([0, width]);
Шкала Y зависит от максимальной частоты:
const yScale = d3.scaleLinear()
.domain([0, d3.max(bins, d => d.length)])
.range([height, 0]);
Оси:
const xAxis = d3.axisBottom(xScale);
const yAxis = d3.axisLeft(yScale);
Каждая корзина представляется прямоугольником:
svg.selectAll("rect")
.data(bins)
.enter()
.append("rect")
.attr("x", d => xScale(d.x0))
.attr("y", d => yScale(d.length))
.attr("width", d => xScale(d.x1) - xScale(d.x0))
.attr("height", d => height - yScale(d.length))
.attr("fill", "steelblue");
Смысл отображения:
Для сопоставления с плотностными функциями используется нормализация:
[ = ]
Это позволяет сравнивать гистограмму с кривыми плотности.
В D3:
const total = data.length;
bins.forEach(bin => {
bin.density = bin.length / total;
});
Плотностная кривая (Kernel Density Estimation, KDE) представляет непрерывную аппроксимацию распределения данных.
Общая формула KDE:
f(x)=_{i=1}^{n}K()
Где:
На практике часто используется гауссово ядро:
K(u)=e^{-}
D3 не включает KDE как готовую функцию, поэтому используется пользовательская реализация.
function kernelGaussian(k) {
return function(v) {
return k(v) / Math.sqrt(2 * Math.PI);
};
}
function kernelDensityEstimator(kernel, X) {
return function(V) {
return X.map(x => [
x,
d3.mean(V, v => kernel(x - v))
]);
};
}
Создаётся сетка значений:
const xDensity = d3.range(0, 100, 1);
Выбирается bandwidth:
const bandwidth = 7;
const kernel = kernelGaussian(bandwidth);
Вычисление плотности:
const density = kernelDensityEstimator(kernel, xDensity)(data);
Результат:
[
[x0, y0],
[x1, y1],
...
]
Используется d3.line():
const line = d3.line()
.x(d => xScale(d[0]))
.y(d => yScale(d[1]))
.curve(d3.curveBasis);
Отрисовка:
svg.append("path")
.datum(density)
.attr("fill", "none")
.attr("stroke", "red")
.attr("stroke-width", 2)
.attr("d", line);
Для корректного наложения требуется единая шкала Y. Обычно применяют нормированную гистограмму.
Изменённая шкала:
yScale.domain([0, d3.max(density, d => d[1])]);
Гистограмма масштабируется к плотности:
svg.selectAll("rect")
.attr("height", d => height - yScale(d.density))
.attr("y", d => yScale(d.density));
Bandwidth определяет степень сглаживания:
Формально влияние видно в ядре:
Увеличение ( h ) уменьшает вклад локальных отклонений, расширяя вклад соседних точек.
При совмещении графиков критично обеспечить:
const xScale = d3.scaleLinear()
.domain(d3.extent(data))
.range([0, width]);
При больших наборах данных вычисления становятся дорогими:
Ускорение достигается также за счёт уменьшения количества точек:
const xDensity = d3.range(min, max, 2);
Гистограмма может служить приближением KDE при:
Формула нормализации:
_{i} f_i x = 1
Где:
Типовая архитектура SVG-графика:
const svg = d3.select("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", `translate(${margin.left},${margin.top})`);
Слои:
Гистограмма показывает дискретную частотность, KDE — непрерывную плотность. При наложении важно:
.attr("fill-opacity", 0.5);
KDE можно интерпретировать как свёртку:
f(x)=(K_h * )(x)
Где:
Плотностная кривая приближается к истинной функции распределения при:
Формально:
_{n} f_n(x)=f(x)