Большинство компонентов библиотеки Nivo построено поверх SVG. Такой подход обеспечивает:
Однако при работе с большими объёмами данных SVG начинает создавать серьёзную нагрузку на браузер. Основная проблема заключается в том, что каждый элемент графика становится отдельным DOM-узлом.
Например:
В SVG каждый элемент требует:
Canvas работает иначе. Вместо множества DOM-элементов используется единая поверхность рисования. Это резко уменьшает нагрузку на браузер и повышает производительность.
SVG-компоненты в Nivo обычно импортируются так:
import { ResponsiveLine } from '@nivo/line'
Особенности:
Недостатки:
Canvas-версии компонентов импортируются отдельно:
import { ResponsiveLineCanvas } from '@nivo/line'
Особенности:
Недостатки:
Nivo предоставляет Canvas-версии для наиболее тяжёлых визуализаций.
SVG:
ResponsiveLine
Canvas:
ResponsiveLineCanvas
SVG:
ResponsiveScatterPlot
Canvas:
ResponsiveScatterPlotCanvas
SVG:
ResponsiveBar
Canvas:
ResponsiveBarCanvas
SVG:
ResponsiveHeatMap
Canvas:
ResponsiveHeatMapCanvas
SVG:
ResponsiveVoronoi
Canvas:
ResponsiveVoronoiCanvas
Canvas не всегда лучше SVG.
SVG остаётся предпочтительным вариантом при:
Canvas оправдан при:
Допустим, отображается график с 50 000 точек.
SVG-компонент:
import { ResponsiveScatterPlot } from '@nivo/scatterplot'
Каждая точка:
<circle />
50 000 элементов:
В Canvas все точки рисуются напрямую:
ctx.fill()
DOM практически не растёт.
import { ResponsiveLine } from '@nivo/line'
<ResponsiveLine
data={data}
/>
import { ResponsiveLineCanvas } from '@nivo/line'
<ResponsiveLineCanvas
data={data}
/>
Во многих случаях этого достаточно.
API Canvas-компонентов максимально похож на SVG-версии.
Поддерживаются:
Пример:
<ResponsiveLineCanvas
data={data}
margin={{ top: 50, right: 50, bottom: 50, left: 60 }}
xScale={{ type: 'point' }}
yScale={{
type: 'linear',
min: 0,
max: 'auto'
}}
axisBottom={{
tickSize: 5,
tickPadding: 5
}}
colors={{ scheme: 'category10' }}
/>
Canvas-компоненты не полностью повторяют SVG-функциональность.
Часто отсутствуют:
SVG-анимации:
motionConfig="gentle"
animate={true}
На больших графиках вызывают:
Canvas чаще используется без анимаций:
animate={false}
Это особенно важно в real-time системах.
Canvas может выглядеть размыто на Retina-дисплеях.
Для повышения качества используется:
pixelRatio={2}
Пример:
<ResponsiveLineCanvas
data={data}
pixelRatio={window.devicePixelRatio}
/>
В SVG hover работает через DOM.
Canvas не содержит отдельных элементов, поэтому Nivo реализует внутренние алгоритмы поиска объектов.
Для line chart используется:
useMesh={true}
Пример:
<ResponsiveLineCanvas
data={data}
useMesh={true}
/>
Mesh создаёт виртуальную интерактивную карту.
useMesh значительно улучшает UX, но создаёт
вычислительную нагрузку.
На очень больших графиках:
Иногда выгоднее отключить mesh:
useMesh={false}
Для повышения производительности можно ограничивать detection radius.
Пример:
<ResponsiveScatterPlotCanvas
data={data}
detectionRadius={6}
/>
Меньший радиус уменьшает объём вычислений.
Canvas особенно эффективен в streaming-сценариях.
Пример:
const [data, setData] = useState(initialData)
useEffect(() => {
const interval = setInterval(() => {
setData(generateNextFrame())
}, 100)
return () => clearInterval(interval)
}, [])
SVG при постоянных обновлениях:
Canvas обновляется заметно стабильнее.
Даже Canvas не спасает от чрезмерного объёма данных.
Распространённая ошибка:
1000000 points
Правильный подход:
Перед рендерингом можно уменьшать количество точек.
Пример простого sampling:
const reducedData = data.filter((_, index) => index % 10 === 0)
Более продвинутые алгоритмы:
Отображается только актуальный диапазон:
const visibleData = data.slice(-1000)
Такой подход:
Tooltip остаётся React-компонентом:
tooltip={({ point }) => (
<div>
{point.data.x}: {point.data.y}
</div>
)}
Но поиск активной точки выполняется вручную внутри Canvas-логики.
Некоторые Canvas-компоненты позволяют использовать custom layers.
Пример:
layers={[
'grid',
'axes',
'lines',
CustomLayer
]}
Canvas-layer получает context:
const CustomLayer = ({ ctx, innerWidth, innerHeight }) => {
ctx.fillStyle = 'red'
ctx.fillRect(0, 0, innerWidth, innerHeight)
}
Canvas API основан на imperative rendering.
Основные методы:
ctx.beginPath()
ctx.moveTo()
ctx.lineTo()
ctx.stroke()
Для окружностей:
ctx.arc(x, y, radius, 0, Math.PI * 2)
Для текста:
ctx.fillText('Label', x, y)
Canvas полностью перерисовывается при обновлении.
Поэтому критично:
Пример:
const Chart = React.memo(({ data }) => {
return (
<ResponsiveLineCanvas data={data} />
)
})
const preparedData = useMemo(() => {
return processData(rawData)
}, [rawData])
Без memoization preprocessing может стать bottleneck.
const handleClick = useCallback(point => {
console.log(point)
}, [])
В тяжёлых приложениях иногда используют:
Nivo напрямую это не предоставляет, но архитектура Canvas позволяет внедрять подобные оптимизации.
Память расходуется на:
Память расходуется в основном на:
При огромных datasets Canvas обычно потребляет значительно меньше памяти.
Основные инструменты:
Важно анализировать:
<circle />
<circle />
<circle />
animate={true}
setState(...)
onMouseMove
Canvas высокого разрешения:
pixelRatio={4}
может резко увеличить потребление памяти.
Чрезмерное количество:
ctx.save()
ctx.restore()
может замедлять рендер.
Canvas не обновляет отдельные элементы — перерисовывается всё.
Подходит для:
Подходит для:
Во многих приложениях используют комбинацию:
Например:
SVG предоставляет:
Canvas этого не имеет по умолчанию.
При необходимости accessibility добавляют отдельно:
aria-label="Chart"
role="img"
Но полноценная семантика всё равно ограничена.
На Retina-дисплеях важно учитывать:
pixelRatio={window.devicePixelRatio}
Однако слишком высокие значения:
Обычно достаточно:
pixelRatio={2}
SVG легче использовать с server-side rendering.
Canvas зависит от браузерного API:
window
document
canvas
В SSR-фреймворках иногда требуется dynamic import:
const Chart = dynamic(
() => import('./Chart'),
{ ssr: false }
)
Canvas удобно экспортируется:
canvas.toDataURL()
или:
canvas.toBlob()
Это полезно для:
Типичная стратегия миграции:
import { ResponsiveScatterPlot } from '@nivo/scatterplot'
<ResponsiveScatterPlot
data={data}
animate={true}
useMesh={true}
/>
import { ResponsiveScatterPlotCanvas } from '@nivo/scatterplot'
<ResponsiveScatterPlotCanvas
data={data}
animate={false}
useMesh={false}
pixelRatio={2}
/>
После перехода обычно наблюдаются:
Особенно заметен эффект на графиках с десятками тысяч объектов.
Canvas-компоненты Nivo:
Это позволяет сохранять удобство React-разработки при значительно более высокой производительности визуализации.