@matthieumordrel/chart-studio 0.3.0 → 0.5.2
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/README.md +10 -378
- package/dist/_internal.d.mts +9 -0
- package/dist/_internal.mjs +9 -0
- package/dist/core/chart-builder-controls.mjs +141 -0
- package/dist/core/chart-capabilities.d.mts +5 -0
- package/dist/core/chart-capabilities.mjs +9 -0
- package/dist/core/config-utils.mjs +2 -1
- package/dist/core/dashboard.types.d.mts +220 -0
- package/dist/core/data-label-defaults.d.mts +92 -0
- package/dist/core/data-label-defaults.mjs +78 -0
- package/dist/core/data-model.types.d.mts +196 -0
- package/dist/core/dataset-builder.types.d.mts +51 -0
- package/dist/core/dataset-chart-metadata.d.mts +8 -0
- package/dist/core/dataset-chart-metadata.mjs +4 -0
- package/dist/core/date-range-presets.d.mts +43 -1
- package/dist/core/date-range-presets.mjs +2 -2
- package/dist/core/date-utils.d.mts +26 -0
- package/dist/core/define-dashboard.d.mts +8 -0
- package/dist/core/define-dashboard.mjs +156 -0
- package/dist/core/define-data-model.d.mts +11 -0
- package/dist/core/define-data-model.mjs +327 -0
- package/dist/core/define-dataset.d.mts +13 -0
- package/dist/core/define-dataset.mjs +111 -0
- package/dist/core/formatting.d.mts +49 -0
- package/dist/core/formatting.mjs +32 -10
- package/dist/core/index.d.mts +19 -0
- package/dist/core/infer-columns.mjs +28 -2
- package/dist/core/materialized-view.mjs +580 -0
- package/dist/core/materialized-view.types.d.mts +223 -0
- package/dist/core/metric-utils.d.mts +18 -2
- package/dist/core/metric-utils.mjs +1 -1
- package/dist/core/model-chart.mjs +242 -0
- package/dist/core/model-chart.types.d.mts +199 -0
- package/dist/core/model-inference.mjs +169 -0
- package/dist/core/model-inference.types.d.mts +71 -0
- package/dist/core/pipeline.mjs +32 -1
- package/dist/core/schema-builder.mjs +28 -158
- package/dist/core/schema-builder.types.d.mts +2 -49
- package/dist/core/types.d.mts +61 -10
- package/dist/core/use-chart-options.d.mts +35 -8
- package/dist/core/use-chart-resolvers.mjs +13 -3
- package/dist/core/use-chart.d.mts +16 -12
- package/dist/core/use-chart.mjs +137 -35
- package/dist/core/use-dashboard.d.mts +190 -0
- package/dist/core/use-dashboard.mjs +551 -0
- package/dist/index.d.mts +14 -4
- package/dist/index.mjs +8 -2
- package/package.json +10 -41
- package/LICENSE +0 -21
- package/dist/core/define-chart-schema.d.mts +0 -38
- package/dist/core/define-chart-schema.mjs +0 -39
- package/dist/ui/chart-axis-ticks.mjs +0 -65
- package/dist/ui/chart-canvas.d.mts +0 -33
- package/dist/ui/chart-canvas.mjs +0 -779
- package/dist/ui/chart-context.d.mts +0 -99
- package/dist/ui/chart-context.mjs +0 -115
- package/dist/ui/chart-date-range-badge.d.mts +0 -20
- package/dist/ui/chart-date-range-badge.mjs +0 -49
- package/dist/ui/chart-date-range-panel.d.mts +0 -18
- package/dist/ui/chart-date-range-panel.mjs +0 -126
- package/dist/ui/chart-date-range.d.mts +0 -20
- package/dist/ui/chart-date-range.mjs +0 -67
- package/dist/ui/chart-debug.d.mts +0 -21
- package/dist/ui/chart-debug.mjs +0 -173
- package/dist/ui/chart-dropdown.mjs +0 -92
- package/dist/ui/chart-filters-panel.d.mts +0 -26
- package/dist/ui/chart-filters-panel.mjs +0 -132
- package/dist/ui/chart-filters.d.mts +0 -18
- package/dist/ui/chart-filters.mjs +0 -48
- package/dist/ui/chart-group-by-selector.d.mts +0 -16
- package/dist/ui/chart-group-by-selector.mjs +0 -32
- package/dist/ui/chart-metric-panel.d.mts +0 -25
- package/dist/ui/chart-metric-panel.mjs +0 -172
- package/dist/ui/chart-metric-selector.d.mts +0 -16
- package/dist/ui/chart-metric-selector.mjs +0 -50
- package/dist/ui/chart-select.mjs +0 -61
- package/dist/ui/chart-source-switcher.d.mts +0 -24
- package/dist/ui/chart-source-switcher.mjs +0 -56
- package/dist/ui/chart-time-bucket-selector.d.mts +0 -17
- package/dist/ui/chart-time-bucket-selector.mjs +0 -37
- package/dist/ui/chart-toolbar-overflow.d.mts +0 -28
- package/dist/ui/chart-toolbar-overflow.mjs +0 -231
- package/dist/ui/chart-toolbar.d.mts +0 -33
- package/dist/ui/chart-toolbar.mjs +0 -60
- package/dist/ui/chart-type-selector.d.mts +0 -19
- package/dist/ui/chart-type-selector.mjs +0 -168
- package/dist/ui/chart-x-axis-selector.d.mts +0 -16
- package/dist/ui/chart-x-axis-selector.mjs +0 -28
- package/dist/ui/index.d.mts +0 -19
- package/dist/ui/index.mjs +0 -18
- package/dist/ui/percent-stacked.mjs +0 -36
- package/dist/ui/theme.css +0 -67
- package/dist/ui/toolbar-types.d.mts +0 -7
- package/dist/ui/toolbar-types.mjs +0 -83
package/README.md
CHANGED
|
@@ -1,395 +1,27 @@
|
|
|
1
|
-
# chart-studio
|
|
1
|
+
# @matthieumordrel/chart-studio
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> Early alpha. Active work in progress. Not recommended for production use yet.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
- use the **optional UI layer** if you also want ready-made controls and a Recharts canvas
|
|
5
|
+
Headless, composable charting for React.
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
## Start Here
|
|
11
|
-
|
|
12
|
-
Choose the path that matches your app:
|
|
13
|
-
|
|
14
|
-
### 1. Headless core
|
|
15
|
-
|
|
16
|
-
Use this if you already have your own design system or chart renderer.
|
|
17
|
-
|
|
18
|
-
You get:
|
|
19
|
-
|
|
20
|
-
- `useChart`
|
|
21
|
-
- optional `schema` via `defineChartSchema`
|
|
22
|
-
- transformed chart data
|
|
23
|
-
- filtering, grouping, metrics, and time bucketing logic
|
|
24
|
-
|
|
25
|
-
Requirements:
|
|
26
|
-
|
|
27
|
-
- `react` >= 18.2.0
|
|
28
|
-
|
|
29
|
-
Install:
|
|
30
|
-
|
|
31
|
-
```bash
|
|
32
|
-
bun add @matthieumordrel/chart-studio react
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
Import from:
|
|
36
|
-
|
|
37
|
-
```tsx
|
|
38
|
-
import { useChart } from '@matthieumordrel/chart-studio'
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
### 2. Ready-made UI
|
|
42
|
-
|
|
43
|
-
Use this if you want the package to render the controls and chart for you.
|
|
44
|
-
|
|
45
|
-
You get:
|
|
46
|
-
|
|
47
|
-
- everything from the headless core
|
|
48
|
-
- `<Chart>`
|
|
49
|
-
- `<ChartToolbar>`
|
|
50
|
-
- `<ChartCanvas>`
|
|
51
|
-
- granular UI controls from `@matthieumordrel/chart-studio/ui`
|
|
52
|
-
|
|
53
|
-
Requirements:
|
|
54
|
-
|
|
55
|
-
- `react` >= 18.2.0
|
|
56
|
-
- `recharts` >= 3.0.0 (v2 is **not** supported)
|
|
57
|
-
- `lucide-react` >= 0.577.0 (optional, for toolbar icons)
|
|
7
|
+
Use this package when you want chart state, filtering, grouping, metrics, time bucketing, transformed data, and the model/dashboard APIs without the optional UI layer.
|
|
58
8
|
|
|
59
9
|
Install:
|
|
60
10
|
|
|
61
11
|
```bash
|
|
62
|
-
bun add @matthieumordrel/chart-studio react
|
|
12
|
+
bun add @matthieumordrel/chart-studio@alpha react
|
|
63
13
|
```
|
|
64
14
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
```css
|
|
68
|
-
@import 'tailwindcss';
|
|
69
|
-
@import '@matthieumordrel/chart-studio/ui/theme.css';
|
|
70
|
-
```
|
|
15
|
+
Current prereleases are published under the `alpha` dist-tag on npm.
|
|
71
16
|
|
|
72
17
|
Import from:
|
|
73
18
|
|
|
74
19
|
```tsx
|
|
75
|
-
import { useChart
|
|
76
|
-
import { Chart, ChartToolbar, ChartCanvas } from '@matthieumordrel/chart-studio/ui'
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
## Smallest Working Example (Single Source)
|
|
80
|
-
|
|
81
|
-
```tsx
|
|
82
|
-
import { useChart } from '@matthieumordrel/chart-studio'
|
|
83
|
-
import { Chart, ChartToolbar, ChartCanvas } from '@matthieumordrel/chart-studio/ui'
|
|
84
|
-
import { data } from './data.json'
|
|
85
|
-
|
|
86
|
-
export function JobsChart() {
|
|
87
|
-
const chart = useChart({ data })
|
|
88
|
-
|
|
89
|
-
return (
|
|
90
|
-
<Chart chart={chart}>
|
|
91
|
-
<ChartToolbar />
|
|
92
|
-
<ChartCanvas height={320} />
|
|
93
|
-
</Chart>
|
|
94
|
-
)
|
|
95
|
-
}
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
## How It Works
|
|
99
|
-
|
|
100
|
-
1. Pass your raw data to `useChart()`.
|
|
101
|
-
2. Add an optional `schema` with `defineChartSchema<Row>()...` when you need labels, type overrides, derived columns, or control restrictions (allowed metrics, groupings, chart types, etc.).
|
|
102
|
-
3. Either render your own UI from the returned state, or use the components from `@matthieumordrel/chart-studio/ui`.
|
|
103
|
-
|
|
104
|
-
## Column Types
|
|
105
|
-
|
|
106
|
-
| Type | What it is for |
|
|
107
|
-
| ---------- | --------------------------------------- |
|
|
108
|
-
| `date` | time-series X-axis |
|
|
109
|
-
| `category` | categorical X-axis, grouping, filtering |
|
|
110
|
-
| `boolean` | grouping, filtering |
|
|
111
|
-
| `number` | metrics such as sum, avg, min, max |
|
|
112
|
-
|
|
113
|
-
## Declarative Schema and Control Restrictions
|
|
114
|
-
|
|
115
|
-
If you want to expose only a subset of groupings, metrics, chart types, or axes, use the fluent `defineChartSchema<Row>()` builder:
|
|
116
|
-
|
|
117
|
-
```tsx
|
|
118
|
-
import { defineChartSchema, useChart } from '@matthieumordrel/chart-studio'
|
|
119
|
-
|
|
120
|
-
type Row = { periodEnd: string; segment: string; revenue: number; netIncome: number }
|
|
121
|
-
|
|
122
|
-
const schema = defineChartSchema<Row>()
|
|
123
|
-
.columns((c) => [
|
|
124
|
-
c.date('periodEnd', { label: 'Period End' }),
|
|
125
|
-
c.category('segment'),
|
|
126
|
-
c.number('revenue'),
|
|
127
|
-
c.number('netIncome')
|
|
128
|
-
])
|
|
129
|
-
.xAxis((x) => x.allowed('periodEnd'))
|
|
130
|
-
.groupBy((g) => g.allowed('segment'))
|
|
131
|
-
.metric((m) =>
|
|
132
|
-
m
|
|
133
|
-
.count()
|
|
134
|
-
.aggregate('revenue', 'sum', 'avg')
|
|
135
|
-
.aggregate('netIncome', 'sum')
|
|
136
|
-
)
|
|
137
|
-
.chartType((t) => t.allowed('bar', 'line'))
|
|
138
|
-
.timeBucket((tb) => tb.allowed('year', 'quarter', 'month'))
|
|
139
|
-
|
|
140
|
-
const chart = useChart({ data, schema })
|
|
141
|
-
```
|
|
142
|
-
|
|
143
|
-
Why this pattern:
|
|
144
|
-
|
|
145
|
-
- `columns` defines types, labels, and formats for raw fields; use `c.exclude(...)` to remove a column from the chart
|
|
146
|
-
- Derived columns use `c.derived.*(...)` helpers for computed values from each row
|
|
147
|
-
- `xAxis`, `groupBy`, `metric`, `chartType`, and `timeBucket` restrict the allowed options
|
|
148
|
-
- invalid column IDs and config keys are rejected at compile time
|
|
149
|
-
- metric restrictions preserve the order you declare, so the first allowed metric becomes the default
|
|
150
|
-
|
|
151
|
-
## Headless Example
|
|
152
|
-
|
|
153
|
-
If you want to render your own UI or your own charting library, use only the core state:
|
|
154
|
-
|
|
155
|
-
```tsx
|
|
156
|
-
import { defineChartSchema, useChart } from '@matthieumordrel/chart-studio'
|
|
157
|
-
|
|
158
|
-
type Job = {
|
|
159
|
-
dateAdded: string
|
|
160
|
-
ownerName: string
|
|
161
|
-
salary: number
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
const jobSchema = defineChartSchema<Job>()
|
|
165
|
-
.columns((c) => [
|
|
166
|
-
c.date('dateAdded', { label: 'Date Added' }),
|
|
167
|
-
c.category('ownerName', { label: 'Consultant' }),
|
|
168
|
-
c.number('salary', { label: 'Salary' })
|
|
169
|
-
])
|
|
170
|
-
|
|
171
|
-
export function JobsChartHeadless({ data }: { data: Job[] }) {
|
|
172
|
-
const chart = useChart({ data, schema: jobSchema })
|
|
173
|
-
|
|
174
|
-
return (
|
|
175
|
-
<div>
|
|
176
|
-
<div>Chart type: {chart.chartType}</div>
|
|
177
|
-
<div>Rows: {chart.transformedData.length}</div>
|
|
178
|
-
<pre>{JSON.stringify(chart.transformedData, null, 2)}</pre>
|
|
179
|
-
</div>
|
|
180
|
-
)
|
|
181
|
-
}
|
|
182
|
-
```
|
|
183
|
-
|
|
184
|
-
## Styling Requirements
|
|
185
|
-
|
|
186
|
-
The headless core has no styling requirements.
|
|
187
|
-
|
|
188
|
-
The `ui` layer is Tailwind-based and uses semantic classes such as:
|
|
189
|
-
|
|
190
|
-
- `bg-background`
|
|
191
|
-
- `text-foreground`
|
|
192
|
-
- `border-border`
|
|
193
|
-
- `bg-popover`
|
|
194
|
-
- `text-muted-foreground`
|
|
195
|
-
|
|
196
|
-
For those classes to render correctly, Tailwind needs real values behind tokens like `background`, `foreground`, `border`, and `popover`.
|
|
197
|
-
|
|
198
|
-
You can use `ui` in two ways:
|
|
199
|
-
|
|
200
|
-
### 1. Recommended: import the built-in theme
|
|
201
|
-
|
|
202
|
-
This is the easiest setup:
|
|
203
|
-
|
|
204
|
-
```css
|
|
205
|
-
@import 'tailwindcss';
|
|
206
|
-
@import '@matthieumordrel/chart-studio/ui/theme.css';
|
|
207
|
-
```
|
|
208
|
-
|
|
209
|
-
This does three things for you:
|
|
210
|
-
|
|
211
|
-
- Tailwind utilities for the package components
|
|
212
|
-
- automatic scanning of the package UI classes
|
|
213
|
-
- default fallback values for all semantic UI tokens
|
|
214
|
-
- built-in light and dark default themes
|
|
215
|
-
|
|
216
|
-
If your app already defines matching shadcn-style variables, those values take over automatically. If not, the built-in defaults are used.
|
|
217
|
-
|
|
218
|
-
The shipped theme supports dark mode through either:
|
|
219
|
-
|
|
220
|
-
- `.dark`
|
|
221
|
-
- `[data-theme="dark"]`
|
|
222
|
-
|
|
223
|
-
### 2. Advanced: define everything yourself
|
|
224
|
-
|
|
225
|
-
If you do not want to import `@matthieumordrel/chart-studio/ui/theme.css`, you can provide all the required semantic tokens yourself in your app theme.
|
|
226
|
-
|
|
227
|
-
If neither of those is true, use the headless core and render your own controls.
|
|
228
|
-
|
|
229
|
-
### Minimum UI theme contract
|
|
230
|
-
|
|
231
|
-
You do not need shadcn itself to use `@matthieumordrel/chart-studio/ui`.
|
|
232
|
-
|
|
233
|
-
If you import `@matthieumordrel/chart-studio/ui/theme.css`, every token below gets a built-in fallback automatically.
|
|
234
|
-
|
|
235
|
-
If your app already defines some of these variables, your values override the defaults for those specific tokens only. Missing ones still fall back to the package defaults.
|
|
236
|
-
|
|
237
|
-
These are the tokens currently expected by the UI layer:
|
|
238
|
-
|
|
239
|
-
| Token | Purpose |
|
|
240
|
-
| -------------------- | -------------------------------------- |
|
|
241
|
-
| `background` | control backgrounds and input surfaces |
|
|
242
|
-
| `foreground` | primary text |
|
|
243
|
-
| `muted` | subtle backgrounds and hover states |
|
|
244
|
-
| `muted-foreground` | secondary text and icons |
|
|
245
|
-
| `border` | outlines and separators |
|
|
246
|
-
| `popover` | dropdowns and floating panels |
|
|
247
|
-
| `popover-foreground` | popover text color |
|
|
248
|
-
| `primary` | selected and active states |
|
|
249
|
-
| `primary-foreground` | text on filled primary surfaces |
|
|
250
|
-
| `ring` | focus-visible ring color |
|
|
251
|
-
|
|
252
|
-
Minimal example:
|
|
253
|
-
|
|
254
|
-
```css
|
|
255
|
-
:root {
|
|
256
|
-
--background: 0 0% 100%;
|
|
257
|
-
--foreground: 222.2 84% 4.9%;
|
|
258
|
-
--muted: 210 40% 96.1%;
|
|
259
|
-
--muted-foreground: 215.4 16.3% 46.9%;
|
|
260
|
-
--border: 214.3 31.8% 91.4%;
|
|
261
|
-
--popover: 0 0% 100%;
|
|
262
|
-
--popover-foreground: 222.2 84% 4.9%;
|
|
263
|
-
--primary: 222.2 47.4% 11.2%;
|
|
264
|
-
--primary-foreground: 210 40% 98%;
|
|
265
|
-
--ring: 221.2 83.2% 53.3%;
|
|
266
|
-
}
|
|
267
|
-
```
|
|
268
|
-
|
|
269
|
-
How this works in practice:
|
|
270
|
-
|
|
271
|
-
- import `ui/theme.css` and do nothing else: the package uses its own defaults
|
|
272
|
-
- toggle dark mode with either `.dark` or `[data-theme="dark"]`: the package uses its built-in dark defaults
|
|
273
|
-
- import `ui/theme.css` and define only a few variables: your values win for those variables, defaults cover the rest
|
|
274
|
-
- skip `ui/theme.css`: you must define the whole token contract yourself
|
|
275
|
-
|
|
276
|
-
That makes the package usable out of the box while still being easy to theme.
|
|
277
|
-
|
|
278
|
-
### Optional chart color tokens
|
|
279
|
-
|
|
280
|
-
Chart series colors also support shadcn-style chart variables:
|
|
281
|
-
|
|
282
|
-
| Token | Purpose |
|
|
283
|
-
| --------- | ------------------- |
|
|
284
|
-
| `chart-1` | first series color |
|
|
285
|
-
| `chart-2` | second series color |
|
|
286
|
-
| `chart-3` | third series color |
|
|
287
|
-
| `chart-4` | fourth series color |
|
|
288
|
-
| `chart-5` | fifth series color |
|
|
289
|
-
|
|
290
|
-
These are also optional when you import `ui/theme.css`.
|
|
291
|
-
|
|
292
|
-
If your app defines `--chart-1` through `--chart-5`, those colors are used automatically.
|
|
293
|
-
|
|
294
|
-
If they are not defined, `chart-studio` falls back to a built-in OKLCH palette, with separate light and dark defaults. That is why you may see blue, rose, cyan, or other fallback colors in charts when your app does not provide chart variables.
|
|
295
|
-
|
|
296
|
-
Minimal example:
|
|
297
|
-
|
|
298
|
-
```css
|
|
299
|
-
:root {
|
|
300
|
-
--chart-1: 221.2 83.2% 53.3%;
|
|
301
|
-
--chart-2: 262.1 83.3% 57.8%;
|
|
302
|
-
--chart-3: 24.6 95% 53.1%;
|
|
303
|
-
--chart-4: 142.1 76.2% 36.3%;
|
|
304
|
-
--chart-5: 346.8 77.2% 49.8%;
|
|
305
|
-
}
|
|
306
|
-
```
|
|
307
|
-
|
|
308
|
-
## Common Questions
|
|
309
|
-
|
|
310
|
-
### Which import path should I use?
|
|
311
|
-
|
|
312
|
-
- Use `@matthieumordrel/chart-studio` for the headless core.
|
|
313
|
-
- Use `@matthieumordrel/chart-studio/ui` for the optional UI components.
|
|
314
|
-
|
|
315
|
-
### Do I need Recharts?
|
|
316
|
-
|
|
317
|
-
Only for the UI layer. The headless core works without it.
|
|
318
|
-
|
|
319
|
-
### Do I need Tailwind?
|
|
320
|
-
|
|
321
|
-
Only for the UI layer. The headless core does not require it.
|
|
322
|
-
|
|
323
|
-
### Can I use multiple datasets?
|
|
324
|
-
|
|
325
|
-
Yes:
|
|
326
|
-
|
|
327
|
-
```tsx
|
|
328
|
-
import { defineChartSchema, useChart } from '@matthieumordrel/chart-studio'
|
|
329
|
-
|
|
330
|
-
const chart = useChart({
|
|
331
|
-
sources: [
|
|
332
|
-
{
|
|
333
|
-
id: 'jobs',
|
|
334
|
-
label: 'Jobs',
|
|
335
|
-
data: jobs,
|
|
336
|
-
schema: defineChartSchema<Job>()
|
|
337
|
-
.columns((c) => [c.date('dateAdded', { label: 'Date Added' })])
|
|
338
|
-
},
|
|
339
|
-
{ id: 'candidates', label: 'Candidates', data: candidates }
|
|
340
|
-
]
|
|
341
|
-
})
|
|
342
|
-
```
|
|
343
|
-
|
|
344
|
-
### What chart types are available?
|
|
345
|
-
|
|
346
|
-
- date X-axis: `bar`, `line`, `area`
|
|
347
|
-
- category or boolean X-axis: `bar`, `pie`, `donut`
|
|
348
|
-
- `pie` and `donut` do not support `groupBy`
|
|
349
|
-
|
|
350
|
-
## Troubleshooting
|
|
351
|
-
|
|
352
|
-
### The UI looks mostly unstyled
|
|
353
|
-
|
|
354
|
-
If the components render but look plain, compressed, or layout incorrectly, the most common cause is that the package theme file is not imported.
|
|
355
|
-
|
|
356
|
-
Start with:
|
|
357
|
-
|
|
358
|
-
```css
|
|
359
|
-
@import 'tailwindcss';
|
|
360
|
-
@import '@matthieumordrel/chart-studio/ui/theme.css';
|
|
20
|
+
import {defineDataset, useChart} from '@matthieumordrel/chart-studio'
|
|
361
21
|
```
|
|
362
22
|
|
|
363
|
-
If you
|
|
364
|
-
|
|
365
|
-
If your app already uses shadcn-style tokens, also make sure tokens such as `background`, `foreground`, `muted`, `border`, `popover`, `primary`, `ring`, and optionally `chart-1` through `chart-5` are defined in your theme.
|
|
366
|
-
|
|
367
|
-
## On the Radar
|
|
368
|
-
|
|
369
|
-
These are known limitations and areas being considered for future versions. None of these are committed — they represent directions the library may grow based on real usage.
|
|
370
|
-
|
|
371
|
-
### Renderer flexibility
|
|
372
|
-
|
|
373
|
-
The UI layer currently only supports Recharts. If you want to use ECharts, Plotly, or another renderer, you can use the headless core but lose the built-in toolbar and canvas composition. A renderer adapter pattern for `<ChartCanvas>` could make the UI layer renderer-agnostic.
|
|
374
|
-
|
|
375
|
-
### Richer aggregation
|
|
376
|
-
|
|
377
|
-
The pipeline supports sum, avg, min, and max. Derived columns can access multiple fields of a single row (e.g. `row.revenue - row.cost`), but there is no support yet for metrics that depend on other rows or on aggregated results — things like "% of total", running totals, percentiles, or post-aggregation ratios (e.g. total revenue / total orders).
|
|
378
|
-
|
|
379
|
-
### Chart interactivity
|
|
380
|
-
|
|
381
|
-
There is currently no built-in support for drill-down, click-to-filter, brush selection, or linked charts. The headless state can be wired manually to achieve some of these, but first-class interactivity primitives would make this significantly easier.
|
|
382
|
-
|
|
383
|
-
### Multi-dataset composition
|
|
384
|
-
|
|
385
|
-
Each chart instance operates on a single flat dataset. Overlaying series from different schemas (e.g. revenue on the left Y-axis and headcount on the right) would require separate chart instances today. Dual-axis and cross-dataset composition are not yet supported.
|
|
386
|
-
|
|
387
|
-
### Schema Builder Ergonomics
|
|
388
|
-
|
|
389
|
-
`defineChartSchema<Row>()` now returns one fluent builder that you pass directly to `useChart(...)` or `inferColumnsFromData(...)`. That keeps the public API strongly typed while improving IntelliSense for raw field ids, derived columns, and control restrictions.
|
|
23
|
+
If you also want the optional ready-made React UI, install `@matthieumordrel/chart-studio-ui` alongside this package.
|
|
390
24
|
|
|
391
|
-
|
|
25
|
+
Special thanks to the teams behind TanStack Table and Recharts.
|
|
392
26
|
|
|
393
|
-
|
|
394
|
-
- `bun run release:publish -- --tag=latest`
|
|
395
|
-
- `npm publish` runs `prepublishOnly`, which calls `bun run release:check`
|
|
27
|
+
Full documentation: <https://github.com/MatthieuMordrel/chart-studio#readme>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { DATE_RANGE_PRESETS, DateRangePreset, autoFilterForBucket, getPresetLabel, resolvePresetFilter } from "./core/date-range-presets.mjs";
|
|
2
|
+
import { CHART_TYPE_CONFIG } from "./core/chart-capabilities.mjs";
|
|
3
|
+
import { buildColorMap, getSeriesColor } from "./core/colors.mjs";
|
|
4
|
+
import { DATA_LABEL_DEFAULTS, DataLabelDefaults, DataLabelPosition, DataLabelStyle, resolveShowDataLabels } from "./core/data-label-defaults.mjs";
|
|
5
|
+
import { computeDateRange, filterByDateRange } from "./core/date-utils.mjs";
|
|
6
|
+
import { ChartValueSurface, NumericRange, createNumericRange, formatChartValue, formatNumericSurfaceValue, formatTimeBucketLabel, shouldAllowDecimalTicks } from "./core/formatting.mjs";
|
|
7
|
+
import { DEFAULT_METRIC, getAggregateMetricLabel, getMetricLabel, isAggregateMetric, isSameMetric } from "./core/metric-utils.mjs";
|
|
8
|
+
import { applyFilters } from "./core/pipeline.mjs";
|
|
9
|
+
export { CHART_TYPE_CONFIG, type ChartValueSurface, DATA_LABEL_DEFAULTS, DATE_RANGE_PRESETS, DEFAULT_METRIC, type DataLabelDefaults, type DataLabelPosition, type DataLabelStyle, type DateRangePreset, type NumericRange, applyFilters, autoFilterForBucket, buildColorMap, computeDateRange, createNumericRange, filterByDateRange, formatChartValue, formatNumericSurfaceValue, formatTimeBucketLabel, getAggregateMetricLabel, getMetricLabel, getPresetLabel, getSeriesColor, isAggregateMetric, isSameMetric, resolvePresetFilter, resolveShowDataLabels, shouldAllowDecimalTicks };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { CHART_TYPE_CONFIG } from "./core/chart-capabilities.mjs";
|
|
2
|
+
import { DATA_LABEL_DEFAULTS, resolveShowDataLabels } from "./core/data-label-defaults.mjs";
|
|
3
|
+
import { buildColorMap, getSeriesColor } from "./core/colors.mjs";
|
|
4
|
+
import { DATE_RANGE_PRESETS, autoFilterForBucket, getPresetLabel, resolvePresetFilter } from "./core/date-range-presets.mjs";
|
|
5
|
+
import { computeDateRange, filterByDateRange } from "./core/date-utils.mjs";
|
|
6
|
+
import { DEFAULT_METRIC, getAggregateMetricLabel, getMetricLabel, isAggregateMetric, isSameMetric } from "./core/metric-utils.mjs";
|
|
7
|
+
import { createNumericRange, formatChartValue, formatNumericSurfaceValue, formatTimeBucketLabel, shouldAllowDecimalTicks } from "./core/formatting.mjs";
|
|
8
|
+
import { applyFilters } from "./core/pipeline.mjs";
|
|
9
|
+
export { CHART_TYPE_CONFIG, DATA_LABEL_DEFAULTS, DATE_RANGE_PRESETS, DEFAULT_METRIC, applyFilters, autoFilterForBucket, buildColorMap, computeDateRange, createNumericRange, filterByDateRange, formatChartValue, formatNumericSurfaceValue, formatTimeBucketLabel, getAggregateMetricLabel, getMetricLabel, getPresetLabel, getSeriesColor, isAggregateMetric, isSameMetric, resolvePresetFilter, resolveShowDataLabels, shouldAllowDecimalTicks };
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { isSameMetric, normalizeMetricAllowances } from "./metric-utils.mjs";
|
|
2
|
+
//#region src/core/chart-builder-controls.ts
|
|
3
|
+
const SELECTABLE_CONTROL_CONFIG = Symbol("chart-schema-selectable-control-config");
|
|
4
|
+
const METRIC_CONTROL_CONFIG = Symbol("chart-schema-metric-config");
|
|
5
|
+
function uniqueValues(values) {
|
|
6
|
+
if (!values || values.length === 0) return;
|
|
7
|
+
return [...new Set(values)];
|
|
8
|
+
}
|
|
9
|
+
function sanitizeSelectableControlConfig(config, supportsDefault) {
|
|
10
|
+
const allowed = uniqueValues(config.allowed);
|
|
11
|
+
let hidden = uniqueValues(config.hidden);
|
|
12
|
+
if (allowed && hidden) {
|
|
13
|
+
const allowedSet = new Set(allowed);
|
|
14
|
+
hidden = hidden.filter((option) => allowedSet.has(option));
|
|
15
|
+
}
|
|
16
|
+
let nextDefault = supportsDefault ? config.default : void 0;
|
|
17
|
+
if (nextDefault !== void 0) {
|
|
18
|
+
if (allowed && !allowed.includes(nextDefault)) nextDefault = void 0;
|
|
19
|
+
if (nextDefault !== void 0 && hidden?.includes(nextDefault)) nextDefault = void 0;
|
|
20
|
+
}
|
|
21
|
+
const nextConfig = {};
|
|
22
|
+
if (allowed && allowed.length > 0) nextConfig.allowed = allowed;
|
|
23
|
+
if (hidden && hidden.length > 0) nextConfig.hidden = hidden;
|
|
24
|
+
if (nextDefault !== void 0) nextConfig.default = nextDefault;
|
|
25
|
+
return nextConfig;
|
|
26
|
+
}
|
|
27
|
+
function createSelectableControlBuilder(config = {}, supportsDefault) {
|
|
28
|
+
const nextConfig = sanitizeSelectableControlConfig(config, supportsDefault);
|
|
29
|
+
return {
|
|
30
|
+
allowed(...options) {
|
|
31
|
+
return createSelectableControlBuilder({
|
|
32
|
+
...nextConfig,
|
|
33
|
+
allowed: options
|
|
34
|
+
}, supportsDefault);
|
|
35
|
+
},
|
|
36
|
+
hidden(...options) {
|
|
37
|
+
return createSelectableControlBuilder({
|
|
38
|
+
...nextConfig,
|
|
39
|
+
hidden: [...nextConfig.hidden ?? [], ...options]
|
|
40
|
+
}, supportsDefault);
|
|
41
|
+
},
|
|
42
|
+
default(option) {
|
|
43
|
+
return createSelectableControlBuilder({
|
|
44
|
+
...nextConfig,
|
|
45
|
+
default: option
|
|
46
|
+
}, supportsDefault);
|
|
47
|
+
},
|
|
48
|
+
[SELECTABLE_CONTROL_CONFIG]: nextConfig
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
function uniqueMetrics(metrics) {
|
|
52
|
+
if (!metrics || metrics.length === 0) return;
|
|
53
|
+
const unique = [];
|
|
54
|
+
for (const metric of metrics) if (!unique.some((candidate) => isSameMetric(candidate, metric))) unique.push(metric);
|
|
55
|
+
return unique;
|
|
56
|
+
}
|
|
57
|
+
function sanitizeMetricConfig(config) {
|
|
58
|
+
const allowed = config.allowed && config.allowed.length > 0 ? [...config.allowed] : void 0;
|
|
59
|
+
let hidden = uniqueMetrics(config.hidden);
|
|
60
|
+
const expandedAllowed = normalizeMetricAllowances(allowed);
|
|
61
|
+
if (expandedAllowed && hidden) hidden = hidden.filter((metric) => expandedAllowed.some((allowedMetric) => isSameMetric(allowedMetric, metric)));
|
|
62
|
+
let nextDefault = config.default;
|
|
63
|
+
if (nextDefault) {
|
|
64
|
+
const defaultMetric = nextDefault;
|
|
65
|
+
if (expandedAllowed && !expandedAllowed.some((metric) => isSameMetric(metric, defaultMetric))) nextDefault = void 0;
|
|
66
|
+
if (nextDefault) {
|
|
67
|
+
const visibleDefault = nextDefault;
|
|
68
|
+
if (hidden?.some((metric) => isSameMetric(metric, visibleDefault))) nextDefault = void 0;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
const nextConfig = {};
|
|
72
|
+
if (allowed && allowed.length > 0) nextConfig.allowed = allowed;
|
|
73
|
+
if (hidden && hidden.length > 0) nextConfig.hidden = hidden;
|
|
74
|
+
if (nextDefault) nextConfig.default = nextDefault;
|
|
75
|
+
return nextConfig;
|
|
76
|
+
}
|
|
77
|
+
function createMetricBuilder(config = {}) {
|
|
78
|
+
const nextConfig = sanitizeMetricConfig(config);
|
|
79
|
+
return {
|
|
80
|
+
count() {
|
|
81
|
+
return createMetricBuilder({
|
|
82
|
+
...nextConfig,
|
|
83
|
+
allowed: [...nextConfig.allowed ?? [], { kind: "count" }]
|
|
84
|
+
});
|
|
85
|
+
},
|
|
86
|
+
aggregate(columnId, firstAggregate, ...restAggregates) {
|
|
87
|
+
const aggregates = [firstAggregate, ...restAggregates];
|
|
88
|
+
const selection = restAggregates.length === 0 ? firstAggregate : aggregates;
|
|
89
|
+
return createMetricBuilder({
|
|
90
|
+
...nextConfig,
|
|
91
|
+
allowed: [...nextConfig.allowed ?? [], {
|
|
92
|
+
kind: "aggregate",
|
|
93
|
+
columnId,
|
|
94
|
+
aggregate: selection
|
|
95
|
+
}]
|
|
96
|
+
});
|
|
97
|
+
},
|
|
98
|
+
hideCount() {
|
|
99
|
+
return createMetricBuilder({
|
|
100
|
+
...nextConfig,
|
|
101
|
+
hidden: [...nextConfig.hidden ?? [], { kind: "count" }]
|
|
102
|
+
});
|
|
103
|
+
},
|
|
104
|
+
hideAggregate(columnId, firstAggregate, ...restAggregates) {
|
|
105
|
+
const aggregates = [firstAggregate, ...restAggregates];
|
|
106
|
+
return createMetricBuilder({
|
|
107
|
+
...nextConfig,
|
|
108
|
+
hidden: [...nextConfig.hidden ?? [], ...aggregates.map((aggregate) => ({
|
|
109
|
+
kind: "aggregate",
|
|
110
|
+
columnId,
|
|
111
|
+
aggregate
|
|
112
|
+
}))]
|
|
113
|
+
});
|
|
114
|
+
},
|
|
115
|
+
defaultCount() {
|
|
116
|
+
return createMetricBuilder({
|
|
117
|
+
...nextConfig,
|
|
118
|
+
default: { kind: "count" }
|
|
119
|
+
});
|
|
120
|
+
},
|
|
121
|
+
defaultAggregate(columnId, aggregate) {
|
|
122
|
+
return createMetricBuilder({
|
|
123
|
+
...nextConfig,
|
|
124
|
+
default: {
|
|
125
|
+
kind: "aggregate",
|
|
126
|
+
columnId,
|
|
127
|
+
aggregate
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
},
|
|
131
|
+
[METRIC_CONTROL_CONFIG]: nextConfig
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
function getSelectableControlConfig(builder) {
|
|
135
|
+
return builder[SELECTABLE_CONTROL_CONFIG];
|
|
136
|
+
}
|
|
137
|
+
function getMetricBuilderConfig(builder) {
|
|
138
|
+
return builder[METRIC_CONTROL_CONFIG];
|
|
139
|
+
}
|
|
140
|
+
//#endregion
|
|
141
|
+
export { createMetricBuilder, createSelectableControlBuilder, getMetricBuilderConfig, getSelectableControlConfig };
|
|
@@ -58,6 +58,11 @@ declare const CHART_TYPE_CONFIG: {
|
|
|
58
58
|
readonly supportsGrouping: false;
|
|
59
59
|
readonly supportsTimeBucketing: false;
|
|
60
60
|
};
|
|
61
|
+
readonly table: {
|
|
62
|
+
readonly supportedXAxisTypes: readonly ["date", "category", "boolean"];
|
|
63
|
+
readonly supportsGrouping: true;
|
|
64
|
+
readonly supportsTimeBucketing: true;
|
|
65
|
+
};
|
|
61
66
|
};
|
|
62
67
|
//#endregion
|
|
63
68
|
export { CHART_TYPE_CONFIG, ChartAxisType, ChartTypeCapabilities };
|
|
@@ -55,6 +55,15 @@ const CHART_TYPE_CONFIG = {
|
|
|
55
55
|
supportedXAxisTypes: ["category", "boolean"],
|
|
56
56
|
supportsGrouping: false,
|
|
57
57
|
supportsTimeBucketing: false
|
|
58
|
+
},
|
|
59
|
+
table: {
|
|
60
|
+
supportedXAxisTypes: [
|
|
61
|
+
"date",
|
|
62
|
+
"category",
|
|
63
|
+
"boolean"
|
|
64
|
+
],
|
|
65
|
+
supportsGrouping: true,
|
|
66
|
+
supportsTimeBucketing: true
|
|
58
67
|
}
|
|
59
68
|
};
|
|
60
69
|
/**
|
|
@@ -77,7 +77,8 @@ const CHART_TYPE_ORDER = [
|
|
|
77
77
|
"area",
|
|
78
78
|
"percent-area",
|
|
79
79
|
"pie",
|
|
80
|
-
"donut"
|
|
80
|
+
"donut",
|
|
81
|
+
"table"
|
|
81
82
|
];
|
|
82
83
|
//#endregion
|
|
83
84
|
export { CHART_TYPE_ORDER, TIME_BUCKET_ORDER, resolveConfiguredIdSelection, resolveConfiguredValue, restrictConfiguredIdOptions, restrictConfiguredValues };
|