@matthieumordrel/chart-studio 0.2.3 → 0.2.5

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 (48) hide show
  1. package/README.md +42 -42
  2. package/dist/core/chart-capabilities.d.mts +15 -0
  3. package/dist/core/chart-capabilities.mjs +23 -0
  4. package/dist/core/colors.mjs +5 -5
  5. package/dist/core/config-utils.mjs +6 -2
  6. package/dist/core/date-range-presets.d.mts +12 -0
  7. package/dist/core/date-range-presets.mjs +152 -0
  8. package/dist/core/define-chart-schema.d.mts +26 -94
  9. package/dist/core/define-chart-schema.mjs +26 -34
  10. package/dist/core/infer-columns.d.mts +2 -2
  11. package/dist/core/infer-columns.mjs +4 -2
  12. package/dist/core/metric-utils.mjs +13 -5
  13. package/dist/core/pipeline-data-points.mjs +4 -1
  14. package/dist/core/schema-builder.mjs +335 -0
  15. package/dist/core/schema-builder.types.d.mts +279 -0
  16. package/dist/core/types.d.mts +61 -17
  17. package/dist/core/use-chart-options.d.mts +7 -4
  18. package/dist/core/use-chart.d.mts +4 -4
  19. package/dist/core/use-chart.mjs +70 -40
  20. package/dist/index.d.mts +2 -2
  21. package/dist/index.mjs +1 -1
  22. package/dist/ui/chart-canvas.d.mts +8 -4
  23. package/dist/ui/chart-canvas.mjs +347 -29
  24. package/dist/ui/chart-context.d.mts +11 -4
  25. package/dist/ui/chart-context.mjs +3 -0
  26. package/dist/ui/chart-date-range-badge.mjs +2 -2
  27. package/dist/ui/chart-date-range-panel.mjs +19 -101
  28. package/dist/ui/chart-date-range.mjs +3 -3
  29. package/dist/ui/chart-debug.d.mts +6 -2
  30. package/dist/ui/chart-debug.mjs +5 -1
  31. package/dist/ui/chart-group-by-selector.d.mts +3 -1
  32. package/dist/ui/chart-group-by-selector.mjs +4 -1
  33. package/dist/ui/chart-metric-selector.mjs +2 -2
  34. package/dist/ui/chart-select.mjs +9 -10
  35. package/dist/ui/chart-source-switcher.d.mts +3 -1
  36. package/dist/ui/chart-source-switcher.mjs +4 -2
  37. package/dist/ui/chart-time-bucket-selector.d.mts +3 -1
  38. package/dist/ui/chart-time-bucket-selector.mjs +4 -1
  39. package/dist/ui/chart-toolbar-overflow.mjs +48 -26
  40. package/dist/ui/chart-toolbar.d.mts +6 -2
  41. package/dist/ui/chart-toolbar.mjs +4 -0
  42. package/dist/ui/chart-type-selector.d.mts +7 -2
  43. package/dist/ui/chart-type-selector.mjs +155 -20
  44. package/dist/ui/chart-x-axis-selector.d.mts +3 -1
  45. package/dist/ui/chart-x-axis-selector.mjs +4 -1
  46. package/dist/ui/percent-stacked.mjs +36 -0
  47. package/dist/ui/theme.css +54 -49
  48. package/package.json +7 -6
package/README.md CHANGED
@@ -22,6 +22,10 @@ You get:
22
22
  - transformed chart data
23
23
  - filtering, grouping, metrics, and time bucketing logic
24
24
 
25
+ Requirements:
26
+
27
+ - `react` >= 18.2.0
28
+
25
29
  Install:
26
30
 
27
31
  ```bash
@@ -46,6 +50,12 @@ You get:
46
50
  - `<ChartCanvas>`
47
51
  - granular UI controls from `@matthieumordrel/chart-studio/ui`
48
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)
58
+
49
59
  Install:
50
60
 
51
61
  ```bash
