Форматы данных для ScatterPlot

Компонент ScatterPlot из библиотеки Nivo предназначен для отображения наборов точек в двумерной системе координат. Каждая точка имеет координаты x и y, а сами точки объединяются в серии.

Базовая структура данных выглядит следующим образом:

const data = [
    {
        id: 'series-1',
        data: [
            { x: 10, y: 20 },
            { x: 15, y: 35 },
            { x: 25, y: 12 },
        ],
    },
    {
        id: 'series-2',
        data: [
            { x: 5, y: 8 },
            { x: 12, y: 28 },
            { x: 18, y: 40 },
        ],
    },
]

Каждый объект верхнего уровня представляет отдельную серию данных.

Основные поля структуры

Поле Назначение
id Уникальный идентификатор серии
data Массив точек
x Координата точки по оси X
y Координата точки по оси Y

Формат точек

Каждая точка внутри массива data является обычным объектом JavaScript.

Минимальный формат

{
    x: 12,
    y: 30
}

Полный формат с дополнительными полями

{
    x: 12,
    y: 30,
    label: 'Точка A',
    color: '#ff0000',
    size: 16
}

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

  • tooltip
  • кастомных рендерах
  • пользовательских слоях
  • динамической стилизации

Числовые значения координат

Наиболее распространённый вариант — использование чисел.

const data = [
    {
        id: 'temperature',
        data: [
            { x: 0, y: 15 },
            { x: 10, y: 18 },
            { x: 20, y: 25 },
            { x: 30, y: 31 },
        ],
    },
]

Такой формат подходит для:

  • математических графиков
  • научных данных
  • статистики
  • измерений
  • аналитики

Строковые значения оси X

ScatterPlot поддерживает строковые значения координат.

const data = [
    {
        id: 'sales',
        data: [
            { x: 'Январь', y: 120 },
            { x: 'Февраль', y: 180 },
            { x: 'Март', y: 90 },
        ],
    },
]

При использовании строк ось становится категориальной.

Особенности категориальной оси

  • точки располагаются по категориям
  • расстояние между элементами одинаковое
  • отсутствует числовая интерполяция

Работа с датами

ScatterPlot поддерживает отображение временных данных.

Формат ISO-строк

const data = [
    {
        id: 'traffic',
        data: [
            { x: '2025-01-01', y: 1200 },
            { x: '2025-01-02', y: 1700 },
            { x: '2025-01-03', y: 900 },
        ],
    },
]

Настройка временной оси

xScale={{
    type: 'time',
    format: '%Y-%m-%d',
    precision: 'day'
}}

Использование объектов Date

Вместо строк допускается применение объектов Date.

const data = [
    {
        id: 'events',
        data: [
            { x: new Date(2025, 0, 1), y: 12 },
            { x: new Date(2025, 0, 2), y: 28 },
            { x: new Date(2025, 0, 3), y: 19 },
        ],
    },
]

Несколько серий данных

ScatterPlot особенно эффективен при сравнении наборов данных.

const data = [
    {
        id: 'men',
        data: [
            { x: 170, y: 65 },
            { x: 180, y: 78 },
            { x: 190, y: 90 },
        ],
    },
    {
        id: 'women',
        data: [
            { x: 160, y: 50 },
            { x: 170, y: 60 },
            { x: 180, y: 72 },
        ],
    },
]

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

  • отдельный цвет
  • легенду
  • независимый набор точек

Вложенная структура данных

Во многих приложениях данные приходят из API в неподходящем формате.

Исходный формат

const apiData = [
    {
        country: 'Germany',
        stats: {
            population: 83,
            gdp: 4200,
        },
    },
    {
        country: 'France',
        stats: {
            population: 67,
            gdp: 3100,
        },
    },
]

Преобразование для ScatterPlot

const data = [
    {
        id: 'countries',
        data: apiData.map(item => ({
            x: item.stats.population,
            y: item.stats.gdp,
            country: item.country,
        })),
    },
]

Динамическое формирование данных

ScatterPlot часто используется вместе с map.

const values = [10, 15, 22, 30]

const data = [
    {
        id: 'dynamic',
        data: values.map((value, index) => ({
            x: index,
            y: value,
        })),
    },
]

Формирование данных из API

Пример ответа сервера

[
    {
        "year": 2020,
        "profit": 120
    },
    {
        "year": 2021,
        "profit": 180
    }
]

Подготовка данных

async function loadChartData() {
    const response = await fetch('/api/stats')
    const result = await response.json()

    return [
        {
            id: 'profit',
            data: result.map(item => ({
                x: item.year,
                y: item.profit,
            })),
        },
    ]
}

Использование дополнительных метаданных

Каждая точка может содержать произвольные поля.

const data = [
    {
        id: 'users',
        data: [
            {
                x: 20,
                y: 1500,
                userId: 15,
                username: 'alex',
                active: true,
            },
        ],
    },
]

Эти данные особенно полезны в tooltip:

tooltip={({ node }) => (
    <div>
        <strong>{node.data.username}</strong>
        <div>ID: {node.data.userId}</div>
    </div>
)}

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

