Компонент Bubble из библиотеки Nivo предназначен для
визуализации иерархических данных в виде набора вложенных или
сгруппированных окружностей. Такой тип диаграмм также называют
Packed Circles или Circle Packing. Каждый круг
представляет узел дерева, а его размер зависит от числового
значения.
Bubble-диаграммы особенно полезны для:
Для работы компонента требуется основной пакет Nivo и пакет Bubble:
npm install @nivo/circle-packing
или:
yarn add @nivo/circle-packing
Компонент использует древовидную структуру. Корневой элемент содержит
дочерние узлы через свойство children.
Пример данных:
const data = {
name: 'root',
children: [
{
name: 'Frontend',
children: [
{
name: 'React',
value: 40
},
{
name: 'Vue',
value: 25
},
{
name: 'Angular',
value: 35
}
]
},
{
name: 'Backend',
children: [
{
name: 'Node.js',
value: 50
},
{
name: 'Python',
value: 45
}
]
}
]
}
Наиболее часто используется компонент ResponsiveBubble,
автоматически подстраивающийся под размеры контейнера.
import { ResponsiveBubble } from '@nivo/circle-packing'
const MyBubbleChart = () => (
<div style={{ height: 600 }}>
<ResponsiveBubble
data={data}
margin={{ top: 20, right: 20, bottom: 20, left: 20 }}
identity="name"
value="value"
colors={{ scheme: 'nivo' }}
/>
</div>
)
Источник данных.
data={data}
Определяет поле, используемое как идентификатор узла.
identity="name"
Поле числового значения.
value="value"
Именно это значение определяет размер окружности.
Отступы внутри контейнера.
margin={{
top: 40,
right: 40,
bottom: 40,
left: 40
}}
Расстояние между кругами.
padding={4}
Большие значения создают более свободную упаковку.
Nivo поддерживает встроенные палитры.
colors={{ scheme: 'category10' }}
Популярные схемы:
nivopairedset1accentdark2category10Вместо схемы можно использовать функцию.
colors={(node) => {
if (node.depth === 1) return '#3b82f6'
if (node.depth === 2) return '#10b981'
return '#ef4444'
}}
Каждый узел имеет свойство depth.
0 — корневой элемент;1 — первый уровень;2 — второй уровень;Пример использования:
borderWidth={(node) => node.depth === 1 ? 4 : 1}
Толщина обводки.
borderWidth={2}
Цвет границы.
borderColor={{
from: 'color',
modifiers: [
['darker', 0.6]
]
}}
Модификатор darker автоматически затемняет основной
цвет.
Поле для отображения текста.
label="name"
Цвет текста.
labelTextColor={{
from: 'color',
modifiers: [
['darker', 3]
]
}}
Минимальный радиус, при котором отображается подпись.
labelSkipRadius={18}
Маленькие круги не будут содержать текст.
Bubble-компонент поддерживает анимации через библиотеку
react-spring.
Включение анимации.
animate={true}
Настройка поведения анимации.
motionConfig="gentle"
Доступные варианты:
defaultgentlewobblystiffslowmolassesОбработка клика по узлу.
onCl ick={(node) => {
console.log(node)
}}
Событие наведения.
onMouseEn ter={(node) => {
console.log('hover', node)
}}
Событие ухода курсора.
onMouseLe ave={() => {
console.log('leave')
}}
По умолчанию компонент показывает tooltip.
Можно создать собственный:
tooltip={({ node }) => (
<div
style={{
padding: 12,
background: '#222',
color: '#fff',
borderRadius: 4
}}
>
<strong>{node.data.name}</strong>
<div>Value: {node.value}</div>
</div>
)}
Часто корневой узел используется только как контейнер данных.
root={{
padding: 0
}}
Показывать только конечные элементы:
leavesOnly={true}
В этом режиме внутренние категории не отображаются как отдельные круги.
Цвет потомков относительно родителя.
childColor={{
from: 'color',
modifiers: [
['brighter', 0.4]
]
}}
Наследование цветов.
inheritColorFromParent={true}
Компоненты Nivo поддерживают единый объект theme.
theme={{
text: {
fontSize: 14,
fill: '#333'
},
tooltip: {
container: {
background: '#111',
color: '#fff'
}
}
}}
Nivo предоставляет две версии Bubble-компонента:
ResponsiveBubble
Преимущества:
Недостатки:
ResponsiveBubbleCanvas
Преимущества:
Недостатки:
Пример использования Canvas-версии:
import { ResponsiveBubbleCanvas } from '@nivo/circle-packing'
const MyChart = () => (
<div style={{ height: 700 }}>
<ResponsiveBubbleCanvas
data={data}
identity="name"
value="value"
colors={{ scheme: 'set2' }}
padding={3}
/>
</div>
)
Позволяет полностью переопределить рендеринг круга.
renderNode={({ node, style }) => (
<g transform={`translate(${style.x}, ${style.y})`}>
<circle
r={style.radius}
fill={style.color}
stroke="#000"
/>
<text
textAnchor="middle"
dominantBaseline="central"
fill="#fff"
>
{node.data.name}
</text>
</g>
)}
Bubble часто применяют для отображения аватаров или логотипов.
renderNode={({ node, style }) => (
<g transform={`translate(${style.x}, ${style.y})`}>
<clipPath id={`clip-${node.id}`}>
<circle r={style.radius} />
</clipPath>
<image
href={node.data.image}
width={style.radius * 2}
height={style.radius * 2}
x={-style.radius}
y={-style.radius}
clipPath={`url(#clip-${node.id})`}
/>
</g>
)}
Bubble хорошо подходит для API-данных.
const [data, setData] = useState(null)
useEffect(() => {
fetch('/api/stats')
.then(res => res.json())
.then(setData)
}, [])
Рендеринг:
if (!data) return <div>Loading...</div>
return (
<ResponsiveBubble
data={data}
identity="name"
value="value"
/>
)
const chartData = useMemo(() => transformData(data), [data])
const handleClick = useCallback((node) => {
console.log(node)
}, [])
При большом количестве узлов рекомендуется:
ResponsiveBubbleCanvas
При сложной иерархии полезно:
Пример:
animate={false}
labelSkipRadius={25}
valueFormat=">-.2f"
Пример результата:
25.46
Bubble-компонент не имеет встроенных легенд, но можно создавать собственные.
const legend = [
{
color: '#3b82f6',
label: 'Frontend'
},
{
color: '#10b981',
label: 'Backend'
}
]
const data = {
name: 'company',
children: [
{
name: 'Engineering',
children: [
{ name: 'Frontend', value: 35 },
{ name: 'Backend', value: 50 },
{ name: 'DevOps', value: 20 }
]
},
{
name: 'Marketing',
children: [
{ name: 'SEO', value: 15 },
{ name: 'Content', value: 25 }
]
},
{
name: 'Sales',
children: [
{ name: 'B2B', value: 40 },
{ name: 'B2C', value: 30 }
]
}
]
}
Компонент:
<ResponsiveBubble
data={data}
identity="name"
value="value"
padding={6}
colors={{ scheme: 'spectral' }}
borderWidth={2}
borderColor={{
from: 'color',
modifiers: [['darker', 0.5]]
}}
labelSkipRadius={16}
labelTextColor={{
from: 'color',
modifiers: [['darker', 3]]
}}
animate={true}
motionConfig="gentle"
/>
Причины:
value;0;Правильно:
<div style={{ height: 600 }}>
<ResponsiveBubble ... />
</div>
Bubble требует объект дерева, а не массив.
Неправильно:
const data = []
Правильно:
const data = {
name: 'root',
children: []
}
Решения:
labelSkipRadius={20}
или:
enableLabels={false}
Bubble использует алгоритм упаковки окружностей:
Внутри Nivo используется функциональность D3 Hierarchy и D3 Pack Layout.
| Bubble | Treemap |
|---|---|
| Использует круги | Использует прямоугольники |
| Лучше визуально | Лучше использует пространство |
| Подходит для презентаций | Подходит для аналитики |
| Менее точен визуально | Более точен по площади |
| Высокая декоративность | Высокая информативность |
Bubble подходит для:
Bubble менее эффективен:
Типичный компонент:
import { ResponsiveBubble } from '@nivo/circle-packing'
export default function Chart({ data }) {
return (
<div style={{ height: 700 }}>
<ResponsiveBubble
data={data}
identity="name"
value="value"
padding={4}
colors={{ scheme: 'paired' }}
/>
</div>
)
}
Типизация данных:
type BubbleNode = {
name: string
value?: number
children?: BubbleNode[]
}
Пример:
const data: BubbleNode = {
name: 'root',
children: [
{
name: 'React',
value: 50
}
]
}
SVG-версию можно экспортировать:
html-to-image;dom-to-image;canvas;Пример:
import { toPng } from 'html-to-image'
toPng(document.getElementById('chart'))
.then((dataUrl) => {
console.log(dataUrl)
})
Nivo поддерживает SSR.
Особенности:
animate={false}
Bubble часто используют вместе с:
Подход позволяет строить сложные аналитические панели с единой системой визуализации.