Компонент ParallelCoordinates библиотеки Nivo предназначен
для визуализации многомерных наборов данных. Вместо традиционных
двумерных графиков используется система параллельных вертикальных осей,
каждая из которых описывает отдельный параметр объекта.
Подобный подход особенно эффективен для анализа:
Каждая запись данных отображается в виде линии, проходящей через все оси.
Пример структуры данных:
const data = [
{
id: 'server-1',
cpu: 78,
memory: 64,
network: 120,
disk: 55
},
{
id: 'server-2',
cpu: 35,
memory: 82,
network: 95,
disk: 40
}
]
Для работы используется пакет:
npm install @nivo/parallel-coordinates
Базовый пример:
import { ResponsiveParallelCoordinates } from '@nivo/parallel-coordinates'
const MyChart = () => (
<ResponsiveParallelCoordinates
data={data}
variables={[
{
id: 'cpu',
value: 'cpu'
},
{
id: 'memory',
value: 'memory'
},
{
id: 'network',
value: 'network'
},
{
id: 'disk',
value: 'disk'
}
]}
margin={{ top: 50, right: 80, bottom: 50, left: 80 }}
/>
)
variablesГлавная настройка осей выполняется через массив
variables.
Каждый объект массива описывает одну вертикальную ось.
Минимальная конфигурация:
variables={[
{
id: 'cpu',
value: 'cpu'
}
]}
idИдентификатор оси.
{
id: 'cpu'
}
Используется:
Значение должно быть уникальным.
valueУказывает, откуда брать значение из объекта данных.
{
id: 'cpu',
value: 'cpu'
}
Фактически:
item['cpu']
Можно использовать функцию:
{
id: 'load',
value: item => item.cpu * item.memory
}
Это позволяет:
minМинимальное значение шкалы.
{
id: 'cpu',
value: 'cpu',
min: 0
}
maxМаксимальное значение.
{
id: 'cpu',
value: 'cpu',
max: 100
}
Полная настройка:
{
id: 'cpu',
value: 'cpu',
min: 0,
max: 100
}
Если min и max не заданы, компонент
вычисляет диапазон автоматически.
{
id: 'temperature',
value: 'temperature'
}
В этом случае:
Можно вычислять диапазон программно.
{
id: 'traffic',
value: 'traffic',
min: data => Math.min(...data.map(d => d.traffic)),
max: data => Math.max(...data.map(d => d.traffic))
}
Это удобно для:
legendТекстовая подпись оси.
{
id: 'cpu',
value: 'cpu',
legend: 'CPU Usage'
}
variables={[
{
id: 'cpu',
value: 'cpu',
legend: 'Загрузка CPU'
},
{
id: 'memory',
value: 'memory',
legend: 'Память'
}
]}
Можно использовать переносы:
{
id: 'network',
value: 'network',
legend: 'Network\nTraffic'
}
tickFormatПозволяет управлять отображением значений шкалы.
Пример процентов:
{
id: 'cpu',
value: 'cpu',
tickFormat: value => `${value}%`
}
{
id: 'revenue',
value: 'revenue',
tickFormat: value => `$${value}`
}
{
id: 'requests',
value: 'requests',
tickFormat: value => `${value / 1000}k`
}
ticksPositionОпределяет положение делений.
{
id: 'cpu',
value: 'cpu',
ticksPosition: 'before'
}
Возможные значения:
beforeafter{
id: 'memory',
value: 'memory',
tickValues: 5
}
{
id: 'cpu',
value: 'cpu',
tickValues: [0, 25, 50, 75, 100]
}
Используется по умолчанию.
{
id: 'temperature',
value: 'temperature'
}
{
id: 'traffic',
value: 'traffic',
scale: {
type: 'log',
base: 10
}
}
Подходит для данных с большим разбросом значений.
{
id: 'balance',
value: 'balance',
min: -100,
max: 100
}
Используется при наличии отрицательных значений.
{
id: 'cpu',
value: 'cpu',
color: '#ff0000'
}
axisWidth={2}
theme={{
axis: {
ticks: {
line: {
stroke: '#999'
}
}
}
}}
axisLegendPosition="start"
Варианты:
startmiddleendaxisLegendOffset={40}
Компонент автоматически распределяет оси по ширине контейнера.
При большом количестве параметров возникают проблемы:
Решение — увеличение ширины контейнера.
<div style={{ width: 1400, height: 500 }}>
<ResponsiveParallelCoordinates />
</div>
Порядок определяется порядком объектов в variables.
variables={[
{
id: 'cpu',
value: 'cpu'
},
{
id: 'memory',
value: 'memory'
},
{
id: 'network',
value: 'network'
}
]}
Изменение порядка:
variables={[
{
id: 'network',
value: 'network'
},
{
id: 'cpu',
value: 'cpu'
},
{
id: 'memory',
value: 'memory'
}
]}
Ось можно исключить из конфигурации.
const visibleVariables = variables.filter(v => v.visible)
Пример:
[
{
id: 'cpu',
visible: true
},
{
id: 'debug',
visible: false
}
]
const mobileVariables = [
{
id: 'cpu',
value: 'cpu'
},
{
id: 'memory',
value: 'memory'
}
]
const desktopVariables = [
{
id: 'cpu',
value: 'cpu'
},
{
id: 'memory',
value: 'memory'
},
{
id: 'network',
value: 'network'
},
{
id: 'disk',
value: 'disk'
},
{
id: 'temperature',
value: 'temperature'
}
]
{
id: 'profit',
value: 'profit',
min: -5000,
max: 5000
}
Для корректного отображения рекомендуется:
{
id: 'temperature',
value: 'temperature',
tickFormat: value => `${value}°C`
}
{
id: 'speed',
value: 'speed',
tickFormat: value => `${value} km/h`
}
{
id: 'memory',
value: 'memory',
tickFormat: value => `${value} GB`
}
Nivo использует экосистему D3.
Можно подключить:
npm install d3-format
Пример:
import { format } from 'd3-format'
const currency = format('$,.2f')
Использование:
{
id: 'revenue',
value: 'revenue',
tickFormat: currency
}
const AxisLabel = ({ id }) => (
<text>
{id.toUpperCase()}
</text>
)
Полная конфигурация:
theme={{
axis: {
domain: {
line: {
stroke: '#555',
strokeWidth: 2
}
},
legend: {
text: {
fontSize: 14,
fill: '#222'
}
},
ticks: {
line: {
stroke: '#777',
strokeWidth: 1
},
text: {
fontSize: 11,
fill: '#555'
}
}
}
}}
const [variables, setVariables] = useState(initialVariables)
Изменение:
setVariables(prev =>
prev.filter(v => v.id !== 'network')
)
Автоматическое создание конфигурации:
const variables = Object.keys(data[0])
.filter(key => key !== 'id')
.map(key => ({
id: key,
value: key
}))
При 20–50 осях возникают:
Оптимизации:
lineOpacity={0.2}
lineWidth={1}
isInteractive={false}
import { ResponsiveParallelCoordinates } from '@nivo/parallel-coordinates'
import { format } from 'd3-format'
const percent = format('.0%')
const data = [
{
id: 'node-1',
cpu: 0.72,
memory: 0.55,
network: 140,
disk: 82
},
{
id: 'node-2',
cpu: 0.48,
memory: 0.91,
network: 95,
disk: 40
}
]
export const Chart = () => (
<div style={{ height: 600 }}>
<ResponsiveParallelCoordinates
data={data}
variables={[
{
id: 'cpu',
value: 'cpu',
min: 0,
max: 1,
tickFormat: percent,
legend: 'CPU'
},
{
id: 'memory',
value: 'memory',
min: 0,
max: 1,
tickFormat: percent,
legend: 'Memory'
},
{
id: 'network',
value: 'network',
tickFormat: v => `${v} Mb/s`,
legend: 'Network'
},
{
id: 'disk',
value: 'disk',
min: 0,
max: 100,
tickFormat: v => `${v}%`,
legend: 'Disk'
}
]}
margin={{
top: 60,
right: 80,
bottom: 50,
left: 80
}}
axisLegendPosition="start"
axisLegendOffset={50}
lineOpacity={0.5}
lineWidth={2}
theme={{
axis: {
legend: {
text: {
fontSize: 14,
fontWeight: 600
}
}
}
}}
/>
</div>
)