Размер и форма точек

Во многих компонентах Nivo точки выступают ключевыми элементами визуализации. Особенно это заметно в Line, ScatterPlot, Bump, Radar и ряде производных графиков. Размер точки влияет не только на внешний вид диаграммы, но и на читаемость данных, плотность визуализации и производительность.

Базовое управление точками осуществляется через свойства:

pointSize
pointColor
pointBorderWidth
pointBorderColor

Пример:

<ResponsiveLine
    data={data}
    pointSize={10}
    pointColor="#4f46e5"
    pointBorderWidth={2}
    pointBorderColor="#ffffff"
/>

В этом случае:

  • pointSize задаёт диаметр точки;
  • pointColor определяет цвет заполнения;
  • pointBorderWidth отвечает за толщину границы;
  • pointBorderColor задаёт цвет рамки.

Свойство pointSize

Фиксированный размер

Наиболее распространённый вариант — единый размер для всех точек.

pointSize={8}

Такой подход подходит:

  • для линейных графиков;
  • для небольших наборов данных;
  • для визуально спокойных интерфейсов.

Пример:

<ResponsiveLine
    data={data}
    pointSize={8}
/>

Очень маленькие точки

При больших объёмах данных крупные точки начинают перекрывать линии и друг друга.

pointSize={3}

Подобный размер полезен:

  • для таймсерий;
  • для плотных графиков;
  • для финансовых данных;
  • для телеметрии.

Крупные точки

Иногда необходимо сделать акцент именно на узлах графика.

pointSize={16}

Крупные точки применяются:

  • в дашбордах;
  • в презентационных графиках;
  • в мобильных интерфейсах;
  • при слабой детализации данных.

Динамический размер точек

В ScatterPlot размер точки часто зависит от значения данных.

Пример структуры данных:

const data = [
    {
        id: "dataset",
        data: [
            { x: 10, y: 20, size: 0.5 },
            { x: 15, y: 10, size: 1.2 },
            { x: 30, y: 40, size: 2.1 }
        ]
    }
]

Настройка:

<ResponsiveScatterPlot
    data={data}
    nodeSize={d => d.data.size * 10}
/>

Здесь размер вычисляется функцией.


Использование функций для вычисления размеров

Nivo позволяет передавать callback-функции.

pointSize={(point) => {
    return point.data.y > 50 ? 14 : 6
}}

Логика может зависеть от:

  • значения точки;
  • категории;
  • серии;
  • индекса;
  • состояния интерфейса.

Пример:

<ResponsiveLine
    data={data}
    pointSize={(point) => {
        if (point.data.y > 100) {
            return 18
        }

        return 6
    }}
/>

Адаптивное масштабирование точек

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

const getPointSize = (value) => {
    const min = 0
    const max = 100

    return 4 + (value / max) * 12
}

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

pointSize={(point) => getPointSize(point.data.y)}

Подобная техника создаёт эффект bubble-chart даже внутри обычного line-графика.


Скрытие точек

Иногда точки вовсе не нужны.

pointSize={0}

Это полезно:

  • при очень длинных линиях;
  • для повышения производительности;
  • при построении плавных графиков;
  • в анимированных визуализациях.

Пример:

<ResponsiveLine
    data={data}
    pointSize={0}
/>

Форма точек

Помимо размеров Nivo поддерживает изменение формы.

Используется свойство:

pointSymbol

Оно принимает React-компонент.


Кастомный pointSymbol

Простейший пример:

const CustomPoint = ({ size, color, borderWidth, borderColor }) => (
    <circle
        r={size / 2}
        fill={color}
        stroke={borderColor}
        strokeWidth={borderWidth}
    />
)

Подключение:

<ResponsiveLine
    data={data}
    pointSymbol={CustomPoint}
/>

Квадратные точки

Вместо круга можно использовать прямоугольник.

const SquarePoint = ({
    size,
    color,
    borderWidth,
    borderColor
}) => (
    <rect
        x={-size / 2}
        y={-size / 2}
        width={size}
        height={size}
        fill={color}
        stroke={borderColor}
        strokeWidth={borderWidth}
    />
)

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

pointSymbol={SquarePoint}

Ромбовидные точки

const DiamondPoint = ({
    size,
    color,
    borderWidth,
    borderColor
}) => (
    <rect
        x={-size / 2}
        y={-size / 2}
        width={size}
        height={size}
        transform={`rotate(45)`}
        fill={color}
        stroke={borderColor}
        strokeWidth={borderWidth}
    />
)

Треугольные точки

const TrianglePoint = ({
    size,
    color,
    borderWidth,
    borderColor
}) => (
    <polygon
        points={`
            0,-${size / 2}
            ${size / 2},${size / 2}
            -${size / 2},${size / 2}
        `}
        fill={color}
        stroke={borderColor}
        strokeWidth={borderWidth}
    />
)

Использование SVG внутри pointSymbol

Поскольку pointSymbol представляет собой React-компонент, допускается использование практически любых SVG-элементов.

Пример звезды:

const StarPoint = ({ color }) => (
    <path
        d="M10 1 L12 7 H18 L13 11 L15 17 L10 13 L5 17 L7 11 L2 7 H8 Z"
        fill={color}
        transform="scale(0.8)"
    />
)

Работа с координатами внутри pointSymbol

Компонент получает координаты автоматически.

Стандартные параметры:

const CustomPoint = ({
    size,
    color,
    borderWidth,
    borderColor
}) => {
    return (
        <circle
            r={size / 2}
            fill={color}
            stroke={borderColor}
            strokeWidth={borderWidth}
        />
    )
}

Позиционирование вручную обычно не требуется, поскольку Nivo уже размещает точку в нужной координате.


Добавление теней

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

