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
}
Такой подход позволяет переиспользовать логику в разных типах графиков без дублирования кода.