@@ -88,7 +98,7 @@ export function JobsChart() {
88
98
  ## How It Works
89
99
 
90
100
  1. Pass your raw data to `useChart()`.
91
- 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.).
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.).
92
102
  3. Either render your own UI from the returned state, or use the components from `@matthieumordrel/chart-studio/ui`.
93
103
 
94
104
  ## Column Types
@@ -102,41 +112,39 @@ export function JobsChart() {
102
112
 
103
113
  ## Declarative Schema and Control Restrictions
104
114
 
105
- If you want to expose only a subset of groupings, metrics, chart types, or axes, use `defineChartSchema<Row>()()` with the control sections:
115
+ If you want to expose only a subset of groupings, metrics, chart types, or axes, use the fluent `defineChartSchema<Row>()` builder:
106
116
 
107
117
  ```tsx
108
118
  import { defineChartSchema, useChart } from '@matthieumordrel/chart-studio'
109
119
 
110
120
  type Row = { periodEnd: string; segment: string; revenue: number; netIncome: number }
111
121
 
112
- const schema = defineChartSchema<Row>()({
113
- columns: {
114
- periodEnd: { type: 'date', label: 'Period End' },
115
- segment: { type: 'category' },
116
- revenue: { type: 'number' },
117
- netIncome: { type: 'number' }
118
- },
119
- xAxis: { allowed: ['periodEnd'] },
120
- groupBy: { allowed: ['segment'] },
121
- metric: {
122
- allowed: [
123
- { kind: 'count' },
124
- { kind: 'aggregate', columnId: 'revenue', aggregate: ['sum', 'avg'] },
125
- { kind: 'aggregate', columnId: 'netIncome', aggregate: 'sum' }
126
- ]
127
- },
128
- chartType: { allowed: ['bar', 'line'] },
129
- timeBucket: { allowed: ['year', 'quarter', 'month'] }
130
- })
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'))
131
139
 
132
140
  const chart = useChart({ data, schema })
133
141
  ```
134
142
 
135
143
  Why this pattern:
136
144
 
137
- - `columns` defines types, labels, and formats for raw fields; use `false` to exclude a column from the chart
138
- - Derived columns use `{ kind: 'derived', type, label?, accessor, format? }` for computed values from each row
139
- - `xAxis`, `groupBy`, `metric`, `chartType`, `timeBucket` restrict the allowed options
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
140
148
  - invalid column IDs and config keys are rejected at compile time
141
149
  - metric restrictions preserve the order you declare, so the first allowed metric becomes the default
142
150
 
@@ -153,13 +161,12 @@ type Job = {
153
161
  salary: number
154
162
  }
155
163
 
156
- const jobSchema = defineChartSchema<Job>()({
157
- columns: {
158
- dateAdded: { type: 'date', label: 'Date Added' },
159
- ownerName: { type: 'category', label: 'Consultant' },
160
- salary: { type: 'number', label: 'Salary' }
161
- }
162
- })
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
+ ])
163
170
 
164
171
  export function JobsChartHeadless({ data }: { data: Job[] }) {
165
172
  const chart = useChart({ data, schema: jobSchema })
@@ -298,12 +305,6 @@ Minimal example:
298
305
  }
