Генератор круговой диаграммы: d3.pie()

d3.pie() формирует вспомогательную структуру данных для построения круговых диаграмм, преобразуя линейный массив значений в набор объектов с угловыми параметрами. Важная особенность заключается в том, что функция не занимается отрисовкой — она только вычисляет геометрию секторов, оставляя визуализацию на d3.arc().

Круговая диаграмма в D3 строится в два этапа:

  1. Подготовка данных через d3.pie()
  2. Построение геометрии через d3.arc()

Базовый принцип работы d3.pie()

Функция принимает массив данных и возвращает массив объектов, содержащих:

  • value — исходное значение
  • startAngle — начальный угол сектора
  • endAngle — конечный угол сектора
  • index — индекс элемента
  • дополнительные поля из исходного объекта (если переданы объекты)

Каждый угол измеряется в радианах, где полный круг равен .

const data = [10, 20, 30];

const pie = d3.pie();
const arcs = pie(data);

console.log(arcs);

Результатом будет структура, где каждый элемент содержит диапазон углов, пропорциональный значению.


Преобразование значений в углы

d3.pie() суммирует все элементы массива и вычисляет долю каждого значения:

[ = ]

Затем эта доля умножается на полный угол окружности:

[ = ]

Таким образом обеспечивается пропорциональность секторов.


Использование value accessor

Если данные представлены объектами, используется функция доступа value():

const data = [
  { name: "A", count: 10 },
  { name: "B", count: 20 },
  { name: "C", count: 30 }
];

const pie = d3.pie()
  .value(d => d.count);

const arcs = pie(data);

Value accessor позволяет отделить визуализацию от структуры данных и использовать произвольные поля.


Сортировка секторов

По умолчанию d3.pie() сортирует данные по убыванию значения. Это поведение можно изменить.

d3.pie().sort(null);

Возможные варианты:

  • sort(null) — сохраняет исходный порядок массива
  • sort((a, b) => a.count - b.count) — пользовательская сортировка

Сортировка влияет на расположение секторов по окружности, но не на их размер.


Начальный и конечный угол

Диапазон построения можно контролировать через:

  • startAngle
  • endAngle
const pie = d3.pie()
  .startAngle(0)
  .endAngle(Math.PI);

Это позволяет создавать полу-круговые диаграммы и нестандартные сегментированные дуги.


Подготовка данных для d3.arc()

Результат d3.pie() напрямую используется в d3.arc():

const arc = d3.arc()
  .innerRadius(0)
  .outerRadius(100);

svg.selectAll("path")
  .data(pie(data))
  .enter()
  .append("path")
  .attr("d", arc);

Каждый элемент массива уже содержит startAngle и endAngle, которые интерпретируются генератором дуг.


Построение кольцевой диаграммы

Добавление внутреннего радиуса превращает круговую диаграмму в кольцевую:

const arc = d3.arc()
  .innerRadius(50)
  .outerRadius(100);

Разница между внешним и внутренним радиусом формирует толщину кольца.


PadAngle и разделение секторов

Для визуального отделения сегментов используется padAngle:

const pie = d3.pie()
  .padAngle(0.02);

Эта настройка добавляет промежутки между секторами, улучшая читаемость диаграммы при большом количестве элементов.


Работа с метками и центроидами

Каждый сектор имеет геометрический центр, вычисляемый через d3.arc().centroid():

const arc = d3.arc()
  .innerRadius(0)
  .outerRadius(100);

const labelArc = d3.arc()
  .innerRadius(60)
  .outerRadius(60);

svg.selectAll("text")
  .data(pie(data))
  .enter()
  .append("text")
  .attr("transform", d => `translate(${labelArc.centroid(d)})`)
  .text(d => d.data.name);

Centroid позволяет точно позиционировать подписи внутри секторов.


Использование объекта данных в pie()

При передаче объектов d3.pie() сохраняет исходные данные в поле data:

{
  data: { name: "A", count: 10 },
  value: 10,
  startAngle: 0,
  endAngle: 1.2
}

Это позволяет легко получать дополнительные свойства:

.text(d => d.data.name)

Динамическое обновление диаграммы

При изменении данных генератор пересчитывает углы заново:

function update(data) {
  const arcs = pie(data);

  svg.selectAll("path")
    .data(arcs)
    .transition()
    .attr("d", arc);
}

Изменение набора данных автоматически приводит к перестройке всех сегментов.


Особенности распределения углов

d3.pie() всегда начинает с 12 часов (−π/2 радиан по умолчанию в arc-логике отображения), если не задан startAngle. Это влияет на визуальную ориентацию диаграммы.

При необходимости диаграмму можно повернуть:

d3.pie().startAngle(Math.PI / 2);

Обработка нулевых и отрицательных значений

  • Нулевые значения создают сектора нулевой длины
  • Отрицательные значения обычно исключаются из расчёта или требуют предварительной фильтрации
const filtered = data.filter(d => d.count > 0);
const arcs = pie(filtered);

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


Компоновка сложных диаграмм

d3.pie() часто используется как основа для:

  • stacked donut charts
  • nested pie charts
  • interactive radial dashboards

В таких случаях важна синхронизация нескольких генераторов секторов и общих радиусов.


Взаимодействие с анимацией

Плавные переходы достигаются через интерполяцию углов:

path.transition()
  .duration(750)
  .attrTween("d", function(d) {
    const interpolate = d3.interpolate(this._current, d);
    this._current = interpolate(1);
    return t => arc(interpolate(t));
  });

Сохранение предыдущего состояния позволяет корректно анимировать изменение формы секторов.


Внутренняя модель вычислений

Алгоритм работы включает несколько этапов:

  1. Суммирование значений
  2. Нормализация в диапазон 0–1
  3. Преобразование в радианы
  4. Последовательное накопление углов
  5. Формирование структуры сектора

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


Связка с масштабированием и цветами

Хотя d3.pie() не управляет стилем, типичная связка включает:

  • d3.scaleOrdinal() для цветов
  • индекс элемента для назначения палитры
const color = d3.scaleOrdinal(d3.schemeCategory10);

svg.selectAll("path")
  .data(pie(data))
  .enter()
  .append("path")
  .attr("fill", d => color(d.index));

Цветовое кодирование усиливает читаемость сегментированной структуры.


Расширение через пользовательские поля

В объекты можно включать дополнительные метаданные, которые сохраняются в d.data:

const data = [
  { label: "Chrome", value: 60, region: "global" },
  { label: "Firefox", value: 25, region: "global" }
];

Эти поля могут использоваться для фильтрации, подсказок и интерактивности без изменения логики генератора.