Круговая диаграмма строится на основе разбиения полного угла (2) на доли, пропорциональные значениям набора данных. В D3.js ключевую роль играет функция d3.pie(), которая преобразует массив чисел в набор угловых сегментов, а также генератор дуг d3.arc(), отвечающий за геометрию каждого сектора.
Базовая структура включает SVG-контейнер, вычисление разметки и отрисовку path-элементов.
const data = [10, 20, 30, 40];
const width = 400;
const height = 400;
const radius = Math.min(width, height) / 2;
const svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", `translate(${width / 2}, ${height / 2})`);
Функция d3.pie() не изменяет данные, а создаёт структуру с углами:
const pie = d3.pie()
.value(d => d);
const arcs = pie(data);
Результатом становится массив объектов, пригодных для передачи в arc-генератор.
Генератор дуг формирует SVG-path на основе внутренних и внешних радиусов.
const arc = d3.arc()
.innerRadius(0)
.outerRadius(radius);
Для классической круговой диаграммы внутренний радиус равен нулю.
Связывание данных выполняется через enter selection:
svg.selectAll("path")
.data(arcs)
.enter()
.append("path")
.attr("d", arc)
.attr("fill", (d, i) => d3.schemeCategory10[i]);
Каждый сектор получает уникальный цвет из стандартной палитры D3.
Подписи размещаются по центроиду дуги:
svg.selectAll("text")
.data(arcs)
.enter()
.append("text")
.text(d => d.data)
.attr("transform", d => `translate(${arc.centroid(d)})`)
.style("text-anchor", "middle")
.style("font-size", "12px");
Метод arc.centroid() возвращает координаты центра сектора.
Кольцевая диаграмма отличается наличием внутреннего радиуса. Это изменяет восприятие структуры данных и позволяет добавлять центральную область для метрик.
const arcDonut = d3.arc()
.innerRadius(radius * 0.5)
.outerRadius(radius);
Разница между круговой и кольцевой диаграммой заключается исключительно в параметре innerRadius.
Центральная область часто используется для отображения суммарного значения:
const total = d3.sum(data);
svg.append("text")
.text(total)
.attr("text-anchor", "middle")
.attr("dy", "0.35em")
.style("font-size", "24px");
Для более гибкого управления цветами применяется d3.scaleOrdinal():
const color = d3.scaleOrdinal()
.domain(data)
.range(d3.schemeSet3);
Использование шкалы позволяет привязать цвет к значению, а не к индексу.
Плавное появление реализуется через интерполяцию углов:
const arcAnim = d3.arc()
.innerRadius(0)
.outerRadius(radius);
svg.selectAll("path")
.data(arcs)
.enter()
.append("path")
.attr("fill", (d, i) => d3.schemeCategory10[i])
.transition()
.duration(800)
.attrTween("d", function(d) {
const interpolate = d3.interpolate(
{ startAngle: d.startAngle, endAngle: d.startAngle },
d
);
return t => arcAnim(interpolate(t));
});
Функция attrTween обеспечивает поэтапное построение дуги от нулевого угла до конечного.
Добавление событий позволяет превратить диаграмму в интерактивный элемент:
svg.selectAll("path")
.on("mouseover", function() {
d3.select(this)
.transition()
.duration(200)
.attr("opacity", 0.7);
})
.on("mouseout", function() {
d3.select(this)
.transition()
.duration(200)
.attr("opacity", 1);
});
Для более сложных сценариев используется изменение масштаба, подсветка сегмента или вывод tooltip.
Всплывающая подсказка создаётся как отдельный HTML-элемент:
const tooltip = d3.select("body")
.append("div")
.style("position", "absolute")
.style("background", "#fff")
.style("padding", "6px")
.style("border", "1px solid #ccc")
.style("opacity", 0);
Привязка к сегментам:
svg.selectAll("path")
.on("mousemove", function(event, d) {
tooltip
.style("opacity", 1)
.style("left", event.pageX + "px")
.style("top", event.pageY + "px")
.text(`Значение: ${d.data}`);
})
.on("mouseout", function() {
tooltip.style("opacity", 0);
});
Каждый сектор определяется двумя углами:
Полный круг равен (2), поэтому доля вычисляется как:
[ = ]
D3 автоматически выполняет нормализацию через pie-генератор, но понимание механики важно для кастомных модификаций.
Для категориальных данных используется объектная структура:
const data = [
{ label: "A", value: 10 },
{ label: "B", value: 20 },
{ label: "C", value: 30 }
];
const pie = d3.pie()
.value(d => d.value);
Подписи:
.text(d => d.data.label)
Толщина кольца определяется разницей радиусов:
const inner = radius * 0.6;
const outer = radius;
const arc = d3.arc()
.innerRadius(inner)
.outerRadius(outer);
Увеличение внутреннего радиуса делает диаграмму более «лёгкой», уменьшение — более массивной.
При отображении нескольких круговых диаграмм на одной странице используется независимая группировка:
const svg = d3.select("body")
.append("svg")
.attr("width", 800)
.attr("height", 400);
const g1 = svg.append("g").attr("transform", "translate(200,200)");
const g2 = svg.append("g").attr("transform", "translate(600,200)");
Каждая группа может иметь собственный pie-генератор и arc-настройки.
При работе с большими наборами данных важно минимизировать перерисовки:
const paths = svg.selectAll("path")
.data(arcs);
paths.enter()
.append("path")
.merge(paths)
.attr("d", arc);
Сегменты с нулевыми значениями автоматически исключаются pie-генератором. При необходимости сохранения структуры требуется предварительная нормализация данных.
Ключевое различие заключается не в логике данных, а в визуальной интерпретации:
Комбинация этих подходов часто используется в аналитических панелях для отображения KPI и распределений одновременно.