hazo_dataviz 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGE_LOG.md +54 -0
- package/README.md +115 -0
- package/SETUP_CHECKLIST.md +54 -0
- package/dist/api/index.d.ts +4 -0
- package/dist/api/index.d.ts.map +1 -0
- package/dist/api/index.js +11 -0
- package/dist/charts/line_chart.d.ts +14 -0
- package/dist/charts/line_chart.d.ts.map +1 -0
- package/dist/charts/line_chart.js +26 -0
- package/dist/charts/multi_line_chart.d.ts +13 -0
- package/dist/charts/multi_line_chart.d.ts.map +1 -0
- package/dist/charts/multi_line_chart.js +32 -0
- package/dist/hooks/use_data_query.d.ts +11 -0
- package/dist/hooks/use_data_query.d.ts.map +1 -0
- package/dist/hooks/use_data_query.js +35 -0
- package/dist/index.client.d.ts +17 -0
- package/dist/index.client.d.ts.map +1 -0
- package/dist/index.client.js +11 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/lib/auto_encode.d.ts +24 -0
- package/dist/lib/auto_encode.d.ts.map +1 -0
- package/dist/lib/auto_encode.js +46 -0
- package/dist/lib/contract-version.d.ts +2 -0
- package/dist/lib/contract-version.d.ts.map +1 -0
- package/dist/lib/contract-version.js +1 -0
- package/dist/lib/format.d.ts +5 -0
- package/dist/lib/format.d.ts.map +1 -0
- package/dist/lib/format.js +31 -0
- package/dist/lib/index.d.ts +3 -0
- package/dist/lib/index.d.ts.map +1 -0
- package/dist/lib/index.js +2 -0
- package/dist/lib/picker.d.ts +4 -0
- package/dist/lib/picker.d.ts.map +1 -0
- package/dist/lib/picker.js +20 -0
- package/dist/lib/runners/in_memory.d.ts +4 -0
- package/dist/lib/runners/in_memory.d.ts.map +1 -0
- package/dist/lib/runners/in_memory.js +303 -0
- package/dist/lib/shape.d.ts +11 -0
- package/dist/lib/shape.d.ts.map +1 -0
- package/dist/lib/shape.js +27 -0
- package/dist/schema/descriptor.d.ts +162 -0
- package/dist/schema/descriptor.d.ts.map +1 -0
- package/dist/schema/descriptor.js +56 -0
- package/dist/schema/query.d.ts +96 -0
- package/dist/schema/query.d.ts.map +1 -0
- package/dist/schema/query.js +37 -0
- package/dist/testing/fixtures.d.ts +163 -0
- package/dist/testing/fixtures.d.ts.map +1 -0
- package/dist/testing/fixtures.js +59 -0
- package/dist/testing/index.d.ts +3 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +2 -0
- package/dist/types/index.d.ts +62 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +1 -0
- package/dist/visualizers/area.d.ts +3 -0
- package/dist/visualizers/area.d.ts.map +1 -0
- package/dist/visualizers/area.js +66 -0
- package/dist/visualizers/bar.d.ts +3 -0
- package/dist/visualizers/bar.d.ts.map +1 -0
- package/dist/visualizers/bar.js +41 -0
- package/dist/visualizers/builtins.d.ts +3 -0
- package/dist/visualizers/builtins.d.ts.map +1 -0
- package/dist/visualizers/builtins.js +7 -0
- package/dist/visualizers/chart_frame.d.ts +97 -0
- package/dist/visualizers/chart_frame.d.ts.map +1 -0
- package/dist/visualizers/chart_frame.js +146 -0
- package/dist/visualizers/index.d.ts +7 -0
- package/dist/visualizers/index.d.ts.map +1 -0
- package/dist/visualizers/index.js +4 -0
- package/dist/visualizers/line.d.ts +3 -0
- package/dist/visualizers/line.d.ts.map +1 -0
- package/dist/visualizers/line.js +66 -0
- package/dist/visualizers/palette.d.ts +13 -0
- package/dist/visualizers/palette.d.ts.map +1 -0
- package/dist/visualizers/palette.js +24 -0
- package/dist/visualizers/single_stat.d.ts +3 -0
- package/dist/visualizers/single_stat.d.ts.map +1 -0
- package/dist/visualizers/single_stat.js +60 -0
- package/dist/visualizers/stacked_bar.d.ts +3 -0
- package/dist/visualizers/stacked_bar.d.ts.map +1 -0
- package/dist/visualizers/stacked_bar.js +75 -0
- package/dist/visualizers/table.d.ts +3 -0
- package/dist/visualizers/table.d.ts.map +1 -0
- package/dist/visualizers/table.js +55 -0
- package/dist/visualizers/visualizer_view.d.ts +12 -0
- package/dist/visualizers/visualizer_view.d.ts.map +1 -0
- package/dist/visualizers/visualizer_view.js +6 -0
- package/package.json +88 -0
package/CHANGE_LOG.md
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# hazo_dataviz — Change Log
|
|
2
|
+
|
|
3
|
+
## 0.3.0 — 2026-06-13 (unreleased)
|
|
4
|
+
|
|
5
|
+
"Studio" visual restyle — a warm, editorial light theme applied to every shipped
|
|
6
|
+
visualizer and to the test-app showroom.
|
|
7
|
+
|
|
8
|
+
- **Shared chart theme** (`visualizers/chart_frame.tsx`): new `CHART_THEME` tokens
|
|
9
|
+
(warm ink/muted/hairline/surface), `axisProps` + `gridProps` (hairline horizontal
|
|
10
|
+
grid, no tick marks), `legendProps`, `CHART_MARGIN`, and a `makeTooltip(labelFmt,
|
|
11
|
+
valueFmt)` factory that renders a warm floating card with a colored swatch + tabular
|
|
12
|
+
value per series. SVG `font-family` is intentionally left unset so charts inherit the
|
|
13
|
+
host page font. `EmptyState` restyled with an icon + "No data to display".
|
|
14
|
+
- **Palette**: `DEFAULT_PALETTE` replaced with the Studio ramp (coral, teal, marigold,
|
|
15
|
+
plum, ocean, olive, rose, amber-brown) — warm/cool alternation so adjacent series stay
|
|
16
|
+
distinguishable; coral + teal lead as the default complementary pair.
|
|
17
|
+
- **Visualizers restyled** to consume the shared theme: `line` (2.5px monotone stroke,
|
|
18
|
+
hover active-dot, custom tooltip), `area` (per-series `<linearGradient>` fills),
|
|
19
|
+
`bar` (rounded `radius`, `maxBarSize`, category gap), `stacked_bar` (rounded top
|
|
20
|
+
segment), `single_stat` (uppercase label, coral accent bar, 52px tabular figure),
|
|
21
|
+
`table` (warm sticky header, hairline rows, right-aligned tabular measures).
|
|
22
|
+
- **stacked_bar.accepts** relaxed: now scores `0.95` for any 2-dimension + ≥1-measure
|
|
23
|
+
query with ≥1 categorical dimension (either dim ordering), so the canonical
|
|
24
|
+
"measure by category over time" shape (temporal x, categorical series) is accepted.
|
|
25
|
+
Previously required `dimensions[0]` itself to be categorical. Jest + autotest aligned.
|
|
26
|
+
- **Test-app "Studio" showroom**: `next/font` wiring (Bricolage Grotesque display +
|
|
27
|
+
Hanken Grotesk body), warm-canvas `globals.css` with CSS-var design tokens and a
|
|
28
|
+
layered radial-glow background, redesigned sidebar (gradient brand mark, active-state
|
|
29
|
+
highlight via `usePathname`), an editorial gallery (eyebrow + display headline,
|
|
30
|
+
per-chart icon cards with hover lift), and a real overview landing page.
|
|
31
|
+
|
|
32
|
+
## 0.2.0 — 2026-06-13 (unreleased)
|
|
33
|
+
|
|
34
|
+
Build 02: Bespoke chart components + recharts visualizers.
|
|
35
|
+
|
|
36
|
+
- `LineChart` — bespoke single-series line chart (recharts) exported from `./client`; accepts `data: ChartDataSeries`, `dates: string[]`, `color`, optional `width`/`height`/`unit`/`showTooltip`/`className`; null values render as gaps (`connectNulls={false}`); responsive by default (130 px height), fixed-size when `width` is given; empty data renders a placeholder without throwing
|
|
37
|
+
- `MultiLineChart` — bespoke multi-series line chart (recharts) exported from `./client`; accepts `series: MultiSeries[]` (each with `label`/`color`/`data`), `dates`; optional `width`/`height`/`showTooltip`/`showLegend`/`className`; same null-gap + responsive behaviour as `LineChart`; empty/all-null series render a placeholder without throwing
|
|
38
|
+
- New types (`ChartDataPoint`, `ChartDataSeries`, `MultiSeries`) added to `src/types/index.ts` and exported from `./client`
|
|
39
|
+
- `charts_smoke.test.tsx` added (describe.skip — same recharts dual-React ESM limitation as render_smoke.test.tsx; real coverage via test-app)
|
|
40
|
+
|
|
41
|
+
## 0.1.0 — 2026-06-13 (unreleased)
|
|
42
|
+
|
|
43
|
+
Build 01: Scaffold + Headless Data Foundation.
|
|
44
|
+
|
|
45
|
+
- Package scaffold: ESM, `moduleResolution: node16`, jest (ESM), test-app (Next 16 stub)
|
|
46
|
+
- `DatasetDescriptor` + `Query` Zod schemas with `parseDescriptor` / `parseQuery`; coded `HAZO_DATAVIZ_INVALID_*` errors via `hazo_core.fromZodValidation`
|
|
47
|
+
- `derivePickerOptions(descriptor)` — pure function returning all valid measures/dims/aggs
|
|
48
|
+
- `createInMemoryRunner(descriptor, rows)` — full filter→time-bucket→group→aggregate→sort→limit pipeline; SQL null semantics; ISO-8601 week bucketing; `last` agg
|
|
49
|
+
- `formatValue` / `formatPercent` / `formatCompact` — delegate currency/number/date to `hazo_core`; `en-US` locale pinned for SSR safety
|
|
50
|
+
- `useDataQuery(descriptor, query, runner)` — React hook; loading→success/error; cancel-on-unmount
|
|
51
|
+
- `./testing` export — `prices`, `sector_breakdown`, `sparse`, `badDescriptorInput` fixtures + `createInMemoryRunner` re-export
|
|
52
|
+
- 70 Jest tests, all green; `audit:structure` + `audit:versions` pass
|
|
53
|
+
|
|
54
|
+
**Deferred to next session:** server-SQL runner (blocked: `hazo_connect` has no GROUP BY/aggregate support — candidate FR); Explorer UI / recharts; saved views / DDL.
|
package/README.md
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# hazo_dataviz
|
|
2
|
+
|
|
3
|
+
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.
|
|
4
|
+
|
|
5
|
+
## Status
|
|
6
|
+
|
|
7
|
+
**v0.3.0** — in development (not yet published to npm). Data layer + built-in visualizers + auto-selection complete. The server-SQL runner is deferred (blocked on `hazo_connect` GROUP BY support — see FR-003).
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install hazo_dataviz hazo_core
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Peer deps: `hazo_core` (required); `hazo_connect` / `hazo_ui` / `hazo_api` (optional). The visualizers depend on `recharts` and `react` (recharts is a direct dep; react is a peer).
|
|
16
|
+
|
|
17
|
+
## Quick start — data layer
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
import {
|
|
21
|
+
parseDescriptor,
|
|
22
|
+
createInMemoryRunner,
|
|
23
|
+
useDataQuery,
|
|
24
|
+
} from 'hazo_dataviz';
|
|
25
|
+
|
|
26
|
+
const descriptor = parseDescriptor({
|
|
27
|
+
name: 'sales',
|
|
28
|
+
version: '1',
|
|
29
|
+
grain: ['date', 'region'],
|
|
30
|
+
fields: {
|
|
31
|
+
date: { role: 'dimension', type: 'time', timeGrains: ['month', 'year'] },
|
|
32
|
+
region: { role: 'dimension', type: 'category' },
|
|
33
|
+
revenue: { role: 'measure', agg: 'sum', format: { kind: 'currency', ccy: 'USD' } },
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const rows = [/* your data */];
|
|
38
|
+
const runner = createInMemoryRunner(descriptor, rows);
|
|
39
|
+
|
|
40
|
+
// In a React component:
|
|
41
|
+
const { result, status, error } = useDataQuery(descriptor, {
|
|
42
|
+
measures: [{ field: 'revenue' }],
|
|
43
|
+
dimensions: ['date'],
|
|
44
|
+
timeGrain: 'month',
|
|
45
|
+
}, runner);
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Quick start — auto-selected visualizer
|
|
49
|
+
|
|
50
|
+
`<VisualizerView>` scores every built-in visualizer against the descriptor + query
|
|
51
|
+
and renders the best fit (argmax). It is client-only — import from `hazo_dataviz/visualizers`.
|
|
52
|
+
|
|
53
|
+
```tsx
|
|
54
|
+
'use client';
|
|
55
|
+
import { VisualizerView } from 'hazo_dataviz/visualizers';
|
|
56
|
+
|
|
57
|
+
// `result` comes from a runner (e.g. createInMemoryRunner above)
|
|
58
|
+
<VisualizerView descriptor={descriptor} query={query} result={result} opts={{ height: 300 }} />;
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Need manual control instead of auto-selection?
|
|
62
|
+
|
|
63
|
+
```tsx
|
|
64
|
+
import { builtinVisualizers, selectVisualizer, deriveEncoding } from 'hazo_dataviz/visualizers';
|
|
65
|
+
|
|
66
|
+
const { visualizer } = selectVisualizer(descriptor, query, result);
|
|
67
|
+
const encoding = deriveEncoding(query, result);
|
|
68
|
+
const el = visualizer.render(result, encoding, { height: 300, colors: ['#f5563d', '#14a098'] });
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Built-in visualizers & how they score
|
|
72
|
+
|
|
73
|
+
`accepts(descriptor, query) → number` (0 = can't render, higher = better fit). The winner is the highest score:
|
|
74
|
+
|
|
75
|
+
| id | When it fits | Score |
|
|
76
|
+
|---|---|---|
|
|
77
|
+
| `single_stat` | 0 dimensions + exactly 1 measure | 0.9 |
|
|
78
|
+
| `line` | ≥1 temporal dimension + ≥1 measure | 0.9 |
|
|
79
|
+
| `bar` | exactly 1 categorical dimension + ≥1 measure | 0.9 |
|
|
80
|
+
| `stacked_bar` | 2 dimensions + ≥1 measure + ≥1 categorical dim | 0.95 (categorical x) / 0.6 (temporal x) |
|
|
81
|
+
| `area` | ≥1 temporal dimension + ≥1 measure | 0.5 (never beats `line`) |
|
|
82
|
+
| `table` | anything | 0.1 (universal fallback) |
|
|
83
|
+
|
|
84
|
+
`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.
|
|
85
|
+
|
|
86
|
+
### Styling (the "Studio" theme)
|
|
87
|
+
|
|
88
|
+
Charts ship pre-styled — warm hairline grid, custom tooltip, rounded bars, gradient
|
|
89
|
+
area fills, tabular figures — with no Tailwind or external CSS required (everything is
|
|
90
|
+
inline SVG/JS so it renders correctly in any consumer). The series palette and theme
|
|
91
|
+
tokens live in `visualizers/palette.ts` and `visualizers/chart_frame.tsx`; override
|
|
92
|
+
colors per-render via `opts.colors`. SVG `font-family` is intentionally left unset so
|
|
93
|
+
charts inherit your app's font.
|
|
94
|
+
|
|
95
|
+
## Bespoke chart primitives
|
|
96
|
+
|
|
97
|
+
For hand-built charts (not query-driven), `hazo_dataviz/client` exports lightweight
|
|
98
|
+
recharts wrappers: `LineChart` (single series) and `MultiLineChart` (multi series),
|
|
99
|
+
both with null-gap handling and responsive sizing.
|
|
100
|
+
|
|
101
|
+
## Exports
|
|
102
|
+
|
|
103
|
+
| Entry point | Exports |
|
|
104
|
+
|---|---|
|
|
105
|
+
| `hazo_dataviz` (server) / `hazo_dataviz/client` | `parseDescriptor`, `parseQuery`, `derivePickerOptions`, `createInMemoryRunner`, `useDataQuery`, `formatValue` / `formatPercent` / `formatCompact`, `LineChart`, `MultiLineChart`, `CONTRACT_VERSION`, + types |
|
|
106
|
+
| `hazo_dataviz/visualizers` | `VisualizerView`, `builtinVisualizers`, `selectVisualizer`, `deriveEncoding`, `classifyShape`, + `Visualizer` / `VisualizerOpts` / `QueryShape` types |
|
|
107
|
+
| `hazo_dataviz/testing` | Fixtures (`prices`, `sector_breakdown`, `sparse`) + `createInMemoryRunner` re-export |
|
|
108
|
+
| `hazo_dataviz/api` | HTTP route handler factories (`hazo_api`-based) |
|
|
109
|
+
|
|
110
|
+
`hazo_dataviz/visualizers` and `hazo_dataviz/client` are client-only (`'use client'`); the
|
|
111
|
+
root `hazo_dataviz` import is server-safe and re-exports the client-safe data layer.
|
|
112
|
+
|
|
113
|
+
## License
|
|
114
|
+
|
|
115
|
+
MIT
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# hazo_dataviz — Setup Checklist
|
|
2
|
+
|
|
3
|
+
Follow these steps when adding hazo_dataviz to a consuming application.
|
|
4
|
+
|
|
5
|
+
## 1. Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install hazo_dataviz hazo_core recharts
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
- `hazo_core` is a required peer dependency.
|
|
12
|
+
- `recharts` is required by the built-in visualizers and `./client` chart primitives.
|
|
13
|
+
- `react` / `react-dom` are peer deps (^18 or ^19).
|
|
14
|
+
- `hazo_connect` / `hazo_ui` / `hazo_api` are optional peers — only needed if you use
|
|
15
|
+
the (future) server-SQL runner or the `./api` route handlers.
|
|
16
|
+
|
|
17
|
+
## 2. Configure
|
|
18
|
+
|
|
19
|
+
Nothing to configure. hazo_dataviz is headless and stateless — there are **no env vars,
|
|
20
|
+
no config files, and no database tables**. The in-memory runner operates on rows you
|
|
21
|
+
pass in; the visualizers are pure given a `Result` + `Encoding`.
|
|
22
|
+
|
|
23
|
+
## 3. Wire it up
|
|
24
|
+
|
|
25
|
+
1. Build a `DatasetDescriptor` with `parseDescriptor()` (throws a coded `HazoError` on
|
|
26
|
+
invalid shape).
|
|
27
|
+
2. Create a runner with `createInMemoryRunner(descriptor, rows)` and query it (or use the
|
|
28
|
+
`useDataQuery` React hook).
|
|
29
|
+
3. Render results with `<VisualizerView descriptor query result />` (auto-selects the
|
|
30
|
+
chart) from `hazo_dataviz/visualizers`.
|
|
31
|
+
|
|
32
|
+
> **Client boundary:** import visualizers from `hazo_dataviz/visualizers` (or chart
|
|
33
|
+
> primitives from `hazo_dataviz/client`) inside a `'use client'` component. The root
|
|
34
|
+
> `hazo_dataviz` import is server-safe and exposes only the client-safe data layer.
|
|
35
|
+
|
|
36
|
+
## 4. Verify
|
|
37
|
+
|
|
38
|
+
A quick smoke test using the bundled fixtures (no app data required):
|
|
39
|
+
|
|
40
|
+
```tsx
|
|
41
|
+
'use client';
|
|
42
|
+
import { VisualizerView } from 'hazo_dataviz/visualizers';
|
|
43
|
+
import { pricesDescriptor, pricesRows, createInMemoryRunner } from 'hazo_dataviz/testing';
|
|
44
|
+
|
|
45
|
+
const runner = createInMemoryRunner(pricesDescriptor, pricesRows);
|
|
46
|
+
const query = { measures: [{ field: 'close' }], dimensions: ['date', 'symbol'] };
|
|
47
|
+
const result = await runner(query);
|
|
48
|
+
|
|
49
|
+
// Renders a multi-series line chart (the auto-selected fit for a temporal query):
|
|
50
|
+
<VisualizerView descriptor={pricesDescriptor} query={query} result={result} opts={{ height: 300 }} />;
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
If the chart renders with a warm hairline grid and a coral/teal series palette, the
|
|
54
|
+
package is wired correctly.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/api/index.ts"],"names":[],"mappings":"AAEA,wBAAgB,oBAAoB,CAAC,OAAO,EAAE;IAAE,cAAc,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAA;CAAE,2BAQjG"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// hazo_dataviz/src/api/index.ts — HTTP route handler factories.
|
|
2
|
+
// Inject hazo_connect; never import it at module top-level.
|
|
3
|
+
export function createExampleHandler(options) {
|
|
4
|
+
// const adapter = await options.getHazoConnect();
|
|
5
|
+
void options;
|
|
6
|
+
return async function handler() {
|
|
7
|
+
return new Response(JSON.stringify({ ok: true }), {
|
|
8
|
+
headers: { 'content-type': 'application/json' },
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import type { ChartDataSeries } from '../types/index.js';
|
|
3
|
+
export interface LineChartProps {
|
|
4
|
+
data: ChartDataSeries;
|
|
5
|
+
dates: string[];
|
|
6
|
+
color: string;
|
|
7
|
+
width?: number;
|
|
8
|
+
height?: number;
|
|
9
|
+
unit?: string;
|
|
10
|
+
showTooltip?: boolean;
|
|
11
|
+
className?: string;
|
|
12
|
+
}
|
|
13
|
+
export declare function LineChart({ data, dates, color, width, height, unit, showTooltip, className, }: LineChartProps): React.ReactElement;
|
|
14
|
+
//# sourceMappingURL=line_chart.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"line_chart.d.ts","sourceRoot":"","sources":["../../src/charts/line_chart.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAU/B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEzD,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,eAAe,CAAC;IACtB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,SAAS,CAAC,EACxB,IAAI,EACJ,KAAK,EACL,KAAK,EACL,KAAK,EACL,MAAM,EACN,IAAS,EACT,WAAkB,EAClB,SAAS,GACV,EAAE,cAAc,GAAG,KAAK,CAAC,YAAY,CA2DrC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { LineChart as RechartsLineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, } from 'recharts';
|
|
4
|
+
export function LineChart({ data, dates, color, width, height, unit = '', showTooltip = true, className, }) {
|
|
5
|
+
// Empty data guard — render a placeholder, never throw
|
|
6
|
+
if (!data || data.length === 0) {
|
|
7
|
+
return (_jsx("div", { className: className, style: {
|
|
8
|
+
display: 'flex',
|
|
9
|
+
alignItems: 'center',
|
|
10
|
+
justifyContent: 'center',
|
|
11
|
+
height: height ?? 130,
|
|
12
|
+
color: '#9ca3af',
|
|
13
|
+
fontSize: 14,
|
|
14
|
+
}, children: "No data" }));
|
|
15
|
+
}
|
|
16
|
+
const chartData = dates.map((d, i) => ({ x: d, value: data[i] ?? null }));
|
|
17
|
+
const chartHeight = height ?? 130;
|
|
18
|
+
const tooltipFormatter = unit
|
|
19
|
+
? (v) => [`${v}${unit}`, '']
|
|
20
|
+
: undefined;
|
|
21
|
+
const inner = (_jsxs(RechartsLineChart, { data: chartData, width: width !== undefined ? width : undefined, height: width !== undefined ? chartHeight : undefined, children: [_jsx(CartesianGrid, { strokeDasharray: "3 3" }), _jsx(XAxis, { dataKey: "x" }), _jsx(YAxis, { tickFormatter: (v) => unit ? `${v}${unit}` : String(v) }), showTooltip && _jsx(Tooltip, { formatter: tooltipFormatter }), _jsx(Line, { type: "monotone", dataKey: "value", stroke: color, dot: false, connectNulls: false, isAnimationActive: false })] }));
|
|
22
|
+
if (width !== undefined) {
|
|
23
|
+
return (_jsx("div", { style: { width, height: chartHeight, overflow: 'hidden' }, className: className, children: inner }));
|
|
24
|
+
}
|
|
25
|
+
return (_jsx("div", { className: className, children: _jsx(ResponsiveContainer, { width: "100%", height: chartHeight, children: inner }) }));
|
|
26
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import type { MultiSeries } from '../types/index.js';
|
|
3
|
+
export interface MultiLineChartProps {
|
|
4
|
+
series: MultiSeries[];
|
|
5
|
+
dates: string[];
|
|
6
|
+
width?: number;
|
|
7
|
+
height?: number;
|
|
8
|
+
showTooltip?: boolean;
|
|
9
|
+
showLegend?: boolean;
|
|
10
|
+
className?: string;
|
|
11
|
+
}
|
|
12
|
+
export declare function MultiLineChart({ series, dates, width, height, showTooltip, showLegend, className, }: MultiLineChartProps): React.ReactElement;
|
|
13
|
+
//# sourceMappingURL=multi_line_chart.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"multi_line_chart.d.ts","sourceRoot":"","sources":["../../src/charts/multi_line_chart.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAW/B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,WAAW,EAAE,CAAC;IACtB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,cAAc,CAAC,EAC7B,MAAM,EACN,KAAK,EACL,KAAK,EACL,MAAM,EACN,WAAkB,EAClB,UAAiB,EACjB,SAAS,GACV,EAAE,mBAAmB,GAAG,KAAK,CAAC,YAAY,CAuE1C"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { LineChart as RechartsLineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer, } from 'recharts';
|
|
4
|
+
export function MultiLineChart({ series, dates, width, height, showTooltip = true, showLegend = true, className, }) {
|
|
5
|
+
const chartHeight = height ?? 130;
|
|
6
|
+
// Empty series or all-empty guard — render a placeholder, never throw
|
|
7
|
+
const hasData = series.length > 0 &&
|
|
8
|
+
series.some((s) => s.data.some((v) => v !== null));
|
|
9
|
+
if (!hasData) {
|
|
10
|
+
return (_jsx("div", { className: className, style: {
|
|
11
|
+
display: 'flex',
|
|
12
|
+
alignItems: 'center',
|
|
13
|
+
justifyContent: 'center',
|
|
14
|
+
height: chartHeight,
|
|
15
|
+
color: '#9ca3af',
|
|
16
|
+
fontSize: 14,
|
|
17
|
+
}, children: "No data" }));
|
|
18
|
+
}
|
|
19
|
+
// Build wide rows: { x: date, [seriesLabel]: value | null, ... }
|
|
20
|
+
const chartData = dates.map((d, i) => {
|
|
21
|
+
const row = { x: d };
|
|
22
|
+
for (const s of series) {
|
|
23
|
+
row[s.label] = s.data[i] ?? null;
|
|
24
|
+
}
|
|
25
|
+
return row;
|
|
26
|
+
});
|
|
27
|
+
const inner = (_jsxs(RechartsLineChart, { data: chartData, width: width !== undefined ? width : undefined, height: width !== undefined ? chartHeight : undefined, children: [_jsx(CartesianGrid, { strokeDasharray: "3 3" }), _jsx(XAxis, { dataKey: "x" }), _jsx(YAxis, {}), showTooltip && _jsx(Tooltip, {}), showLegend && _jsx(Legend, {}), series.map((s) => (_jsx(Line, { type: "monotone", dataKey: s.label, stroke: s.color, dot: false, connectNulls: false, isAnimationActive: false }, s.label)))] }));
|
|
28
|
+
if (width !== undefined) {
|
|
29
|
+
return (_jsx("div", { style: { width, height: chartHeight, overflow: 'hidden' }, className: className, children: inner }));
|
|
30
|
+
}
|
|
31
|
+
return (_jsx("div", { className: className, children: _jsx(ResponsiveContainer, { width: "100%", height: chartHeight, children: inner }) }));
|
|
32
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { DatasetDescriptor } from '../schema/descriptor.js';
|
|
2
|
+
import type { Query } from '../schema/query.js';
|
|
3
|
+
import type { Result, QueryRunner } from '../types/index.js';
|
|
4
|
+
export type QueryStatus = 'idle' | 'loading' | 'success' | 'error';
|
|
5
|
+
export interface UseDataQueryResult {
|
|
6
|
+
result: Result | undefined;
|
|
7
|
+
status: QueryStatus;
|
|
8
|
+
error: unknown;
|
|
9
|
+
}
|
|
10
|
+
export declare function useDataQuery(descriptor: DatasetDescriptor, query: Query, runner: QueryRunner): UseDataQueryResult;
|
|
11
|
+
//# sourceMappingURL=use_data_query.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use_data_query.d.ts","sourceRoot":"","sources":["../../src/hooks/use_data_query.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AACjE,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAE7D,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,CAAC;AAEnE,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,MAAM,EAAE,WAAW,CAAC;IACpB,KAAK,EAAE,OAAO,CAAC;CAChB;AAED,wBAAgB,YAAY,CAC1B,UAAU,EAAE,iBAAiB,EAC7B,KAAK,EAAE,KAAK,EACZ,MAAM,EAAE,WAAW,GAClB,kBAAkB,CAqCpB"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { useState, useEffect, useRef } from 'react';
|
|
2
|
+
export function useDataQuery(descriptor, query, runner) {
|
|
3
|
+
const [result, setResult] = useState(undefined);
|
|
4
|
+
const [status, setStatus] = useState('idle');
|
|
5
|
+
const [error, setError] = useState(undefined);
|
|
6
|
+
// Use a ref to track abort between renders
|
|
7
|
+
const cancelledRef = useRef(false);
|
|
8
|
+
// Serialize query + descriptor for dependency comparison
|
|
9
|
+
const queryKey = JSON.stringify(query);
|
|
10
|
+
const descriptorKey = descriptor.name + '@' + descriptor.version;
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
cancelledRef.current = false;
|
|
13
|
+
setStatus('loading');
|
|
14
|
+
setResult(undefined);
|
|
15
|
+
setError(undefined);
|
|
16
|
+
runner(query)
|
|
17
|
+
.then((res) => {
|
|
18
|
+
if (cancelledRef.current)
|
|
19
|
+
return;
|
|
20
|
+
setResult(res);
|
|
21
|
+
setStatus('success');
|
|
22
|
+
})
|
|
23
|
+
.catch((err) => {
|
|
24
|
+
if (cancelledRef.current)
|
|
25
|
+
return;
|
|
26
|
+
setError(err);
|
|
27
|
+
setStatus('error');
|
|
28
|
+
});
|
|
29
|
+
return () => {
|
|
30
|
+
cancelledRef.current = true;
|
|
31
|
+
};
|
|
32
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
33
|
+
}, [queryKey, descriptorKey, runner]);
|
|
34
|
+
return { result, status, error };
|
|
35
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export { CONTRACT_VERSION } from './lib/contract-version.js';
|
|
2
|
+
export { parseDescriptor } from './schema/descriptor.js';
|
|
3
|
+
export { parseQuery } from './schema/query.js';
|
|
4
|
+
export { derivePickerOptions } from './lib/picker.js';
|
|
5
|
+
export { createInMemoryRunner } from './lib/runners/in_memory.js';
|
|
6
|
+
export { formatValue, formatPercent, formatCompact } from './lib/format.js';
|
|
7
|
+
export { useDataQuery } from './hooks/use_data_query.js';
|
|
8
|
+
export type { DatasetDescriptor, Field, Agg, TimeGrain, Format, JoinSpec, } from './schema/descriptor.js';
|
|
9
|
+
export type { Query, MeasureRef, Filter, FilterOp, Sort } from './schema/query.js';
|
|
10
|
+
export type { Result, Column, Encoding, QueryRunner, PickerOptions } from './types/index.js';
|
|
11
|
+
export type { QueryStatus, UseDataQueryResult } from './hooks/use_data_query.js';
|
|
12
|
+
export { LineChart } from './charts/line_chart.js';
|
|
13
|
+
export { MultiLineChart } from './charts/multi_line_chart.js';
|
|
14
|
+
export type { LineChartProps } from './charts/line_chart.js';
|
|
15
|
+
export type { MultiLineChartProps } from './charts/multi_line_chart.js';
|
|
16
|
+
export type { ChartDataPoint, ChartDataSeries, MultiSeries } from './types/index.js';
|
|
17
|
+
//# sourceMappingURL=index.client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.client.d.ts","sourceRoot":"","sources":["../src/index.client.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAC7D,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AAClE,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAC5E,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AACzD,YAAY,EACV,iBAAiB,EACjB,KAAK,EACL,GAAG,EACH,SAAS,EACT,MAAM,EACN,QAAQ,GACT,MAAM,wBAAwB,CAAC;AAChC,YAAY,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AACnF,YAAY,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAC7F,YAAY,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAGjF,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACnD,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAC9D,YAAY,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAC7D,YAAY,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AACxE,YAAY,EAAE,cAAc,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// Client-safe entry — no Node.js builtins.
|
|
2
|
+
export { CONTRACT_VERSION } from './lib/contract-version.js';
|
|
3
|
+
export { parseDescriptor } from './schema/descriptor.js';
|
|
4
|
+
export { parseQuery } from './schema/query.js';
|
|
5
|
+
export { derivePickerOptions } from './lib/picker.js';
|
|
6
|
+
export { createInMemoryRunner } from './lib/runners/in_memory.js';
|
|
7
|
+
export { formatValue, formatPercent, formatCompact } from './lib/format.js';
|
|
8
|
+
export { useDataQuery } from './hooks/use_data_query.js';
|
|
9
|
+
// Bespoke chart components (recharts-based)
|
|
10
|
+
export { LineChart } from './charts/line_chart.js';
|
|
11
|
+
export { MultiLineChart } from './charts/multi_line_chart.js';
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,mBAAmB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { DatasetDescriptor } from '../schema/descriptor.js';
|
|
2
|
+
import type { Query } from '../schema/query.js';
|
|
3
|
+
import type { Result, Encoding, Visualizer } from '../types/index.js';
|
|
4
|
+
/**
|
|
5
|
+
* Derives an Encoding from a query and result.
|
|
6
|
+
* - x = query.dimensions[0] (or '' if none)
|
|
7
|
+
* - series = query.dimensions[1] if present
|
|
8
|
+
* - y = measure column keys from result.columns where role==='measure' (preserve order)
|
|
9
|
+
*
|
|
10
|
+
* When result is undefined, derive from query alone.
|
|
11
|
+
*/
|
|
12
|
+
export declare function deriveEncoding(query: Query, result?: Result): Encoding;
|
|
13
|
+
export interface SelectVisualizerResult {
|
|
14
|
+
visualizer: Visualizer;
|
|
15
|
+
encoding: Encoding;
|
|
16
|
+
candidates: Visualizer[];
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Score every builtin visualizer and return the best match with encoding.
|
|
20
|
+
* candidates = those with score > 0 sorted descending (stable; on tie keep builtins order).
|
|
21
|
+
* visualizer = top candidate (guaranteed non-empty because table always scores 0.1).
|
|
22
|
+
*/
|
|
23
|
+
export declare function selectVisualizer(descriptor: DatasetDescriptor, query: Query, result?: Result): SelectVisualizerResult;
|
|
24
|
+
//# sourceMappingURL=auto_encode.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auto_encode.d.ts","sourceRoot":"","sources":["../../src/lib/auto_encode.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AACjE,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAGtE;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,QAAQ,CAYtE;AAED,MAAM,WAAW,sBAAsB;IACrC,UAAU,EAAE,UAAU,CAAC;IACvB,QAAQ,EAAE,QAAQ,CAAC;IACnB,UAAU,EAAE,UAAU,EAAE,CAAC;CAC1B;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC9B,UAAU,EAAE,iBAAiB,EAC7B,KAAK,EAAE,KAAK,EACZ,MAAM,CAAC,EAAE,MAAM,GACd,sBAAsB,CAuBxB"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { builtinVisualizers } from '../visualizers/builtins.js';
|
|
2
|
+
/**
|
|
3
|
+
* Derives an Encoding from a query and result.
|
|
4
|
+
* - x = query.dimensions[0] (or '' if none)
|
|
5
|
+
* - series = query.dimensions[1] if present
|
|
6
|
+
* - y = measure column keys from result.columns where role==='measure' (preserve order)
|
|
7
|
+
*
|
|
8
|
+
* When result is undefined, derive from query alone.
|
|
9
|
+
*/
|
|
10
|
+
export function deriveEncoding(query, result) {
|
|
11
|
+
const x = query.dimensions[0] ?? '';
|
|
12
|
+
const series = query.dimensions.length >= 2 ? query.dimensions[1] : undefined;
|
|
13
|
+
let y;
|
|
14
|
+
if (result) {
|
|
15
|
+
y = result.columns.filter((c) => c.role === 'measure').map((c) => c.key);
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
y = query.measures.map((m) => m.field);
|
|
19
|
+
}
|
|
20
|
+
return { x, series, y };
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Score every builtin visualizer and return the best match with encoding.
|
|
24
|
+
* candidates = those with score > 0 sorted descending (stable; on tie keep builtins order).
|
|
25
|
+
* visualizer = top candidate (guaranteed non-empty because table always scores 0.1).
|
|
26
|
+
*/
|
|
27
|
+
export function selectVisualizer(descriptor, query, result) {
|
|
28
|
+
const scored = builtinVisualizers.map((v, idx) => ({
|
|
29
|
+
visualizer: v,
|
|
30
|
+
score: v.accepts(descriptor, query, result),
|
|
31
|
+
idx,
|
|
32
|
+
}));
|
|
33
|
+
// Keep only those with score > 0
|
|
34
|
+
const positive = scored.filter((s) => s.score > 0);
|
|
35
|
+
// Stable sort descending by score (preserve original order on tie)
|
|
36
|
+
positive.sort((a, b) => {
|
|
37
|
+
if (b.score !== a.score)
|
|
38
|
+
return b.score - a.score;
|
|
39
|
+
return a.idx - b.idx;
|
|
40
|
+
});
|
|
41
|
+
// Table always scores 0.1 so candidates is never empty
|
|
42
|
+
const candidates = positive.map((s) => s.visualizer);
|
|
43
|
+
const visualizer = candidates[0];
|
|
44
|
+
const encoding = deriveEncoding(query, result);
|
|
45
|
+
return { visualizer, encoding, candidates };
|
|
46
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contract-version.d.ts","sourceRoot":"","sources":["../../src/lib/contract-version.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,gBAAgB,EAAG,KAAc,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const CONTRACT_VERSION = '0.1';
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { Format } from '../schema/descriptor.js';
|
|
2
|
+
export declare function formatValue(value: unknown, format: Format): string;
|
|
3
|
+
export declare function formatPercent(value: number, decimals?: number): string;
|
|
4
|
+
export declare function formatCompact(value: number): string;
|
|
5
|
+
//# sourceMappingURL=format.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../../src/lib/format.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAItD,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAclE;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,SAAI,GAAG,MAAM,CAMjE;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAKnD"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { formatCurrency, formatNumber, format_date } from 'hazo_core';
|
|
2
|
+
const LOCALE = 'en-US';
|
|
3
|
+
export function formatValue(value, format) {
|
|
4
|
+
if (value === null || value === undefined)
|
|
5
|
+
return '';
|
|
6
|
+
switch (format.kind) {
|
|
7
|
+
case 'currency':
|
|
8
|
+
return formatCurrency(Number(value), format.ccy);
|
|
9
|
+
case 'number':
|
|
10
|
+
return formatNumber(Number(value), format.decimals);
|
|
11
|
+
case 'percent':
|
|
12
|
+
return formatPercent(Number(value));
|
|
13
|
+
case 'compact':
|
|
14
|
+
return formatCompact(Number(value));
|
|
15
|
+
case 'date':
|
|
16
|
+
return format_date(String(value), format.dateFormat ?? 'dmy');
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export function formatPercent(value, decimals = 2) {
|
|
20
|
+
return new Intl.NumberFormat(LOCALE, {
|
|
21
|
+
style: 'percent',
|
|
22
|
+
minimumFractionDigits: decimals,
|
|
23
|
+
maximumFractionDigits: decimals,
|
|
24
|
+
}).format(value / 100);
|
|
25
|
+
}
|
|
26
|
+
export function formatCompact(value) {
|
|
27
|
+
return new Intl.NumberFormat(LOCALE, {
|
|
28
|
+
notation: 'compact',
|
|
29
|
+
maximumFractionDigits: 1,
|
|
30
|
+
}).format(value);
|
|
31
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/lib/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"picker.d.ts","sourceRoot":"","sources":["../../src/lib/picker.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAO,MAAM,yBAAyB,CAAC;AACtE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAIvD,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,iBAAiB,GAAG,aAAa,CAmBhF"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
const ALL_AGGS = ['sum', 'avg', 'min', 'max', 'last', 'count'];
|
|
2
|
+
export function derivePickerOptions(descriptor) {
|
|
3
|
+
const measures = [];
|
|
4
|
+
const dimensions = [];
|
|
5
|
+
const filterableFields = [];
|
|
6
|
+
for (const [field, def] of Object.entries(descriptor.fields)) {
|
|
7
|
+
filterableFields.push(field);
|
|
8
|
+
if (def.role === 'measure') {
|
|
9
|
+
measures.push({ field, aggs: ALL_AGGS });
|
|
10
|
+
}
|
|
11
|
+
else {
|
|
12
|
+
dimensions.push({
|
|
13
|
+
field,
|
|
14
|
+
type: def.type,
|
|
15
|
+
timeGrains: def.timeGrains,
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return { measures, dimensions, filterableFields };
|
|
20
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { DatasetDescriptor } from '../../schema/descriptor.js';
|
|
2
|
+
import type { QueryRunner } from '../../types/index.js';
|
|
3
|
+
export declare function createInMemoryRunner(descriptor: DatasetDescriptor, rows: Record<string, unknown>[]): QueryRunner;
|
|
4
|
+
//# sourceMappingURL=in_memory.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"in_memory.d.ts","sourceRoot":"","sources":["../../../src/lib/runners/in_memory.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAkB,MAAM,4BAA4B,CAAC;AAEpF,OAAO,KAAK,EAAkB,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAmGxE,wBAAgB,oBAAoB,CAClC,UAAU,EAAE,iBAAiB,EAC7B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAC9B,WAAW,CAkMb"}
|