Механизм renderAnnotation в Nivo позволяет полностью
переопределять визуальное представление аннотаций на графиках, заменяя
стандартные подсказки и выделения на произвольные React-элементы. Это
расширение используется при необходимости встроить в визуализацию
сложную логику отображения контекстных меток, пользовательских маркеров,
интерактивных подсказок или комбинированных элементов интерфейса.
Аннотации в Nivo представляют собой слой поверх графика, который не влияет на данные, но добавляет смысловые визуальные акценты. Они применяются для:
Стандартная система аннотаций поддерживает базовые формы (линии,
прямоугольники, кружки), однако при сложных интерфейсных требованиях
этого недостаточно. В таких случаях используется
renderAnnotation, позволяющий заменить стандартный
рендеринг.
Во многих компонентах Nivo, таких как ResponsiveLine,
ResponsiveBar и других, renderAnnotation
передается как функция:
renderAnnotation: (annotation) => ReactNode
Объект annotation содержит данные, необходимые для
построения элемента:
x, y или координаты в масштабе
графика;size или геометрические параметры;datum — исходная точка данных;serieId — идентификатор серии;borderColor, color — стилистические
параметры;note — текстовое описание (если задано);Кастомная аннотация строится как React-компонент, возвращаемый функцией:
const MyAnnotation = ({ x, y, note }) => {
return (
<g transform={`translate(${x}, ${y})`}>
<circle r={6} fill="#ff5500" />
<text
x={10}
y={-10}
fontSize={12}
fill="#333"
>
{note}
</text>
</g>
);
};
Использование в графике:
<ResponsiveLine
data={data}
margin={{ top: 50, right: 50, bottom: 50, left: 60 }}
annotations={[
{
type: 'point',
x: 10,
y: 100,
note: 'Пиковое значение'
}
]}
renderAnnotation={(annotation) => <MyAnnotation {...annotation} />}
/>
Nivo использует внутреннюю систему координат SVG, где аннотации позиционируются относительно уже отрендеренного графика. Это означает:
x и y уже преобразованы через
шкалы (scales);Для сложных случаев важно учитывать смещения margin, так
как они влияют на итоговую позицию.
<g transform={`translate(${x + offsetX}, ${y + offsetY})`}>
Аннотации могут включать интерактивные элементы: hover-эффекты, кликабельные зоны, динамическое изменение состояния.
const InteractiveAnnotation = ({ x, y, datum }) => {
const [active, setActive] = useState(false);
return (
<g
transform={`translate(${x}, ${y})`}
onMouseEn ter={() => setActive(true)}
onMouseLe ave={() => setActive(false)}
>
<circle r={active ? 10 : 6} fill={active ? '#1f77b4' : '#999'} />
{active && (
<text x={12} y={-12} fontSize={11}>
{datum.label}
</text>
)}
</g>
);
};
Такой подход позволяет превращать аннотации в полноценные элементы интерфейса, а не только визуальные маркеры.
renderAnnotation не ограничивается базовыми
SVG-элементами. Внутри можно использовать:
foreignObject для встраивания HTML;Пример с HTML-вставкой:
const HtmlAnnotation = ({ x, y, note }) => {
return (
<foreignObject x={x} y={y} width={120} height={60}>
<div style={{
background: 'white',
border: '1px solid #ddd',
padding: '6px',
borderRadius: '6px'
}}>
<strong>{note}</strong>
</div>
</foreignObject>
);
};
При наличии массива аннотаций renderAnnotation
вызывается для каждой из них. Это позволяет строить сложные системы
меток:
annotations={[
{ x: 5, y: 20, note: 'Старт' },
{ x: 15, y: 80, note: 'Рост' },
{ x: 30, y: 40, note: 'Спад' }
]}
Каждая аннотация обрабатывается независимо, что позволяет комбинировать разные типы отображения.
Аннотации могут адаптироваться к данным графика:
const DynamicAnnotation = ({ datum, x, y }) => {
const color = datum.value > 100 ? 'red' : 'green';
return (
<g transform={`translate(${x}, ${y})`}>
<circle r={5} fill={color} />
<text x={8} y={4} fontSize={10} fill={color}>
{datum.value}
</text>
</g>
);
};
Подобный подход используется для визуального кодирования состояния данных прямо на графике.
В архитектуре Nivo аннотации являются частью слоя поверх основной
отрисовки. renderAnnotation интегрируется в pipeline
рендера следующим образом:
renderAnnotation для каждой сущности;Это означает, что аннотации всегда находятся в верхнем слое SVG и не перекрываются данными графика.
При большом количестве аннотаций важно учитывать нагрузку:
React.memo.const MemoAnnotation = React.memo(({ x, y, note }) => {
return (
<g transform={`translate(${x}, ${y})`}>
<text>{note}</text>
</g>
);
});
При использовании логарифмических или временных шкал аннотации автоматически получают преобразованные координаты. Это означает, что:
x может представлять timestamp в преобразованном
виде;y зависит от домена шкалы;Однако при комбинировании нескольких графиков важно следить за синхронизацией шкал, иначе аннотации будут визуально смещены.
Часто аннотации соединяются с точками графика линиями:
const ConnectedAnnotation = ({ x, y, x1, y1 }) => {
return (
<g>
<line x1={x} y1={y} x2={x1} y2={y1} stroke="#999" />
<circle cx={x1} cy={y1} r={4} />
</g>
);
};
Это используется для создания callout-маркеров, поясняющих конкретные точки данных.
В комплексных дашбордах аннотации становятся частью аналитического слоя. Они могут:
Такая гибкость делает renderAnnotation ключевым
инструментом кастомизации визуализаций в Nivo, позволяя выходить за
пределы стандартной декларативной модели и внедрять прикладную логику
прямо в слой графического отображения.