154 lines · 2 files · 3.9 kB
cases/96-nested-donut/example.tsx83 lines · entry
cases/96-nested-donut/example.tsx
import { Chart } from '@tanstack/charts/react/tooltip'
import { tooltip as exampleTooltip } from '@tanstack/charts/tooltip'
import { flare } from '@tanstack/charts-data/flare'
import { defineChart } from '@tanstack/charts'
import { pie, polar, radialArc } from '@tanstack/charts/polar'
import { nestedFlareDonut } from './transform'
const names = [
'flare.animate',
'flare.data',
'flare.animate.core',
'flare.animate.interpolate',
'flare.data.core',
'flare.data.converters',
]
const colors = [
'#38bdf8',
'#8b5cf6',
'#0284c7',
'#0ea5e9',
'#7c3aed',
'#a855f7',
]
export const createExampleChart = (input: ChartOptions) => {
const sourceRows =
input.revision % 2 === 0
? flare
: flare.filter((row) => row.size === null || row.size >= 1_000)
const data = nestedFlareDonut(sourceRows)
const innerArcs = pie(data.inner, { value: 'size' })
const outerArcs = pie(data.outer, { value: 'size' })
return defineChart(
{
marks: [
polar({
radiusRatio: 0.8,
marks: [
radialArc(innerArcs, {
id: 'family-slices',
key: 'name',
innerRadius: ({ radius }) => radius * 0.12,
outerRadius: ({ radius }) => radius * 0.46,
color: 'name',
}),
radialArc(outerArcs, {
id: 'detail-slices',
key: 'name',
innerRadius: ({ radius }) => radius * 0.56,
color: 'name',
}),
],
scales: {
angle: null,
radius: null,
},
}),
],
scales: {
x: null,
y: null,
},
color: { domain: names, range: colors },
margin: 0,
},
{ keyboard: true, tooltip: exampleTooltip },
)
}
export interface ChartOptions {
revision: number
}
export const exampleAriaLabel = 'Nested Flare package sizes'
export const chart = createExampleChart({
revision: 0,
})
export default function Example() {
return <Chart ariaLabel={exampleAriaLabel} definition={chart} height={480} />
}cases/96-nested-donut/transform.ts71 lines · dependency
cases/96-nested-donut/transform.ts
import type { FlareRow } from '@tanstack/charts-data/flare'
export interface FlareDonutSlice {
name: string
size: number
}
export interface FlareDonutDetail extends FlareDonutSlice {
family: string
}
export interface FlareDonutData {
inner: readonly FlareDonutSlice[]
outer: readonly FlareDonutDetail[]
}
interface DetailGroup {
name: string
family: string
includes: (name: string) => boolean
}
const detailGroups: readonly DetailGroup[] = [
{
name: 'flare.animate.core',
family: 'flare.animate',
includes: (name) =>
name.startsWith('flare.animate.') &&
!name.startsWith('flare.animate.interpolate.'),
},
{
name: 'flare.animate.interpolate',
family: 'flare.animate',
includes: (name) => name.startsWith('flare.animate.interpolate.'),
},
{
name: 'flare.data.core',
family: 'flare.data',
includes: (name) =>
name.startsWith('flare.data.') &&
!name.startsWith('flare.data.converters.'),
},
{
name: 'flare.data.converters',
family: 'flare.data',
includes: (name) => name.startsWith('flare.data.converters.'),
},
]
export function nestedFlareDonut(rows: readonly FlareRow[]): FlareDonutData {
const leaves = rows.filter(
(row): row is FlareRow & { readonly size: number } => row.size !== null,
)
const outer = detailGroups.map((group) => ({
name: group.name,
family: group.family,
size: leaves.reduce(
(total, row) => total + (group.includes(row.name) ? row.size : 0),
0,
),
}))
const inner = ['flare.animate', 'flare.data'].map((family) => ({
name: family,
size: outer.reduce(
(total, detail) => total + (detail.family === family ? detail.size : 0),
0,
),
}))
return { inner, outer }
}