@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.
Files changed (94) hide show
  1. package/README.md +10 -378
  2. package/dist/_internal.d.mts +9 -0
  3. package/dist/_internal.mjs +9 -0
  4. package/dist/core/chart-builder-controls.mjs +141 -0
  5. package/dist/core/chart-capabilities.d.mts +5 -0
  6. package/dist/core/chart-capabilities.mjs +9 -0
  7. package/dist/core/config-utils.mjs +2 -1
  8. package/dist/core/dashboard.types.d.mts +220 -0
  9. package/dist/core/data-label-defaults.d.mts +92 -0
  10. package/dist/core/data-label-defaults.mjs +78 -0
  11. package/dist/core/data-model.types.d.mts +196 -0
  12. package/dist/core/dataset-builder.types.d.mts +51 -0
  13. package/dist/core/dataset-chart-metadata.d.mts +8 -0
  14. package/dist/core/dataset-chart-metadata.mjs +4 -0
  15. package/dist/core/date-range-presets.d.mts +43 -1
  16. package/dist/core/date-range-presets.mjs +2 -2
  17. package/dist/core/date-utils.d.mts +26 -0
  18. package/dist/core/define-dashboard.d.mts +8 -0
  19. package/dist/core/define-dashboard.mjs +156 -0
  20. package/dist/core/define-data-model.d.mts +11 -0
  21. package/dist/core/define-data-model.mjs +327 -0
  22. package/dist/core/define-dataset.d.mts +13 -0
  23. package/dist/core/define-dataset.mjs +111 -0
  24. package/dist/core/formatting.d.mts +49 -0
  25. package/dist/core/formatting.mjs +32 -10
  26. package/dist/core/index.d.mts +19 -0
  27. package/dist/core/infer-columns.mjs +28 -2
  28. package/dist/core/materialized-view.mjs +580 -0
  29. package/dist/core/materialized-view.types.d.mts +223 -0
  30. package/dist/core/metric-utils.d.mts +18 -2
  31. package/dist/core/metric-utils.mjs +1 -1
  32. package/dist/core/model-chart.mjs +242 -0
  33. package/dist/core/model-chart.types.d.mts +199 -0
  34. package/dist/core/model-inference.mjs +169 -0
  35. package/dist/core/model-inference.types.d.mts +71 -0
  36. package/dist/core/pipeline.mjs +32 -1
  37. package/dist/core/schema-builder.mjs +28 -158
  38. package/dist/core/schema-builder.types.d.mts +2 -49
  39. package/dist/core/types.d.mts +61 -10
  40. package/dist/core/use-chart-options.d.mts +35 -8
  41. package/dist/core/use-chart-resolvers.mjs +13 -3
  42. package/dist/core/use-chart.d.mts +16 -12
  43. package/dist/core/use-chart.mjs +137 -35
  44. package/dist/core/use-dashboard.d.mts +190 -0
  45. package/dist/core/use-dashboard.mjs +551 -0
  46. package/dist/index.d.mts +14 -4
  47. package/dist/index.mjs +8 -2
  48. package/package.json +10 -41
  49. package/LICENSE +0 -21
  50. package/dist/core/define-chart-schema.d.mts +0 -38
  51. package/dist/core/define-chart-schema.mjs +0 -39
  52. package/dist/ui/chart-axis-ticks.mjs +0 -65
  53. package/dist/ui/chart-canvas.d.mts +0 -33
  54. package/dist/ui/chart-canvas.mjs +0 -779
  55. package/dist/ui/chart-context.d.mts +0 -99
  56. package/dist/ui/chart-context.mjs +0 -115
  57. package/dist/ui/chart-date-range-badge.d.mts +0 -20
  58. package/dist/ui/chart-date-range-badge.mjs +0 -49
  59. package/dist/ui/chart-date-range-panel.d.mts +0 -18
  60. package/dist/ui/chart-date-range-panel.mjs +0 -126
  61. package/dist/ui/chart-date-range.d.mts +0 -20
  62. package/dist/ui/chart-date-range.mjs +0 -67
  63. package/dist/ui/chart-debug.d.mts +0 -21
  64. package/dist/ui/chart-debug.mjs +0 -173
  65. package/dist/ui/chart-dropdown.mjs +0 -92
  66. package/dist/ui/chart-filters-panel.d.mts +0 -26
  67. package/dist/ui/chart-filters-panel.mjs +0 -132
  68. package/dist/ui/chart-filters.d.mts +0 -18
  69. package/dist/ui/chart-filters.mjs +0 -48
  70. package/dist/ui/chart-group-by-selector.d.mts +0 -16
  71. package/dist/ui/chart-group-by-selector.mjs +0 -32
  72. package/dist/ui/chart-metric-panel.d.mts +0 -25
  73. package/dist/ui/chart-metric-panel.mjs +0 -172
  74. package/dist/ui/chart-metric-selector.d.mts +0 -16
  75. package/dist/ui/chart-metric-selector.mjs +0 -50
  76. package/dist/ui/chart-select.mjs +0 -61
  77. package/dist/ui/chart-source-switcher.d.mts +0 -24
  78. package/dist/ui/chart-source-switcher.mjs +0 -56
  79. package/dist/ui/chart-time-bucket-selector.d.mts +0 -17
  80. package/dist/ui/chart-time-bucket-selector.mjs +0 -37
  81. package/dist/ui/chart-toolbar-overflow.d.mts +0 -28
  82. package/dist/ui/chart-toolbar-overflow.mjs +0 -231
  83. package/dist/ui/chart-toolbar.d.mts +0 -33
  84. package/dist/ui/chart-toolbar.mjs +0 -60
  85. package/dist/ui/chart-type-selector.d.mts +0 -19
  86. package/dist/ui/chart-type-selector.mjs +0 -168
  87. package/dist/ui/chart-x-axis-selector.d.mts +0 -16
  88. package/dist/ui/chart-x-axis-selector.mjs +0 -28
  89. package/dist/ui/index.d.mts +0 -19
  90. package/dist/ui/index.mjs +0 -18
  91. package/dist/ui/percent-stacked.mjs +0 -36
  92. package/dist/ui/theme.css +0 -67
  93. package/dist/ui/toolbar-types.d.mts +0 -7
  94. 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
- Composable charting for React with two adoption paths:
3
+ > Early alpha. Active work in progress. Not recommended for production use yet.
4
4
 
5
- - use the **headless core** if you want chart state, filtering, grouping, and transformed data
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
- Package: `@matthieumordrel/chart-studio`
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 recharts lucide-react
12
+ bun add @matthieumordrel/chart-studio@alpha react
63
13
  ```
64
14
 
65
- Then import the package theme once in your app stylesheet:
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 } from '@matthieumordrel/chart-studio'
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 are importing the package source directly in a local playground or monorepo, make sure Tailwind is scanning those source files too.
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
- ## Release
25
+ Special thanks to the teams behind TanStack Table and Recharts.
392
26
 
393
- - `bun run release:check`
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 };