Cross-filtering
Analytics screens live or die on linked views: click a bar, the table
filters; select table rows, the charts respond. @zuilib/charts ships that
as one small, JSON-safe state object and a provider that follows the exact
controlled/uncontrolled contract of @zuilib/data-grid.
interface CrossFilterState {
filters: {field: string; values: (string | number)[]}[]
highlight: {field: string; values: (string | number)[]} | null
}
filters is durable (clicks accumulate it); highlight is transient
emphasis — marks whose field value is outside it dim to 35% opacity.
The provider
import {CrossFilterProvider, useCrossFilter} from '@zuilib/charts'
<CrossFilterProvider defaultState={...} onStateChange={persist}>
<Chart spec={regionSpec} rows={byRegion} />
<Chart spec={statusSpec} rows={byStatus} />
</CrossFilterProvider>
Uncontrolled, the provider owns the state (and still reports every change).
Controlled (state + onStateChange), it emits the whole next state and
renders exactly what you pass back — ignoring an emission rejects it, the
same contract as the grid. Inside the provider a mark click on a chart
whose interaction is 'filter' calls
toggleValue(field, value, {additive: shiftKey}): a plain click replaces
the field's values (clicking the lone kept value clears it), shift-click
accumulates. Charts without a provider still report through onMarkClick.
useCrossFilter() exposes the state plus toggleValue, clearField,
clearAll, setHighlight, and valuesFor for building filter chips or a
"clear filters" button.
Linking a data grid
The interop module is four pure functions — structural converters with no
import of @zuilib/data-grid or TanStack Table, so the charts stay
standalone and the wiring is ~10 lines:
toColumnFilters(state)— the cross-filter entries as select-stylecolumnFilters({id, value: values[]}), ready to merge intoDataGridState.fromColumnFilters(columnFilters, fields)— the inverse; never throws on untrusted input.toHighlight(rows, field)— grid-selected rows folded into a highlight.applyFilters(rows, state, {except})— client-side filtering for feeding a second chart;exceptskips the chart's own field so its clicked mark stays visible (the standard cross-filter rule).
Click a customer bar or a status slice (shift-click to accumulate) and the grid filters and pages back to the start; tick grid rows and the other customers' bars dim. Clicking a lone selected mark clears its filter.