299
306
  ```
300
307
 
301
- ## Compatibility
302
-
303
- - `react`: `>=18.2.0 <20`
304
- - `recharts`: `>=3.0.0 <4` for the UI layer
305
- - `lucide-react`: `>=0.577.0 <1` for the UI layer
306
-
307
308
  ## Common Questions
308
309
 
309
310
  ### Which import path should I use?
@@ -332,9 +333,8 @@ const chart = useChart({
332
333
  id: 'jobs',
333
334
  label: 'Jobs',
334
335
  data: jobs,
335
- schema: defineChartSchema<Job>()({
336
- columns: { dateAdded: { type: 'date', label: 'Date Added' } }
337
- })
336
+ schema: defineChartSchema<Job>()
337
+ .columns((c) => [c.date('dateAdded', { label: 'Date Added' })])
338
338
  },
339
339
  { id: 'candidates', label: 'Candidates', data: candidates }
340
340
  ]
@@ -384,9 +384,9 @@ There is currently no built-in support for drill-down, click-to-filter, brush se
384
384
 
385
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
386
 
387
- ### The double-call schema syntax
387
+ ### Schema Builder Ergonomics
388
388
 
389
- `defineChartSchema<Row>()()` uses a double function call as a workaround for TypeScript's lack of partial type argument inference. This lets you provide the row type explicitly while the column IDs are inferred automatically. It works well but can surprise newcomers — this will be revisited if TypeScript adds native support for partial inference.
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.
390
390
 
391
391
  ## Release
392
392
 
@@ -23,6 +23,16 @@ declare const CHART_TYPE_CONFIG: {
23
23
  readonly supportsGrouping: true;
24
24
  readonly supportsTimeBucketing: true;
25
25
  };
26
+ readonly 'grouped-bar': {
27
+ readonly supportedXAxisTypes: readonly ["date", "category", "boolean"];
28
+ readonly supportsGrouping: true;
29
+ readonly supportsTimeBucketing: true;
30
+ };
31
+ readonly 'percent-bar': {
32
+ readonly supportedXAxisTypes: readonly ["date", "category", "boolean"];
33
+ readonly supportsGrouping: true;
34
+ readonly supportsTimeBucketing: true;
35
+ };
26
36
  readonly line: {
27
37
  readonly supportedXAxisTypes: readonly ["date"];
28
38
  readonly supportsGrouping: true;
@@ -33,6 +43,11 @@ declare const CHART_TYPE_CONFIG: {
33
43
  readonly supportsGrouping: true;
34
44
  readonly supportsTimeBucketing: true;
35
45
  };
46
+ readonly 'percent-area': {
47
+ readonly supportedXAxisTypes: readonly ["date"];
48
+ readonly supportsGrouping: true;
49
+ readonly supportsTimeBucketing: true;
50
+ };
36
51
  readonly pie: {
37
52
  readonly supportedXAxisTypes: readonly ["category", "boolean"];
38
53
  readonly supportsGrouping: false;
@@ -13,6 +13,24 @@ const CHART_TYPE_CONFIG = {
13
13
  supportsGrouping: true,
14
14
  supportsTimeBucketing: true
15
15
  },
16
+ "grouped-bar": {
17
+ supportedXAxisTypes: [
18
+ "date",
19
+ "category",
20
+ "boolean"
21
+ ],
22
+ supportsGrouping: true,
23
+ supportsTimeBucketing: true
24
+ },
25
+ "percent-bar": {
26
+ supportedXAxisTypes: [
27
+ "date",
28
+ "category",
29
+ "boolean"
30
+ ],
31
+ supportsGrouping: true,
32
+ supportsTimeBucketing: true
33
+ },
16
34
  line: {
17
35
  supportedXAxisTypes: ["date"],
18
36
  supportsGrouping: true,
@@ -23,6 +41,11 @@ const CHART_TYPE_CONFIG = {
23
41
  supportsGrouping: true,
24
42
  supportsTimeBucketing: true
25
43
  },
44
+ "percent-area": {
45
+ supportedXAxisTypes: ["date"],
46
+ supportsGrouping: true,
47
+ supportsTimeBucketing: true
48
+ },
26
49
  pie: {
27
50
  supportedXAxisTypes: ["category", "boolean"],
28
51
  supportsGrouping: false,
@@ -20,11 +20,11 @@ const FALLBACK_COLORS = [
20
20
  ];
21
21
  /** Shadcn chart CSS variables (5 colors) with safe fallbacks. */
22
22
  const SHADCN_CHART_COLORS = [
23
- `var(--chart-1, var(--cs-chart-1, hsl(245 72% 57%)))`,
24
- `var(--chart-2, var(--cs-chart-2, hsl(271 72% 55%)))`,
25
- `var(--chart-3, var(--cs-chart-3, hsl(330 68% 54%)))`,
26
- `var(--chart-4, var(--cs-chart-4, hsl(170 65% 38%)))`,
27
- `var(--chart-5, var(--cs-chart-5, hsl(30 90% 54%)))`
23
+ `var(--chart-1, var(--cs-chart-1, oklch(0.501 0.228 277.992)))`,
24
+ `var(--chart-2, var(--cs-chart-2, oklch(0.550 0.235 302.715)))`,
25
+ `var(--chart-3, var(--cs-chart-3, oklch(0.609 0.206 354.673)))`,
26
+ `var(--chart-4, var(--cs-chart-4, oklch(0.635 0.109 178.228)))`,
27
+ `var(--chart-5, var(--cs-chart-5, oklch(0.732 0.166 58.213)))`
28
28
  ];
29
29
  /**
30
30
  * Get a color for the Nth series.
@@ -50,9 +50,10 @@ function restrictConfiguredValues(values, config, fallbackToBaseIfEmpty = false)
50
50
  /**
51
51
  * Resolve one primitive selection against the current option list.
52
52
  */
53
- function resolveConfiguredValue(currentValue, values, configuredDefault) {
54
- if (values.includes(currentValue)) return currentValue;
53
+ function resolveConfiguredValue(currentValue, values, configuredDefault, globalDefault) {
54
+ if (currentValue !== null && values.includes(currentValue)) return currentValue;
55
55
  if (configuredDefault && values.includes(configuredDefault)) return configuredDefault;
56
+ if (globalDefault && values.includes(globalDefault)) return globalDefault;
56
57
  return values[0] ?? currentValue;
57
58
  }
58
59
  /**
@@ -70,8 +71,11 @@ const TIME_BUCKET_ORDER = [
70
71
  */
71
72
  const CHART_TYPE_ORDER = [
72
73
  "bar",
74
+ "grouped-bar",
75
+ "percent-bar",
73
76
  "line",
74
77
  "area",
78
+ "percent-area",
75
79
  "pie",
76
80
  "donut"
77
81
  ];
@@ -0,0 +1,12 @@
1
+ //#region src/core/date-range-presets.d.ts
2
+ /**
3
+ * All recognised date range preset identifiers.
4
+ *
5
+ * - `'auto'` — derived from the active time bucket
6
+ * - `'all-time'` — no date filtering
7
+ * - Relative presets — rolling window from "now"
8
+ * - Calendar presets — aligned to calendar boundaries
9
+ */
10
+ type DateRangePresetId = 'auto' | 'all-time' | 'last-7-days' | 'last-30-days' | 'last-3-months' | 'last-12-months' | 'quarter-to-date' | 'year-to-date' | 'last-year';
11
+ //#endregion
12
+ export { DateRangePresetId };
@@ -0,0 +1,152 @@
1
+ //#region src/core/date-range-presets.ts
2
+ /**
3
+ * Ordered list of all date range presets shown in the UI.
4
+ *
5
+ * Layout hint (2-column grid):
6
+ * Auto | All time
7
+ * Last 7 days | Last 30 days
8
+ * Last 3 months | Last 12 months
9
+ * Quarter to date | Year to date
10
+ * Last year |
11
+ */
12
+ const DATE_RANGE_PRESETS = [
13
+ {
14
+ id: "auto",
15
+ label: "Auto",
16
+ description: "Adjusts the date range based on the time bucket: day → last 30 days, week → last 3 months, month → last 12 months, quarter/year → all time",
17
+ buildFilter: () => null
18
+ },
19
+ {
20
+ id: "all-time",
21
+ label: "All time",
22
+ buildFilter: () => null
23
+ },
24
+ {
25
+ id: "last-7-days",
26
+ label: "Last 7 days",
27
+ buildFilter: () => ({
28
+ from: daysAgo(7),
29
+ to: null
30
+ })
31
+ },
32
+ {
33
+ id: "last-30-days",
34
+ label: "Last 30 days",
35
+ buildFilter: () => ({
36
+ from: daysAgo(30),
37
+ to: null
38
+ })
39
+ },
40
+ {
41
+ id: "last-3-months",
42
+ label: "Last 3 months",
43
+ buildFilter: () => ({
44
+ from: monthsAgo(3),
45
+ to: null
46
+ })
47
+ },
48
+ {
49
+ id: "last-12-months",
50
+ label: "Last 12 months",
51
+ buildFilter: () => ({
52
+ from: monthsAgo(12),
53
+ to: null
54
+ })
55
+ },
56
+ {
57
+ id: "quarter-to-date",
58
+ label: "Quarter to date",
59
+ buildFilter: () => ({
60
+ from: startOfQuarter(),
61
+ to: null
62
+ })
63
+ },
64
+ {
65
+ id: "year-to-date",
66
+ label: "Year to date",
67
+ buildFilter: () => ({
68
+ from: startOfYear(),
69
+ to: null
70
+ })
71
+ },
72
+ {
73
+ id: "last-year",
74
+ label: "Last year",
75
+ buildFilter: () => lastYear()
76
+ }
77
+ ];
78
+ /**
79
+ * Map a time bucket to a sensible default date range.
80
+ *
81
+ * - `day` → last 30 days (enough for a meaningful daily trend)
82
+ * - `week` → last 3 months (~13 weeks)
83
+ * - `month` → last 12 months
84
+ * - `quarter` → all time (null)
85
+ * - `year` → all time (null)
86
+ */
87
+ function autoFilterForBucket(bucket) {
88
+ switch (bucket) {
89
+ case "day": return {
90
+ from: daysAgo(30),
91
+ to: null
92
+ };
93
+ case "week": return {
94
+ from: monthsAgo(3),
95
+ to: null
96
+ };
97
+ case "month": return {
98
+ from: monthsAgo(12),
99
+ to: null
100
+ };
101
+ case "quarter":
102
+ case "year": return null;
103
+ }
104
+ }
105
+ /**
106
+ * Compute the effective `DateRangeFilter` for a given preset.
107
+ *
108
+ * For `'auto'`, this uses the provided `timeBucket` to derive the range.
109
+ * For all other presets, the filter is computed from the preset definition.
110
+ *
111
+ * @returns The resolved filter, or `null` for "all time".
112
+ */
113
+ function resolvePresetFilter(presetId, timeBucket) {
114
+ if (presetId === "auto") return autoFilterForBucket(timeBucket);
115
+ return DATE_RANGE_PRESETS.find((p) => p.id === presetId)?.buildFilter() ?? null;
116
+ }
117
+ /**
118
+ * Get the human-readable label for a preset ID.
119
+ */
120
+ function getPresetLabel(presetId) {
121
+ return DATE_RANGE_PRESETS.find((p) => p.id === presetId)?.label ?? "Custom";
122
+ }
123
+ function daysAgo(n) {
124
+ const d = /* @__PURE__ */ new Date();
125
+ d.setDate(d.getDate() - n);
126
+ d.setHours(0, 0, 0, 0);
127
+ return d;
128
+ }
129
+ function monthsAgo(n) {
130
+ const d = /* @__PURE__ */ new Date();
131
+ d.setMonth(d.getMonth() - n);
132
+ d.setHours(0, 0, 0, 0);
133
+ return d;
134
+ }
135
+ function startOfQuarter() {
136
+ const d = /* @__PURE__ */ new Date();
137
+ const quarterMonth = Math.floor(d.getMonth() / 3) * 3;
138
+ return new Date(d.getFullYear(), quarterMonth, 1);
139
+ }
140
+ function startOfYear() {
141
+ const d = /* @__PURE__ */ new Date();
142
+ return new Date(d.getFullYear(), 0, 1);
143
+ }
144
+ function lastYear() {
145
+ const year = (/* @__PURE__ */ new Date()).getFullYear() - 1;
146
+ return {
147
+ from: new Date(year, 0, 1),
148
+ to: new Date(year, 11, 31)
149
+ };
150
+ }
151
+ //#endregion
152
+ export { DATE_RANGE_PRESETS, getPresetLabel, resolvePresetFilter };
@@ -1,106 +1,38 @@
1
- import { ChartTypeConfig, DefinedChartSchema, ExactShape, FiltersConfig, GroupByConfig, MetricConfig, ResolvedFilterColumnIdFromSchema, ResolvedGroupByColumnIdFromSchema, ResolvedMetricColumnIdFromSchema, ResolvedXAxisColumnIdFromSchema, SchemaColumnsValidationShape, TimeBucketConfig, XAxisConfig } from "./types.mjs";
1
+ import { ChartSchemaBuilder } from "./schema-builder.types.mjs";
2
2
 
3
3
  //#region src/core/define-chart-schema.d.ts
4
- type SchemaFromSections<T, TColumns extends Record<string, unknown> | undefined, TXAxis, TGroupBy, TFilters, TMetric, TChartType, TTimeBucket> = {
5
- columns?: Extract<TColumns, Record<string, unknown> | undefined>;
6
- xAxis?: Extract<TXAxis, XAxisConfig<ResolvedXAxisColumnIdFromSchema<T, {
7
- columns?: TColumns;
8
- }>> | undefined>;
9
- groupBy?: Extract<TGroupBy, GroupByConfig<ResolvedGroupByColumnIdFromSchema<T, {
10
- columns?: TColumns;
11
- }>> | undefined>;
12
- filters?: Extract<TFilters, FiltersConfig<ResolvedFilterColumnIdFromSchema<T, {
13
- columns?: TColumns;
14
- }>> | undefined>;
15
- metric?: Extract<TMetric, MetricConfig<ResolvedMetricColumnIdFromSchema<T, {
16
- columns?: TColumns;
17
- }>> | undefined>;
18
- chartType?: Extract<TChartType, ChartTypeConfig | undefined>;
19
- timeBucket?: Extract<TTimeBucket, TimeBucketConfig | undefined>;
20
- };
21
- type DefineChartSchemaInput<T, TColumns extends Record<string, unknown> | undefined, TXAxis, TGroupBy, TFilters, TMetric, TChartType, TTimeBucket> = {
22
- /**
23
- * Shape the available chart columns.
24
- *
25
- * This is usually the most important part of the schema. Use it to:
26
- * - rename inferred raw fields with `label`
27
- * - force a field to a specific `type`
28
- * - apply `format` or `formatter`
29
- * - remove a raw field with `false`
30
- * - add brand new derived columns with `kind: 'derived'`
31
- */
32
- columns?: TColumns & ExactShape<SchemaColumnsValidationShape<T, NoInfer<TColumns>>, NoInfer<TColumns>>;
33
- /**
34
- * Restrict which resolved columns may be selected on the X-axis.
35
- *
36
- * Use this when you want to expose only a subset of possible X-axis fields.
37
- */
38
- xAxis?: TXAxis & ExactShape<XAxisConfig<ResolvedXAxisColumnIdFromSchema<T, {
39
- columns?: TColumns;
40
- }>>, NoInfer<TXAxis>>;
41
- /**
42
- * Restrict which resolved columns may be used to split the chart into series.
43
- *
44
- * This powers grouped / multi-series charts.
45
- */
46
- groupBy?: TGroupBy & ExactShape<GroupByConfig<ResolvedGroupByColumnIdFromSchema<T, {
47
- columns?: TColumns;
48
- }>>, NoInfer<TGroupBy>>;
49
- /**
50
- * Restrict which resolved columns appear in the filters UI.
51
- *
52
- * Only category and boolean-like columns are eligible here.
53
- */
54
- filters?: TFilters & ExactShape<FiltersConfig<ResolvedFilterColumnIdFromSchema<T, {
55
- columns?: TColumns;
56
- }>>, NoInfer<TFilters>>;
57
- /**
58
- * Restrict which metrics and aggregate combinations remain selectable.
59
- *
60
- * Use this when you want to curate the metric dropdown rather than exposing
61
- * every available numeric aggregate.
62
- */
63
- metric?: TMetric & ExactShape<MetricConfig<ResolvedMetricColumnIdFromSchema<T, {
64
- columns?: TColumns;
65
- }>>, NoInfer<TMetric>>; /** Restrict which chart renderers are available to the user. */
66
- chartType?: TChartType & ExactShape<ChartTypeConfig, NoInfer<TChartType>>;
67
- /**
68
- * Restrict which time buckets remain available for date X-axes.
69
- *
70
- * Example: allow only `'month'` and `'quarter'`.
71
- */
72
- timeBucket?: TTimeBucket & ExactShape<TimeBucketConfig, NoInfer<TTimeBucket>>;
73
- };
74
4
  /**
75
- * Define one explicit chart schema with strict exact-object checking.
5
+ * Define one explicit chart schema through a fluent builder API.
76
6
  *
77
- * The schema is the single advanced authoring surface for chart-studio:
78
- * `columns` can override or exclude inferred raw fields and also define derived
79
- * columns, while the top-level control sections restrict the public chart
80
- * contract.
7
+ * Put `.columns(...)` early in the chain so later sections can narrow against
8
+ * the declared column ids and roles.
81
9
  *
82
10
  * Typical shape:
83
11
  *
84
12
  * ```ts
