@zuilib/charts
v0.5.0
Published
ZUI — enterprise analytics charts on Recharts: a serializable chart spec, styled primitives, and cross-filtering that links charts to data tables
Maintainers
Readme
@zuilib/charts
Enterprise analytics charts on Recharts, styled only through ZUI tokens: a
serializable chart spec for runtime chart builders, styled primitives for
hand-written composition, and cross-filtering that links charts to each
other and to @zuilib/data-grid — without a hard dependency on it.
Install
pnpm add @zuilib/chartsReact and React DOM are the required peers. Recharts, the ZUI primitives and
tokens install transitively. Install recharts yourself only when your own
code imports it for hand-written composition.
For a Tailwind v4 host, load the component and chart source entries once:
@import "tailwindcss";
@import "@zuilib/primitives/tailwind.css";
@import "@zuilib/charts/tailwind.css";Usage
import Chart from '@zuilib/charts'
// Plain JSON: store it, send it from a server, let users build it.
const spec = {
version: 1,
type: 'bar',
title: 'Revenue by region',
xKey: 'region',
series: [{field: 'revenue', label: 'Revenue', format: 'currency:USD'}],
yAxis: {format: 'compact'},
}
<Chart spec={spec} rows={rows} />Component API
Chart — @zuilib/charts
| Prop | Type | Default |
|------|------|---------|
| spec | ChartSpec | required — the serializable chart description |
| rows | ChartRow[] | required — the rows behind the chart |
| registry | ChartRegistry | built-ins — createChartRegistry(defaultChartTypes, yourDefs) adds or overrides types without forking the component |
| formats | Record<string, FormatterFn \| FormatterFactory> | — custom formatter tokens the spec may name (functions stay out of the spec) |
| locale | string | user's locale — BCP 47 tag for formatted values |
| width / height | number | responsive — fixed pixel size for tests and SSR |
| aspect | number | 16/9 — responsive width/height ratio when height is left out; the chart never gets shorter than 200px |
| loading | boolean | false — renders a skeleton instead of the chart |
| error | ReactNode | — renders an error state instead of the chart |
| emptyState | ReactNode | the noData label — renders when rows is empty |
| hiddenSeries / defaultHiddenSeries / onHiddenSeriesChange | string[], string[], (fields: string[]) => void | — series fields hidden by the legend; controlled when hiddenSeries is passed, otherwise kept internally from defaultHiddenSeries |
| onMarkClick | (event: ChartMarkEvent) => void | — every mark click, whether or not a CrossFilterProvider is present |
| labels | Partial<ChartLabels> | English — every built-in string |
| animate | boolean | true unless the user prefers reduced motion |
| ariaLabel | string | the spec's title — accessible name of the figure |
| className | string | merged onto the container |
Sizing
A chart fills its container. Leave width and height out and the frame
measures itself: the height follows aspect (16/9) with a 200px floor so a
phone-width chart keeps a readable plot; pass height to fix the height and
keep the width fluid; pass both for a fixed size (tests, SSR). The frame is
a CSS size container (@container, min-w-0), so it never holds a flex or
grid parent open at the width of a previous render — put it in any column
without a min-w-0 of your own. Under 384px the value axis sizes to its
labels and the category axis keeps only its end ticks; the legend wraps and,
on touch pointers, its buttons grow to 44px hit areas. Desktop rendering
does not change.
Entry points
@zuilib/charts (or ./chart) is the config-driven renderer: ten chart
types (line, area, bar, pie/donut, scatter, composed, radar, radial-bar,
funnel, treemap) from one discriminated ChartSpec, with loading, error
and empty states, a controlled hiddenSeries legend, and onMarkClick.
Formats are named tokens ('currency:USD', 'compact', 'percent',
'date:short') resolved through ./format; custom formatter functions
arrive via the formats prop so the spec stays JSON.
./registry is the open chart-type registry <Chart> dispatches through.
Every type is a ChartTypeDef — renderer, cross-filter fields, and a
complete JSON Schema of its spec variant — and
createChartRegistry(defaultChartTypes, yourDefs) merges defs last-wins
by type, so an app adds or overrides a chart type without forking the
package:
import { createChartRegistry, defaultChartTypes, defineChartType, chartTypeSchema } from '@zuilib/charts/registry'
const registry = createChartRegistry(defaultChartTypes, [
defineChartType<GaugeChartSpec>({
type: 'gauge',
family: 'named',
render: GaugeRenderer, // receives RendererProps<GaugeChartSpec> from @zuilib/charts/authoring
crossFilterField: (spec) => spec.nameKey,
schema: chartTypeSchema('gauge', {
nameKey: { type: 'string' },
valueKey: { type: 'string' },
}, ['nameKey', 'valueKey']),
}),
])
<Chart spec={gaugeSpec} rows={rows} registry={registry} />Custom specs type as CustomChartSpec (type plus the shared options and
whatever the def's schema declares); built-in specs stay the closed
ChartSpec union, so the escape hatch never loosens their checking.
chartSpecJsonSchema(registry?) emits the whole ChartSpec JSON Schema
(draft 2020-12, per-type if/then conditionals from the defs' complete
fragments) for validation and structured generation — one source, so the
schema can't drift from what renders.
./authoring is the custom-chart authoring surface: RendererProps (what
a registered renderer receives) and the mark-interaction helpers
(reportMarkClick, activeCategoryValues, markDimOpacity,
MarkInteraction) that keep an app-registered chart type's clicks and
dimming consistent with the built-ins.
./chart-frame, ./chart-tooltip, ./chart-legend and
./chart-defaults are the primitives for hand-written Recharts
composition: ChartFrame owns the series-color CSS variables
(fill={seriesColorVar('revenue')}) and the states; ChartTooltipPanel
and ChartLegendList render on popover tokens via Recharts'
Tooltip/Legend content prop, the legend as real buttons (click
isolates, shift-click toggles, aria-pressed).
./cross-filter is the shared filter/highlight state
(CrossFilterProvider, useCrossFilter) with the same
controlled/uncontrolled whole-state contract as the data grid; ./interop
is four pure converters (toColumnFilters, fromColumnFilters,
toHighlight, applyFilters) that wire it to a data grid's
columnFilters and row selection structurally, with zero imports.
Theming
Series colors are the --chart-1 … --chart-8 tokens — a categorical
palette validated for color-vision-deficiency separation in both modes.
Slots are assigned in fixed declaration order and never cycled. A brand may
override --chart-1 with its primary hue; overriding more slots requires
re-validating the order. Grid lines, ticks, tooltips and states read the
same semantic tokens as every other ZUI package.
Localisation
Every built-in string is overridable through the labels prop
(ChartLabels), and every formatted value honors the locale prop.
Mark entrance animation follows prefers-reduced-motion (or the animate
prop).