ScatterPlot может работать как bubble chart.

Формат данных

const data = [
    {
        id: 'products',
        data: [
            {
                x: 12,
                y: 30,
                z: 100,
            },
            {
                x: 20,
                y: 18,
                z: 250,
            },
        ],
    },
]

Настройка размеров

nodeSize={node => node.data.z / 10}

Поле z обычно обозначает:

  • объём
  • количество
  • популярность
  • вес
  • рейтинг

Nullable-значения

Иногда данные содержат пропуски.

const data = [
    {
        id: 'metrics',
        data: [
            { x: 1, y: 10 },
            { x: 2, y: null },
            { x: 3, y: 25 },
        ],
    },
]

Следует учитывать:

  • null может привести к ошибкам рендера
  • лучше предварительно фильтровать значения

Фильтрация

const filtered = sourceData.filter(
    item => item.y !== null
)

Генерация больших наборов данных

ScatterPlot часто используется для визуализации тысяч точек.

const data = [
    {
        id: 'random',
        data: Array.from({ length: 1000 }, (_, i) => ({
            x: i,
            y: Math.random() * 100,
        })),
    },
]

Типизация данных в TypeScript

Базовые типы

type Point = {
    x: number
    y: number
}

type Serie = {
    id: string
    data: Point[]
}

Расширенные типы

type UserPoint = {
    x: number
    y: number
    username: string
    age: number
}

type UserSerie = {
    id: string
    data: UserPoint[]
}

Формат данных для ResponsiveScatterPlot

Компонент ResponsiveScatterPlot использует тот же формат.

<ResponsiveScatterPlot
    data={data}
/>

Различий между ScatterPlot и ResponsiveScatterPlot в структуре данных нет.


Нормализация данных

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

Исходные данные

[
    { valueX: 12, valueY: 55 },
    { valueX: 18, valueY: 70 },
]

Нормализованные данные

const normalized = raw.map(item => ({
    x: item.valueX,
    y: item.valueY,
}))

Ошибки форматов данных

Отсутствует поле data

Неверно:

[
    {
        id: 'test'
    }
]

Верно:

[
    {
        id: 'test',
        data: []
    }
]

Неправильный тип координат

Неверно:

{
    x: {},
    y: []
}

Верно:

{
    x: 10,
    y: 20
}

Дублирование id

Неверно:

[
    { id: 'series', data: [] },
    { id: 'series', data: [] },
]

Идентификаторы серий должны быть уникальными.


Подготовка данных для реальных задач

Аналитика продаж

const data = [
    {
        id: 'sales',
        data: [
            { x: 1, y: 5000 },
            { x: 2, y: 7200 },
            { x: 3, y: 6100 },
        ],
    },
]

Географические данные

const data = [
    {
        id: 'cities',
        data: [
            {
                x: 37.6173,
                y: 55.7558,
                city: 'Москва',
            },
            {
                x: 30.3141,
                y: 59.9386,
                city: 'Санкт-Петербург',
            },
        ],
    },
]

Научные измерения

const data = [
    {
        id: 'experiment',
        data: [
            {
                x: 0.1,
                y: 12.4,
            },
            {
                x: 0.2,
                y: 15.9,
            },
        ],
    },
]

Оптимизация структуры данных

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

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

Плохой вариант

{
    x: {
        value: {
            current: 12
        }
    }
}

Хороший вариант

{
    x: 12
}

Проверка данных перед рендером

Валидация

function validateData(data) {
    return data.every(series =>
        Array.isArray(series.data)
    )
}

Проверка координат

function isValidPoint(point) {
    return (
        typeof point.x !== 'undefined' &&
        typeof point.y !== 'undefined'
    )
}

Универсальная функция преобразования

function createScatterData(items, xKey, yKey, id) {
    return [
        {
            id,
            data: items.map(item => ({
                x: item[xKey],
                y: item[yKey],
            })),
        },
    ]
}

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

const chartData = createScatterData(
    users,
    'age',
    'salary',
    'employees'
)

Связь структуры данных и шкал

Тип данных напрямую влияет на настройки xScale и yScale.

Линейная шкала

xScale={{
    type: 'linear'
}}

Используется для чисел.


Временная шкала

xScale={{
    type: 'time'
}}

Используется для дат.


Категориальная шкала

xScale={{
    type: 'point'
}}

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


Комбинирование разных типов данных

ScatterPlot допускает смешанные форматы.

const data = [
    {
        id: 'hybrid',
        data: [
            {
                x: '2025-01',
                y: 120,
                region: 'EU',
                users: 500,
            },
        ],
    },
]

Главное условие — корректная настройка шкалы.


Immutable-подход

При обновлении данных желательно создавать новые объекты.

Неверно

data[0].data.push({
    x: 10,
    y: 20,
})

Верно

const updated = [
    {
        ...data[0],
        data: [
            ...data[0].data,
            {
                x: 10,
                y: 20,
            },
        ],
    },
]

Такой подход особенно важен в React-приложениях.