85
- * const schema = defineChartSchema<Row>()({
86
- * columns: {
87
- * createdAt: {type: 'date', label: 'Created'},
88
- * revenue: {type: 'number', format: 'currency'},
89
- * margin: {
90
- * kind: 'derived',
91
- * type: 'number',
92
- * label: 'Margin',
93
- * format: 'percent',
94
- * accessor: row => row.profit / row.revenue,
95
- * },
96
- * },
97
- * xAxis: {allowed: ['createdAt']},
98
- * metric: {
99
- * allowed: [{kind: 'aggregate', columnId: 'revenue', aggregate: 'sum'}],
100
- * },
101
- * })
13
+ * const schema = defineChartSchema<Row>()
14
+ * .columns((c) => [
15
+ * c.date('createdAt', {label: 'Created'}),
16
+ * c.category('ownerName', {label: 'Owner'}),
17
+ * c.number('salary', {format: 'currency'}),
18
+ * c.exclude('internalId'),
19
+ * c.derived.category('salaryBand', {
20
+ * label: 'Salary Band',
21
+ * accessor: row => row.salary != null && row.salary > 100_000 ? 'High' : 'Base',
22
+ * }),
23
+ * ])
24
+ * .xAxis((x) => x.allowed('createdAt').default('createdAt'))
25
+ * .groupBy((g) => g.allowed('ownerName', 'salaryBand'))
26
+ * .metric((m) =>
27
+ * m
28
+ * .count()
29
+ * .aggregate('salary', 'sum', 'avg')
30
+ * .defaultAggregate('salary', 'sum')
31
+ * )
32
+ *
33
+ * // Pass the builder directly to useChart(...) or inferColumnsFromData(...).
102
34
  * ```
103
35
  */
104
- declare function defineChartSchema<T>(): <const TColumns extends Record<string, unknown> | undefined = undefined, const TXAxis = undefined, const TGroupBy = undefined, const TFilters = undefined, const TMetric = undefined, const TChartType = undefined, const TTimeBucket = undefined>(schema: DefineChartSchemaInput<T, TColumns, TXAxis, TGroupBy, TFilters, TMetric, TChartType, TTimeBucket>) => DefinedChartSchema<T, SchemaFromSections<T, TColumns, TXAxis, TGroupBy, TFilters, TMetric, TChartType, TTimeBucket>>;
36
+ declare function defineChartSchema<TRow>(): ChartSchemaBuilder<TRow, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined>;
105
37
  //#endregion
106
38
  export { defineChartSchema };
@@ -1,47 +1,39 @@
1
+ import { createChartSchemaBuilder } from "./schema-builder.mjs";
1
2
  //#region src/core/define-chart-schema.ts
2
3
  /**
3
- * Define one explicit chart schema with strict exact-object checking.
4
+ * Define one explicit chart schema through a fluent builder API.
4
5
  *
5
- * The schema is the single advanced authoring surface for chart-studio:
6
- * `columns` can override or exclude inferred raw fields and also define derived
7
- * columns, while the top-level control sections restrict the public chart
8
- * contract.
6
+ * Put `.columns(...)` early in the chain so later sections can narrow against
7
+ * the declared column ids and roles.
9
8
  *
10
9
  * Typical shape:
11
10
  *
12
11
  * ```ts
