d3-annotation: библиотека аннотаций

Основные принципы работы аннотаций

Библиотека d3-annotation предназначена для добавления поясняющих элементов к графикам, построенным на D3.js. Аннотации представляют собой визуальные маркеры, связанные с конкретными точками, областями или объектами на графике, и состоят из текста, линий-соединителей и вспомогательной геометрии.

Ключевая идея заключается в разделении визуализации данных и поясняющего слоя. Аннотации строятся поверх SVG-графики и не вмешиваются в вычисление данных, а лишь интерпретируют уже существующие координаты.

Аннотация в d3-annotation обычно включает:

  • note — текстовое описание
  • subject — выделяемый объект (точка, область, линия)
  • connector — линия, связывающая текст и объект

Установка и подключение библиотеки

Библиотека доступна через npm и CDN.

Через npm:

npm install d3-annotation

Подключение в модуле:

import { annotation, annotationLabel, annotationCallout } from "d3-annotation";

Через CDN:

<script src="https://cdn.jsdelivr.net/npm/d3-annotation@2.5.1/dist/d3-annotation.min.js"></script>

После подключения в глобальной области появляется объект d3.annotation.


Базовая структура аннотации

Аннотация описывается объектом с координатами и текстом:

const annotations = [
  {
    note: {
      title: "Пиковое значение",
      label: "Максимум функции"
    },
    x: 120,
    y: 80,
    dx: 30,
    dy: -40
  }
];

Здесь:

  • x, y — координаты точки на SVG
  • dx, dy — смещение текста относительно точки
  • note — содержимое аннотации

Создание аннотационного слоя:

const makeAnnotations = d3.annotation()
  .type(d3.annotationLabel)
  .annotations(annotations);

d3.select("svg")
  .append("g")
  .attr("class", "annotations")
  .call(makeAnnotations);

Типы аннотаций

Библиотека предоставляет несколько стандартных типов оформления.

annotationLabel

Простая подпись без сложного выделения области.

d3.annotationLabel

Используется для минималистичных пояснений, где не требуется рамка или сложная геометрия.


annotationCallout

Аннотация с прямоугольной областью и линией-указателем.

d3.annotationCallout

Подходит для выделения точек интереса на диаграммах.


annotationCalloutElbow

Аннотация с «ломаным» соединителем, создающим эффект углового перехода линии.

d3.annotationCalloutElbow

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


annotationCalloutCircle

Выделение круглой области вокруг объекта.

d3.annotationCalloutCircle

Используется для акцентирования точечных значений или событий.


Конфигурация текста и структуры note

Объект note управляет текстовой частью аннотации.

{
  note: {
    title: "Заголовок",
    label: "Дополнительное описание",
    wrap: 150
  }
}

Параметр wrap задаёт ширину переноса текста в пикселях.

Дополнительно можно задавать HTML-стилизацию через классы:

{
  note: {
    label: "Текст",
    className: "custom-note"
  }
}

Привязка аннотаций к данным

Аннотации часто строятся на основе массива данных.

const data = [
  { x: 10, y: 20, label: "A" },
  { x: 40, y: 80, label: "B" }
];

const annotations = data.map(d => ({
  note: { label: d.label },
  x: xScale(d.x),
  y: yScale(d.y),
  dx: 10,
  dy: -10
}));

Здесь важно, что координаты преобразуются через шкалы D3 (xScale, yScale), что обеспечивает корректное позиционирование на графике.


Интеграция с осями и масштабами

При использовании D3-осей аннотации должны учитывать трансформации координат.

const xScale = d3.scaleLinear()
  .domain([0, 100])
  .range([0, width]);

const yScale = d3.scaleLinear()
  .domain([0, 100])
  .range([height, 0]);

Аннотация привязывается уже к вычисленным пикселям:

x: xScale(50),
y: yScale(75)

При изменении масштаба (zoom/pan) аннотации должны обновляться вместе с графиком.


Настройка соединителей (connector)

Соединитель управляет линией между текстом и объектом.

{
  connector: {
    end: "arrow",
    type: "line",
    lineType: "horizontal"
  }
}

Основные параметры:

  • end — форма конца линии (arrow, dot)
  • type — тип линии
  • lineType — поведение (прямая, горизонтальная, вертикальная)

Стилизация аннотаций через CSS

Аннотации рендерятся как SVG-элементы и могут стилизоваться через CSS.

.annotation text {
  font-size: 12px;
  fill: #333;
}

.annotation path {
  stroke: #999;
  stroke-width: 1px;
}

Можно также переопределять стили отдельных классов:

.annotation.callout {
  fill: none;
  stroke: black;
}

Анимация аннотаций

D3 позволяет анимировать появление аннотаций через стандартные transition.

d3.selectAll(".annotation")
  .style("opacity", 0)
  .transition()
  .duration(800)
  .style("opacity", 1);

Также можно анимировать изменение координат:

d3.selectAll(".annotation")
  .transition()
  .duration(500)
  .attr("transform", d => `translate(${d.x}, ${d.y})`);

Динамическое обновление аннотаций

При изменении данных аннотации пересоздаются или обновляются через повторный вызов call.

function updateAnnotations(data) {
  const annotations = data.map(d => ({
    note: { label: d.label },
    x: xScale(d.x),
    y: yScale(d.y),
    dx: 15,
    dy: -15
  }));

  const makeAnnotations = d3.annotation()
    .type(d3.annotationCallout)
    .annotations(annotations);

  svg.select(".annotations")
    .call(makeAnnotations);
}

Работа с несколькими слоями аннотаций

Аннотации можно группировать по слоям для сложных визуализаций:

const layer1 = svg.append("g").attr("class", "annotations-primary");
const layer2 = svg.append("g").attr("class", "annotations-secondary");

Это позволяет разделять смысловые уровни:

  • ключевые события
  • вспомогательные пояснения
  • технические пометки

Пользовательские типы аннотаций

Библиотека позволяет создавать собственные типы через расширение.

const customType = d3.annotationCustomType(
  d3.annotationCallout,
  {
    connector: { type: "elbow" }
  }
);

const makeAnnotations = d3.annotation()
  .type(customType)
  .annotations(annotations);

Такой подход используется для стандартизации визуального языка в проекте.


Частые ошибки при использовании

Некорректное позиционирование аннотаций:

  • использование координат до применения scale
  • игнорирование transform контейнера

Наложение текста:

  • слишком маленькие значения dx/dy
  • отсутствие wrap у длинных текстов

Несогласованность при zoom:

  • аннотации не пересчитываются при изменении масштаба

Организация больших наборов аннотаций

При большом количестве аннотаций используется группировка и фильтрация:

const filtered = annotations.filter(d => d.importance > 5);

Также применяется ленивое отображение:

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

Использование в сложных визуализациях

В комбинированных графиках (линии + бары + scatter) аннотации выступают как независимый слой интерпретации данных. Они не зависят от типа графика, а работают исключительно с координатами SVG.

Типичная структура:

SVG
 ├── axes
 ├── chart elements
 ├── annotations layer
 ├── overlays

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