Кастомные цветовые функции

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

Цветовые свойства в Nivo принимают не только строковые значения, но и функции. Такой подход позволяет создавать динамические визуализации, где визуальное представление напрямую зависит от структуры данных.

Базовый принцип:

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

Пример функционального определения:

const getColor = (bar) => {
    return bar.value > 100 ? '#ff4d4f' : '#52c41a'
}

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

Цвет как функция данных

Наиболее мощный механизм кастомизации заключается в передаче функции в проп colors. Она может принимать структуру данных графика.

Пример для столбчатого графика:

<ResponsiveBar
    data={data}
    indexBy="country"
    keys={['hotdog', 'burger', 'sandwich']}
    colors={({ id, data }) => {
        if (id === 'hotdog') return '#e63946'
        if (data.region === 'EU') return '#457b9d'
        return '#a8dadc'
    }}
/>

Здесь доступ к объекту data позволяет учитывать дополнительные поля, не участвующие напрямую в построении графика.

Использование индексов и серий

Функция может опираться на идентификатор серии:

colors={({ id, index }) => {
    const palette = ['#264653', '#2a9d8f', '#e9c46a', '#f4a261']
    return palette[index % palette.length]
}}

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

Интеграция с цветовыми шкалами

Часто кастомные функции комбинируются с интерполяторами и шкалами, особенно из d3-scale. Это позволяет строить непрерывные градиенты.

Пример линейной шкалы:

import { scaleLinear } from 'd3-scale'

const colorScale = scaleLinear()
    .domain([0, 100])
    .range(['#fee5d9', '#a50f15'])

const getColor = (d) => colorScale(d.value)

Передача в компонент:

<ResponsiveLine
    data={data}
    colors={(point) => colorScale(point.data.value)}
/>

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

Условные цветовые правила

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

colors={({ value }) => {
    if (value < 20) return '#d73027'
    if (value < 50) return '#fee08b'
    return '#1a9850'
}}

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

Цветовые функции и темы

В Nivo существует система тем, где цвета могут зависеть от глобального контекста. Кастомные функции могут опираться на theme-параметры:

const theme = {
    textColor: '#333',
    grid: {
        line: {
            stroke: '#e0e0e0'
        }
    }
}

Функции цвета могут использовать внешние переменные темы:

colors={({ data }) => {
    return data.highlight
        ? theme.accentColor
        : theme.textColor
}}

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

Производные цвета и манипуляции оттенками

В кастомных функциях часто применяется преобразование базового цвета: осветление, затемнение, изменение насыщенности. Обычно это реализуется через вспомогательные библиотеки вроде d3-color.

import { color as d3Color } from 'd3-color'

const getColor = (d) => {
    const base = d3Color('#4c78a8')
    base.opacity = d.value / 100
    return base.toString()
}

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

Цветовые функции в взаимодействии с событиями

Функции могут учитывать состояние взаимодействия: hover, active, selected. Хотя сам Nivo не хранит сложную логику состояний внутри цвета, она может передаваться через внешние props.

const selectedIds = new Set(['A', 'C'])

colors={({ id }) => {
    return selectedIds.has(id) ? '#ff7f0e' : '#c7c7c7'
}}

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

Градиенты через функции

Кастомные цветовые функции часто используются для создания псевдоградиентов без использования SVG gradient definitions.

const gradient = (value) => {
    const t = value / 100
    return `rgba(${Math.floor(255 * t)}, 100, 150, 1)`
}

Передача:

colors={(d) => gradient(d.value)}

Такой подход упрощает генерацию визуальных шкал в bar и line графиках.

Комбинирование факторов в цветовой логике

Сложные сценарии требуют объединения нескольких параметров:

colors={({ value, indexValue, data }) => {
    if (data.type === 'critical') return '#d00000'
    if (indexValue.includes('EU') && value > 50) return '#005f73'
    return indexValue === 'fallback' ? '#94d2bd' : '#e9d8a6'
}}

Цветовая функция превращается в полноценный слой бизнес-логики, встроенный в визуализацию.

Цветовые функции и производительность

При больших объёмах данных важно учитывать стоимость вычислений внутри функций. Каждое обращение к colors вызывается для элемента графика, поэтому:

  • избегаются тяжёлые вычисления внутри функции
  • предварительно кэшируются результаты
  • используются мемоизированные маппинги

Оптимизированный вариант:

const colorMap = {
    A: '#1f77b4',
    B: '#ff7f0e',
    C: '#2ca02c'
}

colors={({ id }) => colorMap[id] || '#ccc'}

Расширенные сценарии с доменными правилами

В сложных визуализациях цветовая функция становится отражением предметной области.

const riskColor = ({ riskScore }) => {
    if (riskScore > 80) return '#7f0000'
    if (riskScore > 50) return '#d73027'
    if (riskScore > 20) return '#fc8d59'
    return '#91cf60'
}

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

Использование функций в комбинации с легендами

При использовании кастомных цветов важно сохранять согласованность с легендами. Обычно легенда строится отдельно, но цветовая функция должна быть детерминированной:

const color = ({ id }) => {
    switch (id) {
        case 'chrome': return '#4285F4'
        case 'firefox': return '#FF7139'
        case 'safari': return '#0FB5EE'
        default: return '#999'
    }
}

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

Абстракция цветовых функций

Для масштабируемых приложений цветовые функции часто выносятся в отдельные модули:

export const createColorStrategy = (theme) => (d) => {
    if (d.status === 'error') return theme.error
    if (d.status === 'warning') return theme.warning
    return theme.success
}

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