13
- * const schema = defineChartSchema<Row>()({
14
- * columns: {
15
- * createdAt: {type: 'date', label: 'Created'},
16
- * revenue: {type: 'number', format: 'currency'},
17
- * margin: {
18
- * kind: 'derived',
19
- * type: 'number',
20
- * label: 'Margin',
21
- * format: 'percent',
22
- * accessor: row => row.profit / row.revenue,
23
- * },
24
- * },
25
- * xAxis: {allowed: ['createdAt']},
26
- * metric: {
27
- * allowed: [{kind: 'aggregate', columnId: 'revenue', aggregate: 'sum'}],
28
- * },
29
- * })
12
+ * const schema = defineChartSchema<Row>()
13
+ * .columns((c) => [
14
+ * c.date('createdAt', {label: 'Created'}),
15
+ * c.category('ownerName', {label: 'Owner'}),
16
+ * c.number('salary', {format: 'currency'}),
17
+ * c.exclude('internalId'),
18
+ * c.derived.category('salaryBand', {
19
+ * label: 'Salary Band',
20
+ * accessor: row => row.salary != null && row.salary > 100_000 ? 'High' : 'Base',
21
+ * }),
22
+ * ])
23
+ * .xAxis((x) => x.allowed('createdAt').default('createdAt'))
24
+ * .groupBy((g) => g.allowed('ownerName', 'salaryBand'))
25
+ * .metric((m) =>
26
+ * m
27
+ * .count()
28
+ * .aggregate('salary', 'sum', 'avg')
29
+ * .defaultAggregate('salary', 'sum')
30
+ * )
31
+ *
32
+ * // Pass the builder directly to useChart(...) or inferColumnsFromData(...).
30
33
  * ```
31
34
  */
32
35
  function defineChartSchema() {
33
- /**
34
- * Brand one schema object while preserving its literal types.
35
- *
36
- * This is what lets the schema stay both strongly typed and editor-friendly
37
- * when it is later passed to `useChart(...)`.
38
- */
39
- return function defineSchema(schema) {
40
- return {
41
- ...schema,
42
- __chartSchemaBrand: "chart-schema-definition"
43
- };
44
- };
36
+ return createChartSchemaBuilder();
45
37
  }
46
38
  //#endregion
47
39
  export { defineChartSchema };
@@ -1,9 +1,9 @@
1
- import { ChartColumn, ChartSchema, ResolvedColumnIdFromSchema } from "./types.mjs";
1
+ import { ChartColumn, ChartSchemaDefinition, ResolvedChartSchemaFromDefinition, ResolvedColumnIdFromSchema } from "./types.mjs";
2
2
 
3
3
  //#region src/core/infer-columns.d.ts
4
4
  /**
5
5
  * Resolve chart columns directly from raw data and an optional explicit schema.
6
6
  */
7
- declare function inferColumnsFromData<T, const TSchema extends ChartSchema<T, any> | undefined = undefined>(data: readonly T[], schema?: TSchema): readonly ChartColumn<T, ResolvedColumnIdFromSchema<T, TSchema>>[];
7
+ declare function inferColumnsFromData<T, const TSchema extends ChartSchemaDefinition<T, any> | undefined = undefined>(data: readonly T[], schema?: TSchema): readonly ChartColumn<T, ResolvedColumnIdFromSchema<T, ResolvedChartSchemaFromDefinition<TSchema>>>[];
8
8
  //#endregion
9
9
  export { inferColumnsFromData };