Круговая и кольцевая диаграмма

Круговая диаграмма строится на основе разбиения полного угла (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()

Функция d3.pie() не изменяет данные, а создаёт структуру с углами:

  • startAngle
  • endAngle
  • value
  • index
const pie = d3.pie()
  .value(d => d);

const arcs = pie(data);

Результатом становится массив объектов, пригодных для передачи в arc-генератор.


Геометрия с d3.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() возвращает координаты центра сектора.


Кольцевая диаграмма (donut chart)

Кольцевая диаграмма отличается наличием внутреннего радиуса. Это изменяет восприятие структуры данных и позволяет добавлять центральную область для метрик.

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.


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);
  });

Работа с угловыми значениями

Каждый сектор определяется двумя углами:

  • startAngle
  • endAngle

Полный круг равен (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-настройки.


Оптимизация отрисовки

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

  • переиспользование arc-генератора
  • кеширование результатов pie()
  • избегание повторного создания DOM-элементов
  • применение update selection вместо полного enter/exit цикла
const paths = svg.selectAll("path")
  .data(arcs);

paths.enter()
  .append("path")
  .merge(paths)
  .attr("d", arc);

Поведение при нулевых значениях

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


Сравнение круговой и кольцевой структуры

Ключевое различие заключается не в логике данных, а в визуальной интерпретации:

  • круговая диаграмма подчёркивает доли целого
  • кольцевая диаграмма оставляет центральное пространство для дополнительной информации

Комбинация этих подходов часто используется в аналитических панелях для отображения KPI и распределений одновременно.