Skip to main content

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-style columnFilters ({id, value: values[]}), ready to merge into DataGridState.
  • 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; except skips the chart's own field so its clicked mark stays visible (the standard cross-filter rule).
Loading example

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.