hazo_dataviz
v0.4.2
Published
Headless data visualization primitives for hazo apps
Downloads
459
Readme
hazo_dataviz
Headless data-visualization primitives for hazo apps. A descriptor-driven data layer (schema, in-memory runner, React hook, formatters) plus a set of built-in recharts visualizers that auto-select the right chart for a given query shape — all styled with a warm, self-contained "Studio" theme out of the box.
Status
v0.4.0 — Data layer + built-in visualizers + auto-selection complete, plus interactive time-series widgets (TimeSeriesChart / ChartControls) with client-side grain roll-up. The server-SQL runner is deferred (blocked on hazo_connect GROUP BY support — see FR-003).
Install
npm install hazo_dataviz hazo_core rechartsPeer deps: hazo_core (required); recharts (required for the chart components — ^2.15.4 || ^3.0.0, supply one copy yourself); react / react-dom; hazo_connect / hazo_ui / hazo_api (optional).
Quick start — data layer
import {
parseDescriptor,
createInMemoryRunner,
useDataQuery,
} from 'hazo_dataviz';
const descriptor = parseDescriptor({
name: 'sales',
version: '1',
grain: ['date', 'region'],
fields: {
date: { role: 'dimension', type: 'time', timeGrains: ['month', 'year'] },
region: { role: 'dimension', type: 'category' },
revenue: { role: 'measure', agg: 'sum', format: { kind: 'currency', ccy: 'USD' } },
},
});
const rows = [/* your data */];
const runner = createInMemoryRunner(descriptor, rows);
// In a React component:
const { result, status, error } = useDataQuery(descriptor, {
measures: [{ field: 'revenue' }],
dimensions: ['date'],
timeGrain: 'month',
}, runner);Quick start — auto-selected visualizer
<VisualizerView> scores every built-in visualizer against the descriptor + query
and renders the best fit (argmax). It is client-only — import from hazo_dataviz/visualizers.
'use client';
import { VisualizerView } from 'hazo_dataviz/visualizers';
// `result` comes from a runner (e.g. createInMemoryRunner above)
<VisualizerView descriptor={descriptor} query={query} result={result} opts={{ height: 300 }} />;Need manual control instead of auto-selection?
import { builtinVisualizers, selectVisualizer, deriveEncoding } from 'hazo_dataviz/visualizers';
const { visualizer } = selectVisualizer(descriptor, query, result);
const encoding = deriveEncoding(query, result);
const el = visualizer.render(result, encoding, { height: 300, colors: ['#f5563d', '#14a098'] });Built-in visualizers & how they score
accepts(descriptor, query) → number (0 = can't render, higher = better fit). The winner is the highest score:
| id | When it fits | Score |
|---|---|---|
| single_stat | 0 dimensions + exactly 1 measure | 0.9 |
| line | ≥1 temporal dimension + ≥1 measure | 0.9 |
| bar | exactly 1 categorical dimension + ≥1 measure | 0.9 |
| stacked_bar | 2 dimensions + ≥1 measure + ≥1 categorical dim | 0.95 (categorical x) / 0.6 (temporal x) |
| area | ≥1 temporal dimension + ≥1 measure | 0.5 (never beats line) |
| table | anything | 0.1 (universal fallback) |
stacked_bar is graduated so a multi-series time series (temporal x) still defaults to line while a category-by-category split (categorical x) picks the stacked bar.
Styling (the "Studio" theme)
Charts ship pre-styled — warm hairline grid, custom tooltip, rounded bars, gradient
area fills, tabular figures — with no Tailwind or external CSS required (everything is
inline SVG/JS so it renders correctly in any consumer). The series palette and theme
tokens live in visualizers/palette.ts and visualizers/chart_frame.tsx; override
colors per-render via opts.colors. SVG font-family is intentionally left unset so
charts inherit your app's font.
Bespoke chart primitives
For hand-built charts (not query-driven), hazo_dataviz/client exports lightweight
recharts wrappers: LineChart (single series) and MultiLineChart (multi series),
both with null-gap handling and responsive sizing.
Interactive time-series widgets
TimeSeriesChart is a higher-level widget for daily time-series data with built-in
grain roll-up. It accepts single- (data) or multi-series (series) input, renders as
a line or bar chart, and rolls daily points up to weekly / monthly / quarterly / yearly
via aggregateSeries as the grain changes. It ships a segmented ChartControls toolbar
(uncontrolled by default; pass all of grain / chartType / onGrainChange /
onChartTypeChange to control it externally and share one toolbar across charts).
import { TimeSeriesChart } from 'hazo_dataviz/client';
<TimeSeriesChart
dates={dates} // ['2024-01-01', ...]
series={[
{ label: 'Revenue', color: '#f5563d', data: revenue, agg: 'sum' },
{ label: 'Sessions', color: '#14a098', data: sessions, agg: 'avg' },
]}
unit="k"
referenceDate="2024-03-01" // dashed "today" marker (day grain only)
/>ChartControls (grain + chart-type segmented control) and the pure helpers
aggregateSeries / bucketDate are also exported standalone for consumer reuse.
Exports
| Entry point | Exports |
|---|---|
| hazo_dataviz (server) / hazo_dataviz/client | parseDescriptor, parseQuery, derivePickerOptions, createInMemoryRunner, useDataQuery, formatValue / formatPercent / formatCompact, LineChart, MultiLineChart, TimeSeriesChart, ChartControls, aggregateSeries, bucketDate, CONTRACT_VERSION, + types |
| hazo_dataviz/visualizers | VisualizerView, builtinVisualizers, selectVisualizer, deriveEncoding, classifyShape, + Visualizer / VisualizerOpts / QueryShape types |
| hazo_dataviz/testing | Fixtures (prices, sector_breakdown, sparse) + createInMemoryRunner re-export |
| hazo_dataviz/api | HTTP route handler factories (hazo_api-based) |
hazo_dataviz/visualizers and hazo_dataviz/client are client-only ('use client'); the
root hazo_dataviz import is server-safe and re-exports the client-safe data layer.
License
MIT