const ShadowPoint = ({
    size,
    color
}) => (
    <g>
        <circle
            r={size / 2 + 2}
            fill="rgba(0,0,0,0.15)"
        />

        <circle
            r={size / 2}
            fill={color}
        />
    </g>
)

Полупрозрачные точки

pointColor="rgba(79, 70, 229, 0.5)"

Подобный стиль полезен:

  • в scatter-графиках;
  • при высокой плотности;
  • для heat-like визуализации.

Границы точек

Тонкая белая граница часто улучшает читаемость.

pointBorderWidth={2}
pointBorderColor="#fff"

Особенно эффективно:

  • на тёмных графиках;
  • при пересечении линий;
  • в stacked-графиках.

Наследование цвета линии

Точка может автоматически получать цвет серии.

pointColor={{ from: "serieColor" }}

Пример:

<ResponsiveLine
    data={data}
    pointColor={{ from: "serieColor" }}
/>

Автоматическое затемнение границы

pointBorderColor={{
    from: "serieColor",
    modifiers: [
        ["darker", 0.6]
    ]
}}

Такой подход создаёт более объёмный вид точек.


Условное изменение формы

Форма точки может зависеть от данных.

pointSymbol={(props) => {
    if (props.datum.data.y > 50) {
        return <TrianglePoint {...props} />
    }

    return <SquarePoint {...props} />
}}

Анимация точек

Nivo поддерживает встроенную анимацию через motionConfig.

motionConfig="gentle"

Пример:

<ResponsiveLine
    data={data}
    pointSize={12}
    motionConfig="wobbly"
/>

При обновлении данных точки плавно изменяют положение и размеры.


Производительность при большом количестве точек

Большое число SVG-элементов может заметно нагружать браузер.

Основные рекомендации:

Уменьшение размеров

pointSize={2}

Отключение точек

pointSize={0}

Упрощение pointSymbol

Сложные SVG-компоненты:

  • фильтры;
  • тени;
  • маски;
  • path-элементы;

могут значительно снижать FPS.


Canvas вместо SVG

Для экстремально больших объёмов данных используется Canvas-версия.

ResponsiveLineCanvas

или:

ResponsiveScatterPlotCanvas

Canvas работает значительно быстрее при тысячах точек.


Интерактивное увеличение точки при наведении

Пример кастомного символа:

const HoverPoint = ({
    size,
    color,
    isInteractive
}) => (
    <circle
        r={isInteractive ? size : size / 2}
        fill={color}
    />
)

Комбинирование размера и прозрачности

Один из наиболее информативных подходов.

nodeSize={(d) => d.data.importance * 12}
colors={{ scheme: "category10" }}
opacity={0.6}

Такой стиль помогает отображать:

  • интенсивность;
  • вероятность;
  • важность;
  • вес данных.

Создание эффекта bubble chart

Даже линейный график может визуально напоминать bubble chart.

<ResponsiveLine
    data={data}
    pointSize={(point) => point.data.volume / 10}
    pointColor={{ from: "serieColor" }}
    pointBorderWidth={1}
    pointBorderColor="#fff"
/>

Использование градиентов

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

defs={[
    {
        id: "gradient",
        type: "linearGradient",
        colors: [
            { offset: 0, color: "#60a5fa" },
            { offset: 100, color: "#2563eb" }
        ]
    }
]}
pointColor="url(#gradient)"

Практический пример комплексной настройки

<ResponsiveLine
    data={data}
    margin={{
        top: 40,
        right: 40,
        bottom: 60,
        left: 80
    }}
    pointSize={(point) => {
        if (point.data.y > 80) return 16
        if (point.data.y > 40) return 10

        return 6
    }}
    pointColor={{ from: "serieColor" }}
    pointBorderWidth={2}
    pointBorderColor={{
        from: "serieColor",
        modifiers: [["darker", 0.5]]
    }}
    pointSymbol={TrianglePoint}
    enablePoints={true}
    useMesh={true}
    motionConfig="gentle"
/>

В данном примере реализованы:

  • динамический размер;
  • кастомная форма;
  • наследование цвета;
  • автоматическое затемнение границы;
  • анимация;
  • улучшенная интерактивность через useMesh.

Особенности ScatterPlot

В ScatterPlot размеры особенно важны, поскольку точки являются главным визуальным элементом.

Пример:

<ResponsiveScatterPlot
    data={data}
    nodeSize={(node) => node.data.z * 20}
/>

Поле z часто используется как третье измерение данных.


Масштабирование через d3-scale

Для сложных проектов удобно использовать d3-scale.

import { scaleLinear } from "d3-scale"

const sizeScale = scaleLinear()
    .domain([0, 1000])
    .range([4, 30])

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

nodeSize={(node) => sizeScale(node.data.value)}

Визуальная иерархия через размеры

Размеры точек позволяют создавать акценты:

Размер Назначение
2–4 Плотные данные
5–8 Стандартные графики
9–14 Акцентированные точки
15+ Bubble-представление

Подходы к выбору формы

Форма Когда использовать
Круг Универсальный вариант
Квадрат Технические графики
Ромб Выделение категории
Треугольник Сигналы и события
Звезда Особые точки

Типичные ошибки

Слишком крупные точки

pointSize={30}

Приводит к:

  • перекрытию линий;
  • потере читаемости;
  • проблемам с tooltip.

Сложный SVG в тысячах точек

<filter>
<mask>
<clipPath>

Сильно ухудшает производительность.


Отсутствие границы

На тёмных фонах точки могут сливаться с линиями.

pointBorderWidth={0}

Неправильное масштабирование

Если размер зависит от данных напрямую:

pointSize={(p) => p.data.y}

график может стать нечитабельным. Необходима нормализация диапазона.