@matthieumordrel/chart-studio 0.2.5 → 0.4.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.
Files changed (48) hide show
  1. package/README.md +402 -12
  2. package/dist/core/chart-builder-controls.mjs +141 -0
  3. package/dist/core/dashboard.types.d.mts +220 -0
  4. package/dist/core/data-label-defaults.mjs +74 -0
  5. package/dist/core/data-model.types.d.mts +196 -0
  6. package/dist/core/dataset-builder.types.d.mts +51 -0
  7. package/dist/core/dataset-chart-metadata.d.mts +8 -0
  8. package/dist/core/dataset-chart-metadata.mjs +4 -0
  9. package/dist/core/date-range-presets.mjs +1 -1
  10. package/dist/core/define-dashboard.d.mts +8 -0
  11. package/dist/core/define-dashboard.mjs +156 -0
  12. package/dist/core/define-data-model.d.mts +11 -0
  13. package/dist/core/define-data-model.mjs +327 -0
  14. package/dist/core/define-dataset.d.mts +13 -0
  15. package/dist/core/define-dataset.mjs +111 -0
  16. package/dist/core/index.d.mts +17 -0
  17. package/dist/core/infer-columns.mjs +28 -2
  18. package/dist/core/materialized-view.mjs +580 -0
  19. package/dist/core/materialized-view.types.d.mts +223 -0
  20. package/dist/core/model-chart.mjs +242 -0
  21. package/dist/core/model-chart.types.d.mts +199 -0
  22. package/dist/core/model-inference.mjs +169 -0
  23. package/dist/core/model-inference.types.d.mts +71 -0
  24. package/dist/core/pipeline.mjs +32 -1
  25. package/dist/core/schema-builder.mjs +28 -158
  26. package/dist/core/schema-builder.types.d.mts +2 -49
  27. package/dist/core/types.d.mts +59 -8
  28. package/dist/core/use-chart-options.d.mts +35 -8
  29. package/dist/core/use-chart-resolvers.mjs +13 -3
  30. package/dist/core/use-chart.d.mts +16 -12
  31. package/dist/core/use-chart.mjs +136 -34
  32. package/dist/core/use-dashboard.d.mts +190 -0
  33. package/dist/core/use-dashboard.mjs +551 -0
  34. package/dist/index.d.mts +10 -3
  35. package/dist/index.mjs +5 -2
  36. package/dist/ui/chart-canvas.d.mts +11 -4
  37. package/dist/ui/chart-canvas.mjs +45 -34
  38. package/dist/ui/chart-context.d.mts +2 -0
  39. package/dist/ui/chart-context.mjs +2 -0
  40. package/dist/ui/chart-filters-panel.d.mts +1 -1
  41. package/dist/ui/chart-filters-panel.mjs +163 -37
  42. package/dist/ui/chart-group-by-selector.mjs +4 -4
  43. package/dist/ui/chart-time-bucket-selector.mjs +1 -1
  44. package/dist/ui/chart-toolbar-overflow.mjs +5 -13
  45. package/dist/ui/chart-toolbar.mjs +1 -1
  46. package/package.json +1 -1
  47. package/dist/core/define-chart-schema.d.mts +0 -38
  48. package/dist/core/define-chart-schema.mjs +0 -39
@@ -1,7 +1,6 @@
1
- import { isSameMetric, normalizeMetricAllowances } from "./metric-utils.mjs";
1
+ import { createMetricBuilder, createSelectableControlBuilder, getMetricBuilderConfig, getSelectableControlConfig } from "./chart-builder-controls.mjs";
2
+ import { DATASET_CHART_METADATA } from "./dataset-chart-metadata.mjs";
2
3
  //#region src/core/schema-builder.ts
3
- const SELECTABLE_CONTROL_CONFIG = Symbol("chart-schema-selectable-control-config");
4
- const METRIC_CONTROL_CONFIG = Symbol("chart-schema-metric-config");
5
4
  const COLUMN_HELPER = {
6
5
  field(id, options = {}) {
7
6
  return {
@@ -104,141 +103,6 @@ const COLUMN_HELPER = {
104
103
  }
105
104
  }
106
105
  };
107
- function uniqueValues(values) {
108
- if (!values || values.length === 0) return;
109
- return [...new Set(values)];
110
- }
111
- function sanitizeSelectableControlConfig(config, supportsDefault) {
112
- const allowed = uniqueValues(config.allowed);
113
- let hidden = uniqueValues(config.hidden);
114
- if (allowed && hidden) {
115
- const allowedSet = new Set(allowed);
116
- hidden = hidden.filter((option) => allowedSet.has(option));
117
- }
118
- let nextDefault = supportsDefault ? config.default : void 0;
119
- if (nextDefault !== void 0) {
120
- if (allowed && !allowed.includes(nextDefault)) nextDefault = void 0;
121
- if (nextDefault !== void 0 && hidden?.includes(nextDefault)) nextDefault = void 0;
122
- }
123
- const nextConfig = {};
124
- if (allowed && allowed.length > 0) nextConfig.allowed = allowed;
125
- if (hidden && hidden.length > 0) nextConfig.hidden = hidden;
126
- if (nextDefault !== void 0) nextConfig.default = nextDefault;
127
- return nextConfig;
128
- }
129
- function createSelectableControlBuilder(config = {}, supportsDefault) {
130
- const nextConfig = sanitizeSelectableControlConfig(config, supportsDefault);
131
- return {
132
- allowed(...options) {
133
- return createSelectableControlBuilder({
134
- ...nextConfig,
135
- allowed: options
136
- }, supportsDefault);
137
- },
138
- hidden(...options) {
139
- return createSelectableControlBuilder({
140
- ...nextConfig,
141
- hidden: [...nextConfig.hidden ?? [], ...options]
142
- }, supportsDefault);
143
- },
144
- default(option) {
145
- return createSelectableControlBuilder({
146
- ...nextConfig,
147
- default: option
148
- }, supportsDefault);
149
- },
150
- [SELECTABLE_CONTROL_CONFIG]: nextConfig
151
- };
152
- }
153
- function uniqueMetrics(metrics) {
154
- if (!metrics || metrics.length === 0) return;
155
- const unique = [];
156
- for (const metric of metrics) if (!unique.some((candidate) => isSameMetric(candidate, metric))) unique.push(metric);
157
- return unique;
158
- }
159
- function sanitizeMetricConfig(config) {
160
- const allowed = config.allowed && config.allowed.length > 0 ? [...config.allowed] : void 0;
161
- let hidden = uniqueMetrics(config.hidden);
162
- const expandedAllowed = normalizeMetricAllowances(allowed);
163
- if (expandedAllowed && hidden) hidden = hidden.filter((metric) => expandedAllowed.some((allowedMetric) => isSameMetric(allowedMetric, metric)));
164
- let nextDefault = config.default;
165
- if (nextDefault) {
166
- const defaultMetric = nextDefault;
167
- if (expandedAllowed && !expandedAllowed.some((metric) => isSameMetric(metric, defaultMetric))) nextDefault = void 0;
168
- if (nextDefault) {
169
- const visibleDefault = nextDefault;
170
- if (hidden?.some((metric) => isSameMetric(metric, visibleDefault))) nextDefault = void 0;
171
- }
172
- }
173
- const nextConfig = {};
174
- if (allowed && allowed.length > 0) nextConfig.allowed = allowed;
175
- if (hidden && hidden.length > 0) nextConfig.hidden = hidden;
176
- if (nextDefault) nextConfig.default = nextDefault;
177
- return nextConfig;
178
- }
179
- function createMetricBuilder(config = {}) {
180
- const nextConfig = sanitizeMetricConfig(config);
181
- return {
182
- count() {
183
- return createMetricBuilder({
184
- ...nextConfig,
185
- allowed: [...nextConfig.allowed ?? [], { kind: "count" }]
186
- });
187
- },
188
- aggregate(columnId, firstAggregate, ...restAggregates) {
189
- const aggregates = [firstAggregate, ...restAggregates];
190
- const selection = restAggregates.length === 0 ? firstAggregate : aggregates;
191
- return createMetricBuilder({
192
- ...nextConfig,
193
- allowed: [...nextConfig.allowed ?? [], {
194
- kind: "aggregate",
195
- columnId,
196
- aggregate: selection
197
- }]
198
- });
199
- },
200
- hideCount() {
201
- return createMetricBuilder({
202
- ...nextConfig,
203
- hidden: [...nextConfig.hidden ?? [], { kind: "count" }]
204
- });
205
- },
206
- hideAggregate(columnId, firstAggregate, ...restAggregates) {
207
- const aggregates = [firstAggregate, ...restAggregates];
208
- return createMetricBuilder({
209
- ...nextConfig,
210
- hidden: [...nextConfig.hidden ?? [], ...aggregates.map((aggregate) => ({
211
- kind: "aggregate",
212
- columnId,
213
- aggregate
214
- }))]
215
- });
216
- },
217
- defaultCount() {
218
- return createMetricBuilder({
219
- ...nextConfig,
220
- default: { kind: "count" }
221
- });
222
- },
223
- defaultAggregate(columnId, aggregate) {
224
- return createMetricBuilder({
225
- ...nextConfig,
226
- default: {
227
- kind: "aggregate",
228
- columnId,
229
- aggregate
230
- }
231
- });
232
- },
233
- [METRIC_CONTROL_CONFIG]: nextConfig
234
- };
235
- }
236
- function getSelectableControlConfig(builder) {
237
- return builder[SELECTABLE_CONTROL_CONFIG];
238
- }
239
- function getMetricBuilderConfig(builder) {
240
- return builder[METRIC_CONTROL_CONFIG];
241
- }
242
106
  function buildColumnsMap(entries) {
243
107
  const columns = {};
244
108
  for (const entry of entries) {
@@ -247,69 +111,62 @@ function buildColumnsMap(entries) {
247
111
  }
248
112
  return columns;
249
113
  }
250
- function assertColumnEntries(entries) {
251
- if (!Array.isArray(entries)) throw new TypeError("defineChartSchema().columns(...) must return an array of column entries.");
114
+ function assertColumnEntries(entries, owner = "defineDataset") {
115
+ if (!Array.isArray(entries)) throw new TypeError(`${owner}().columns(...) must return an array of column entries.`);
252
116
  }
253
117
  function resolveChartSchemaDefinition(schema) {
254
118
  if (!schema) return;
255
119
  if (typeof schema === "object" && "build" in schema && typeof schema.build === "function") return schema.build();
256
120
  return schema;
257
121
  }
258
- function createChartSchemaBuilder(state = {}) {
122
+ function createDatasetChartBuilder(state = {}, metadata) {
259
123
  let cachedSchema;
260
- return {
261
- columns(defineColumns) {
262
- const entries = defineColumns(COLUMN_HELPER);
263
- assertColumnEntries(entries);
264
- return createChartSchemaBuilder({
265
- ...state,
266
- columns: buildColumnsMap(entries)
267
- });
268
- },
124
+ const createNext = (nextState) => createDatasetChartBuilder(nextState, metadata);
125
+ const builder = {
269
126
  xAxis(defineXAxis) {
270
127
  const builder = defineXAxis(createSelectableControlBuilder({}, true));
271
- return createChartSchemaBuilder({
128
+ return createNext({
272
129
  ...state,
273
130
  xAxis: getSelectableControlConfig(builder)
274
131
  });
275
132
  },
276
133
  groupBy(defineGroupBy) {
277
134
  const builder = defineGroupBy(createSelectableControlBuilder({}, true));
278
- return createChartSchemaBuilder({
135
+ return createNext({
279
136
  ...state,
280
137
  groupBy: getSelectableControlConfig(builder)
281
138
  });
282
139
  },
283
140
  filters(defineFilters) {
284
141
  const builder = defineFilters(createSelectableControlBuilder({}, false));
285
- return createChartSchemaBuilder({
142
+ return createNext({
286
143
  ...state,
287
144
  filters: getSelectableControlConfig(builder)
288
145
  });
289
146
  },
290
147
  metric(defineMetric) {
291
148
  const builder = defineMetric(createMetricBuilder());
292
- return createChartSchemaBuilder({
149
+ return createNext({
293
150
  ...state,
294
151
  metric: getMetricBuilderConfig(builder)
295
152
  });
296
153
  },
297
154
  chartType(defineChartType) {
298
155
  const builder = defineChartType(createSelectableControlBuilder({}, true));
299
- return createChartSchemaBuilder({
156
+ return createNext({
300
157
  ...state,
301
158
  chartType: getSelectableControlConfig(builder)
302
159
  });
303
160
  },
304
161
  timeBucket(defineTimeBucket) {
305
162
  const builder = defineTimeBucket(createSelectableControlBuilder({}, true));
306
- return createChartSchemaBuilder({
163
+ return createNext({
307
164
  ...state,
308
165
  timeBucket: getSelectableControlConfig(builder)
309
166
  });
310
167
  },
311
168
  connectNulls(value) {
312
- return createChartSchemaBuilder({
169
+ return createNext({
313
170
  ...state,
314
171
  connectNulls: value
315
172
  });
@@ -327,9 +184,22 @@ function createChartSchemaBuilder(state = {}) {
327
184
  ...state.connectNulls !== void 0 ? { connectNulls: state.connectNulls } : {},
328
185
  __chartSchemaBrand: "chart-schema-definition"
329
186
  };
187
+ if (metadata) Object.defineProperty(cachedSchema, DATASET_CHART_METADATA, {
188
+ value: metadata,
189
+ enumerable: false,
190
+ configurable: false,
191
+ writable: false
192
+ });
330
193
  return cachedSchema;
331
194
  }
332
195
  };
196
+ if (metadata) Object.defineProperty(builder, DATASET_CHART_METADATA, {
197
+ value: metadata,
198
+ enumerable: false,
199
+ configurable: false,
200
+ writable: false
201
+ });
202
+ return builder;
333
203
  }
334
204
  //#endregion
335
- export { createChartSchemaBuilder, resolveChartSchemaDefinition };
205
+ export { COLUMN_HELPER, assertColumnEntries, buildColumnsMap, createDatasetChartBuilder, resolveChartSchemaDefinition };
@@ -1,4 +1,4 @@
1
- import { BaseColumnHint, ChartType, ChartTypeConfig, CountMetric, DefinedChartSchema, DerivedBooleanColumnSchema, DerivedCategoryColumnSchema, DerivedDateColumnSchema, DerivedNumberColumnSchema, FiltersConfig, GroupByConfig, InferableFieldKey, Metric, MetricConfig, NumericAggregateFunction, RawColumnSchemaFor, ResolvedFilterColumnIdFromSchema, ResolvedGroupByColumnIdFromSchema, ResolvedMetricColumnIdFromSchema, ResolvedXAxisColumnIdFromSchema, TimeBucket, TimeBucketConfig, XAxisConfig } from "./types.mjs";
1
+ import { BaseColumnHint, ChartTypeConfig, CountMetric, DerivedBooleanColumnSchema, DerivedCategoryColumnSchema, DerivedDateColumnSchema, DerivedNumberColumnSchema, FiltersConfig, GroupByConfig, InferableFieldKey, Metric, MetricConfig, NumericAggregateFunction, RawColumnSchemaFor, TimeBucketConfig, XAxisConfig } from "./types.mjs";
2
2
 
3
3
  //#region src/core/schema-builder.types.d.ts
4
4
  type Nullish = null | undefined;
@@ -7,9 +7,6 @@ type Simplify<T> = { [TKey in keyof T]: T[TKey] } & {};
7
7
  type NonEmptyReadonlyArray<TValue> = readonly [TValue, ...TValue[]];
8
8
  type FieldKeyMatchingValue<T, TValue> = Extract<{ [TKey in InferableFieldKey<T>]-?: [NonNullish<T[TKey]>] extends [TValue] ? TKey : never }[InferableFieldKey<T>], string>;
9
9
  type ReplaceConfigValue<TConfig, TKey extends PropertyKey, TValue> = Simplify<Omit<TConfig, TKey> & { [TProperty in TKey]: TValue }>;
10
- type SchemaColumnsContext<TColumns extends Record<string, unknown> | undefined> = [TColumns] extends [undefined] ? undefined : {
11
- columns?: TColumns;
12
- };
13
10
  type AppendConfigValue<TConfig, TKey extends PropertyKey, TValue> = Simplify<Omit<TConfig, TKey> & { [TProperty in TKey]: TConfig extends Record<TKey, infer TExisting extends readonly unknown[]> ? readonly [...TExisting, TValue] : readonly [TValue] }>;
14
11
  type AppendManyConfigValues<TConfig, TKey extends PropertyKey, TValues extends readonly unknown[]> = Simplify<Omit<TConfig, TKey> & { [TProperty in TKey]: TConfig extends Record<TKey, infer TExisting extends readonly unknown[]> ? readonly [...TExisting, ...TValues] : TValues }>;
15
12
  type AggregateSelectionFromList<TAggregates extends readonly NumericAggregateFunction[]> = TAggregates extends readonly [infer TOnlyAggregate extends NumericAggregateFunction] ? TOnlyAggregate : TAggregates;
@@ -231,49 +228,5 @@ type SchemaFromBuilder<TColumns extends Record<string, unknown> | undefined, TXA
231
228
  timeBucket?: Extract<TTimeBucket, TimeBucketConfig | undefined>;
232
229
  connectNulls?: Extract<TConnectNulls, boolean | undefined>;
233
230
  };
234
- type ChartSchemaBuilder<TRow, TColumns extends Record<string, unknown> | undefined = undefined, TXAxis extends XAxisConfig<any> | undefined = undefined, TGroupBy extends GroupByConfig<any> | undefined = undefined, TFilters extends FiltersConfig<any> | undefined = undefined, TMetric extends MetricConfig<any> | undefined = undefined, TChartType extends ChartTypeConfig | undefined = undefined, TTimeBucket extends TimeBucketConfig | undefined = undefined, TConnectNulls extends boolean | undefined = undefined> = {
235
- /**
236
- * Declare explicit raw and derived columns for this schema.
237
- *
238
- * Place this early in the chain so later sections can narrow against the
239
- * final column ids and roles.
240
- */
241
- columns: TColumns extends undefined ? <const TEntries extends readonly SchemaColumnEntry<TRow>[]>(defineColumns: (columns: ColumnHelper<TRow>) => TEntries & ValidateColumnEntries<TEntries>) => ChartSchemaBuilder<TRow, ColumnsFromEntries<TRow, TEntries>, TXAxis, TGroupBy, TFilters, TMetric, TChartType, TTimeBucket, TConnectNulls> : never;
242
- /**
243
- * Configure which columns may appear on the X-axis.
244
- */
245
- xAxis<const TBuilder extends SelectableControlBuilder<ResolvedXAxisColumnIdFromSchema<TRow, SchemaColumnsContext<TColumns>>, true>>(defineXAxis: (xAxis: SelectableControlBuilder<ResolvedXAxisColumnIdFromSchema<TRow, SchemaColumnsContext<TColumns>>, true>) => TBuilder): ChartSchemaBuilder<TRow, TColumns, SelectableControlBuilderConfig<TBuilder>, TGroupBy, TFilters, TMetric, TChartType, TTimeBucket, TConnectNulls>;
246
- /**
247
- * Configure which columns may split the chart into series.
248
- */
249
- groupBy<const TBuilder extends SelectableControlBuilder<ResolvedGroupByColumnIdFromSchema<TRow, SchemaColumnsContext<TColumns>>, true>>(defineGroupBy: (groupBy: SelectableControlBuilder<ResolvedGroupByColumnIdFromSchema<TRow, SchemaColumnsContext<TColumns>>, true>) => TBuilder): ChartSchemaBuilder<TRow, TColumns, TXAxis, SelectableControlBuilderConfig<TBuilder>, TFilters, TMetric, TChartType, TTimeBucket, TConnectNulls>;
250
- /**
251
- * Configure which columns may appear in the filters UI.
252
- */
253
- filters<const TBuilder extends SelectableControlBuilder<ResolvedFilterColumnIdFromSchema<TRow, SchemaColumnsContext<TColumns>>, false>>(defineFilters: (filters: SelectableControlBuilder<ResolvedFilterColumnIdFromSchema<TRow, SchemaColumnsContext<TColumns>>, false>) => TBuilder): ChartSchemaBuilder<TRow, TColumns, TXAxis, TGroupBy, SelectableControlBuilderConfig<TBuilder>, TMetric, TChartType, TTimeBucket, TConnectNulls>;
254
- /**
255
- * Configure the public metric surface.
256
- */
257
- metric<const TBuilder extends MetricBuilder<ResolvedMetricColumnIdFromSchema<TRow, SchemaColumnsContext<TColumns>>, any, any, any>>(defineMetric: (metric: MetricBuilder<ResolvedMetricColumnIdFromSchema<TRow, SchemaColumnsContext<TColumns>>>) => TBuilder): ChartSchemaBuilder<TRow, TColumns, TXAxis, TGroupBy, TFilters, MetricBuilderConfig<TBuilder>, TChartType, TTimeBucket, TConnectNulls>;
258
- /**
259
- * Restrict which chart renderers are exposed to users.
260
- */
261
- chartType<const TBuilder extends SelectableControlBuilder<ChartType, true>>(defineChartType: (chartType: SelectableControlBuilder<ChartType, true>) => TBuilder): ChartSchemaBuilder<TRow, TColumns, TXAxis, TGroupBy, TFilters, TMetric, SelectableControlBuilderConfig<TBuilder>, TTimeBucket, TConnectNulls>;
262
- /**
263
- * Restrict which time buckets are exposed for date X-axes.
264
- */
265
- timeBucket<const TBuilder extends SelectableControlBuilder<TimeBucket, true>>(defineTimeBucket: (timeBucket: SelectableControlBuilder<TimeBucket, true>) => TBuilder): ChartSchemaBuilder<TRow, TColumns, TXAxis, TGroupBy, TFilters, TMetric, TChartType, SelectableControlBuilderConfig<TBuilder>, TConnectNulls>;
266
- /**
267
- * Control whether line and area charts visually bridge null gaps.
268
- *
269
- * `true` keeps the line connected across missing buckets. `false` shows a
270
- * visible gap.
271
- */
272
- connectNulls<const TValue extends boolean>(value: TValue): ChartSchemaBuilder<TRow, TColumns, TXAxis, TGroupBy, TFilters, TMetric, TChartType, TTimeBucket, TValue>;
273
- /**
274
- * Finalize the schema into the runtime object consumed by `useChart(...)`.
275
- */
276
- build(): DefinedChartSchema<TRow, SchemaFromBuilder<TColumns, TXAxis, TGroupBy, TFilters, TMetric, TChartType, TTimeBucket, TConnectNulls>>;
277
- };
278
231
  //#endregion
279
- export { ChartSchemaBuilder };
232
+ export { ColumnHelper, ColumnsFromEntries, MetricBuilder, MetricBuilderConfig, SchemaColumnEntry, SchemaFromBuilder, SelectableControlBuilder, SelectableControlBuilderConfig, ValidateColumnEntries };
@@ -497,15 +497,21 @@ type ChartSchemaValidationTarget<T, TSchema extends ChartSchema<T, any>> = {
497
497
  chartType?: ChartTypeConfig;
498
498
  timeBucket?: TimeBucketConfig;
499
499
  };
500
- /** Strict schema object returned by `defineChartSchema(...)`. */
500
+ /** Strict schema object returned by a chart schema builder. */
501
501
  type ValidatedChartSchema<T, TSchema> = TSchema extends ChartSchema<T, any> ? TSchema & ExactShape<ChartSchemaValidationTarget<T, TSchema>, TSchema> & ValidateChartSchemaLiterals<T, TSchema> : TSchema;
502
- /** Strict schema object returned by `defineChartSchema(...)`. */
502
+ /** Strict schema object returned by a chart schema builder. */
503
503
  type DefinedChartSchema<T, TSchema extends ChartSchema<T, any> = ChartSchema<T, any>> = TSchema & ChartSchemaDefinitionBrand;
504
504
  /**
505
505
  * Public schema definition input accepted by chart-studio APIs.
506
506
  *
507
- * Most callers use the fluent builder returned by `defineChartSchema<Row>()`.
508
- * Plain schema objects are also accepted at the runtime boundary.
507
+ * In the simple single-chart case this is the contract for
508
+ * `useChart({data, schema})`.
509
+ *
510
+ * Most callers use the dataset-backed chart builder from
511
+ * `defineDataset<Row>().chart(...)`.
512
+ *
513
+ * Builders can be passed directly. Plain schema objects are also accepted at
514
+ * the runtime boundary.
509
515
  */
510
516
  type ChartSchemaDefinition<T, TSchema extends ChartSchema<T, any> = ChartSchema<T, any>> = TSchema | {
511
517
  build: () => DefinedChartSchema<T, TSchema>;
@@ -741,6 +747,8 @@ type ChartSchemaDefinitionBrand = {
741
747
  * - For boolean columns: true/false/null (null = no filter)
742
748
  */
743
749
  type FilterState<TColumnId extends string = string> = Map<TColumnId, Set<string>>;
750
+ /** Whether one chart input slice is owned externally or by the hook itself. */
751
+ type ChartControlMode = 'controlled' | 'uncontrolled';
744
752
  /** Sort direction. */
745
753
  type SortDirection = 'asc' | 'desc';
746
754
  /**
@@ -836,6 +844,37 @@ type DateRangeFilter = {
836
844
  from: Date | null;
837
845
  to: Date | null;
838
846
  };
847
+ /**
848
+ * Requested date-range selection for externally driven chart inputs.
849
+ *
850
+ * When `preset` is non-null, the effective `dateRangeFilter` is derived from
851
+ * that preset and `customFilter` is simply preserved for later custom mode.
852
+ */
853
+ type ChartDateRangeSelection = {
854
+ preset: DateRangePresetId | null;
855
+ customFilter: DateRangeFilter | null;
856
+ };
857
+ /**
858
+ * Optional externally driven inputs for the chart's data scope.
859
+ *
860
+ * Provide a value to control that slice. Omit it to let `useChart(...)` own
861
+ * the state internally. Callbacks fire for both controlled and uncontrolled
862
+ * usage; in controlled mode they are the only way setters can request changes.
863
+ */
864
+ type ChartDataScopeInputs<TFilterColumnId extends string = string, TDateColumnId extends string = string> = {
865
+ filters?: FilterState<TFilterColumnId>;
866
+ onFiltersChange?: (filters: FilterState<TFilterColumnId>) => void;
867
+ referenceDateId?: TDateColumnId | null;
868
+ onReferenceDateIdChange?: (columnId: TDateColumnId | null) => void;
869
+ dateRange?: ChartDateRangeSelection;
870
+ onDateRangeChange?: (selection: ChartDateRangeSelection) => void;
871
+ };
872
+ /** Controlled/uncontrolled status for each data-scope slice. */
873
+ type ChartDataScopeControlState = {
874
+ filters: ChartControlMode;
875
+ referenceDateId: ChartControlMode;
876
+ dateRange: ChartControlMode;
877
+ };
839
878
  /**
840
879
  * Full chart state returned by the useChart hook.
841
880
  * Contains both controlled state and derived computations.
@@ -879,7 +918,12 @@ type ChartInstance<T, TColumnId extends string = string, TChartType extends Char
879
918
  availableGroupBys: Array<{
880
919
  id: TGroupById;
881
920
  label: string;
882
- }>; /** Current metric (what the Y-axis measures). */
921
+ }>;
922
+ /**
923
+ * Whether groupBy can be set to `null` (no grouping).
924
+ * False when the schema forces a default that always resolves to a column.
925
+ */
926
+ isGroupByOptional: boolean; /** Current metric (what the Y-axis measures). */
883
927
  metric: TMetric;
884
928
  /**
885
929
  * Change the metric.
@@ -899,7 +943,8 @@ type ChartInstance<T, TColumnId extends string = string, TChartType extends Char
899
943
  * Whether line and area charts connect across null data points.
900
944
  * Derived from the schema's `connectNulls` option.
901
945
  */
902
- connectNulls: boolean; /** Active filter values per column. */
946
+ connectNulls: boolean; /** Which data-scope slices are externally controlled vs chart-owned. */
947
+ dataScopeControl: ChartDataScopeControlState; /** Active filter values per column after runtime sanitization for the active source. */
903
948
  filters: FilterState<TFilterColumnId>;
904
949
  /**
905
950
  * Toggle a specific filter value on/off for a column.
@@ -911,7 +956,7 @@ type ChartInstance<T, TColumnId extends string = string, TChartType extends Char
911
956
  availableFilters: AvailableFilter<TFilterColumnId>[]; /** Current sort configuration (null = default order). */
912
957
  sorting: SortConfig | null; /** Change sorting. */
913
958
  setSorting: (sorting: SortConfig | null) => void; /** Date range for the active reference date column (computed from filtered data). */
914
- dateRange: DateRange<TDateColumnId> | null; /** Which date column provides the visible date range context. */
959
+ dateRange: DateRange<TDateColumnId> | null; /** Which date column provides the visible date range context after source-local fallback. */
915
960
  referenceDateId: TDateColumnId | null;
916
961
  /**
917
962
  * Change the reference date column.
@@ -946,6 +991,8 @@ type ChartInstance<T, TColumnId extends string = string, TChartType extends Char
946
991
  type ChartInstanceFromSchema<T, TSchema extends ChartSchema<T, any> | undefined = undefined> = ChartInstance<T, ResolvedColumnIdFromSchema<T, TSchema>, RestrictedChartTypeFromSchema<TSchema>, RestrictedXAxisColumnIdFromSchema<T, TSchema>, RestrictedGroupByColumnIdFromSchema<T, TSchema>, Extract<MetricColumnIdFromMetric<RestrictedMetricFromSchema<T, TSchema>>, ResolvedMetricColumnIdFromSchema<T, TSchema>>, RestrictedMetricFromSchema<T, TSchema>, RestrictedFilterColumnIdFromSchema<T, TSchema>, ResolvedDateColumnIdFromSchema<T, TSchema>, RestrictedTimeBucketFromSchema<TSchema>>;
947
992
  /** Single-source chart instance narrowed by any supported schema input. */
948
993
  type ChartInstanceFromSchemaDefinition<T, TSchema extends ChartSchemaDefinition<T, any> | undefined = undefined> = ChartInstanceFromSchema<T, ResolvedChartSchemaFromDefinition<TSchema>>;
994
+ /** Data-scope input typing for one single-source chart definition. */
995
+ type ChartDataScopeInputsFromSchemaDefinition<T, TSchema extends ChartSchemaDefinition<T, any> | undefined = undefined> = ChartDataScopeInputs<RestrictedFilterColumnIdFromSchema<T, ResolvedChartSchemaFromDefinition<TSchema>>, ResolvedDateColumnIdFromSchema<T, ResolvedChartSchemaFromDefinition<TSchema>>>;
949
996
  type SourceIdFromSource<TSource extends AnyChartSourceOptions> = TSource['id'];
950
997
  type SourceRowFromSource<TSource extends AnyChartSourceOptions> = TSource extends ChartSourceOptions<string, infer TRow, any> ? TRow : never;
951
998
  type SourceColumnIdFromSource<TSource extends AnyChartSourceOptions> = TSource extends ChartSourceOptions<string, infer TRow, infer TSchema> ? ResolvedColumnIdFromSchema<TRow, ResolvedChartSchemaFromDefinition<TSchema>> : never;
@@ -956,6 +1003,8 @@ type SourceFilterColumnIdFromSource<TSource extends AnyChartSourceOptions> = TSo
956
1003
  type SourceDateColumnIdFromSource<TSource extends AnyChartSourceOptions> = TSource extends ChartSourceOptions<string, infer TRow, infer TSchema> ? ResolvedDateColumnIdFromSchema<TRow, ResolvedChartSchemaFromDefinition<TSchema>> : never;
957
1004
  type SourceIdFromSources<TSources extends NonEmptyChartSourceOptions> = Extract<TSources[number]['id'], string>;
958
1005
  type SourceColumnIdFromSources<TSources extends NonEmptyChartSourceOptions> = TSources[number] extends infer TSource ? TSource extends AnyChartSourceOptions ? SourceColumnIdFromSource<TSource> : never : never;
1006
+ type SourceFilterColumnIdFromSources<TSources extends NonEmptyChartSourceOptions> = TSources[number] extends infer TSource ? TSource extends AnyChartSourceOptions ? SourceFilterColumnIdFromSource<TSource> : never : never;
1007
+ type SourceDateColumnIdFromSources<TSources extends NonEmptyChartSourceOptions> = TSources[number] extends infer TSource ? TSource extends AnyChartSourceOptions ? SourceDateColumnIdFromSource<TSource> : never : never;
959
1008
  type MultiSourceChartBranch<TSources extends NonEmptyChartSourceOptions, TSource extends AnyChartSourceOptions> = Omit<ChartInstance<SourceRowFromSource<TSource>, SourceColumnIdFromSources<TSources>>, 'activeSourceId' | 'setActiveSource' | 'sources' | 'xAxisId' | 'setXAxis' | 'availableXAxes' | 'groupById' | 'setGroupBy' | 'availableGroupBys' | 'metric' | 'setMetric' | 'availableMetrics' | 'filters' | 'toggleFilter' | 'clearFilter' | 'availableFilters' | 'dateRange' | 'referenceDateId' | 'setReferenceDateId' | 'availableDateColumns' | 'columns'> & {
960
1009
  activeSourceId: SourceIdFromSource<TSource>;
961
1010
  setActiveSource: (sourceId: SourceIdFromSources<TSources>) => void;
@@ -997,5 +1046,7 @@ type MultiSourceChartBranch<TSources extends NonEmptyChartSourceOptions, TSource
997
1046
  * Narrow on `activeSourceId` to recover the source-specific row and column IDs.
998
1047
  */
999
1048
  type MultiSourceChartInstance<TSources extends NonEmptyChartSourceOptions> = TSources[number] extends infer TSource ? TSource extends AnyChartSourceOptions ? MultiSourceChartBranch<TSources, TSource> : never : never;
1049
+ /** Data-scope input typing for one multi-source source-switching chart. */
1050
+ type MultiSourceChartDataScopeInputs<TSources extends NonEmptyChartSourceOptions> = ChartDataScopeInputs<SourceFilterColumnIdFromSources<TSources>, SourceDateColumnIdFromSources<TSources>>;
1000
1051
  //#endregion
1001
- export { AggregateFunction, AggregateMetric, AvailableFilter, BaseColumnHint, BooleanColumn, CategoricalChartType, CategoryColumn, ChartColumn, ChartColumnType, ChartInstance, ChartInstanceFromSchema, ChartInstanceFromSchemaDefinition, ChartSchema, ChartSchemaDefinition, ChartSeries, ChartSourceOptions, ChartType, ChartTypeConfig, ColumnFormat, ColumnFormatPreset, CountMetric, DateColumn, DateColumnFormat, DefinedChartSchema, DerivedBooleanColumnSchema, DerivedCategoryColumnSchema, DerivedColumnSchema, DerivedDateColumnSchema, DerivedNumberColumnSchema, FilterState, FiltersConfig, GroupByConfig, InferableFieldKey, Metric, MetricConfig, MultiSourceChartInstance, NonEmptyChartSourceOptions, NumberColumn, NumberColumnFormat, NumericAggregateFunction, RawColumnSchemaFor, RawColumnSchemaMap, ResolvedChartSchemaFromDefinition, ResolvedColumnIdFromSchema, ResolvedFilterColumnIdFromSchema, ResolvedGroupByColumnIdFromSchema, ResolvedMetricColumnIdFromSchema, ResolvedXAxisColumnIdFromSchema, RestrictedChartTypeFromSchema, RestrictedFilterColumnIdFromSchema, RestrictedGroupByColumnIdFromSchema, RestrictedMetricFromSchema, RestrictedTimeBucketFromSchema, RestrictedXAxisColumnIdFromSchema, SelectableControlConfig, SortConfig, SortDirection, TimeBucket, TimeBucketConfig, TimeSeriesChartType, TransformedDataPoint, ValidatedChartSchema, XAxisConfig };
1052
+ export { AggregateFunction, AggregateMetric, AggregateMetricAllowance, AvailableFilter, BaseColumnHint, BooleanColumn, CategoricalChartType, CategoryColumn, ChartColumn, ChartColumnType, ChartControlMode, ChartDataScopeControlState, ChartDataScopeInputs, ChartDataScopeInputsFromSchemaDefinition, ChartDateRangeSelection, ChartInstance, ChartInstanceFromSchema, ChartInstanceFromSchemaDefinition, ChartSchema, ChartSchemaDefinition, ChartSeries, ChartSourceOptions, ChartType, ChartTypeConfig, ColumnFormat, ColumnFormatPreset, ColumnHintFor, CountMetric, DateColumn, DateColumnFormat, DateRange, DateRangeFilter, DefinedChartSchema, DerivedBooleanColumnSchema, DerivedCategoryColumnSchema, DerivedColumnSchema, DerivedDateColumnSchema, DerivedNumberColumnSchema, DurationColumnFormat, DurationInputUnit, FilterState, FiltersConfig, GroupByConfig, InferableFieldKey, Metric, MetricAllowance, MetricConfig, MultiSourceChartDataScopeInputs, MultiSourceChartInstance, NonEmptyChartSourceOptions, NumberColumn, NumberColumnFormat, NumericAggregateFunction, NumericAggregateSelection, RawColumnSchemaFor, RawColumnSchemaMap, ResolvedChartSchemaFromDefinition, ResolvedColumnIdFromSchema, ResolvedDateColumnIdFromSchema, ResolvedFilterColumnIdFromSchema, ResolvedGroupByColumnIdFromSchema, ResolvedMetricColumnIdFromSchema, ResolvedXAxisColumnIdFromSchema, RestrictedChartTypeFromSchema, RestrictedFilterColumnIdFromSchema, RestrictedGroupByColumnIdFromSchema, RestrictedMetricFromSchema, RestrictedTimeBucketFromSchema, RestrictedXAxisColumnIdFromSchema, SelectableControlConfig, SortConfig, SortDirection, TimeBucket, TimeBucketConfig, TimeSeriesChartType, TransformedDataPoint, ValidatedChartSchema, XAxisConfig };
@@ -1,11 +1,11 @@
1
- import { ChartSchemaDefinition, ChartSourceOptions, NonEmptyChartSourceOptions } from "./types.mjs";
1
+ import { ChartDataScopeInputsFromSchemaDefinition, ChartSchemaDefinition, ChartSourceOptions, MultiSourceChartDataScopeInputs, NonEmptyChartSourceOptions } from "./types.mjs";
2
2
 
3
3
  //#region src/core/use-chart-options.d.ts
4
4
  /**
5
5
  * Single-source options for `useChart(...)`.
6
6
  *
7
- * This is the common case: one dataset, one optional schema, one optional
8
- * human-readable source label.
7
+ * This is the stable chart-first path:
8
+ * one dataset, one optional schema, one chart.
9
9
  */
10
10
  interface SingleSourceOptions<T, TSchema extends ChartSchemaDefinition<T, any> | undefined = undefined> {
11
11
  /**
@@ -18,8 +18,11 @@ interface SingleSourceOptions<T, TSchema extends ChartSchemaDefinition<T, any> |
18
18
  /**
19
19
  * Optional explicit schema layered on top of inference.
20
20
  *
21
- * Usually this is the fluent builder returned by `defineChartSchema<Row>()`.
22
- * Plain schema objects are also accepted.
21
+ * Usually this is the dataset-backed chart builder from
22
+ * `defineDataset<Row>().chart(...)`.
23
+ *
24
+ * Builders can be passed directly without calling `.build()`. Plain schema
25
+ * objects are also accepted.
23
26
  *
24
27
  * Use this when you want to:
25
28
  * - rename fields with `label`
@@ -28,6 +31,9 @@ interface SingleSourceOptions<T, TSchema extends ChartSchemaDefinition<T, any> |
28
31
  * - exclude fields
29
32
  * - create derived columns
30
33
  * - restrict what users can select in the chart UI
34
+ *
35
+ * Unspecified raw fields still participate through inference unless you
36
+ * explicitly exclude them.
31
37
  */
32
38
  schema?: TSchema;
33
39
  /**
@@ -36,18 +42,39 @@ interface SingleSourceOptions<T, TSchema extends ChartSchemaDefinition<T, any> |
36
42
  * Example: `'Jobs'`, `'Quarterly Financials'`, or `'Pipeline Health'`.
37
43
  */
38
44
  sourceLabel?: string;
45
+ /**
46
+ * Optional externally driven data-scope inputs for this one chart.
47
+ *
48
+ * This is additive:
49
+ * `useChart({data})` and `useChart({data, schema})` stay the simple chart-first paths.
50
+ *
51
+ * Only data-scope state belongs here:
52
+ * filters, reference-date selection, and date-range selection.
53
+ *
54
+ * Presentation controls such as `xAxis`, `groupBy`, `metric`, and
55
+ * `chartType` remain chart-local.
56
+ */
57
+ inputs?: ChartDataScopeInputsFromSchemaDefinition<T, TSchema>;
39
58
  sources?: never;
40
59
  }
41
60
  /**
42
61
  * Multi-source options for `useChart(...)`.
43
62
  *
44
- * Use this when the user should be able to switch between several datasets that
45
- * may each have their own schema.
63
+ * Use this for source-switching within one chart. It is separate from the
64
+ * single-source `useChart({data, schema})` contract and is not dashboard
65
+ * composition.
46
66
  */
47
67
  interface MultiSourceOptions<TSources extends NonEmptyChartSourceOptions = NonEmptyChartSourceOptions> {
48
68
  data?: never;
49
69
  schema?: never;
50
70
  sourceLabel?: never;
71
+ /**
72
+ * Optional externally driven data-scope inputs for one source-switching chart.
73
+ *
74
+ * These inputs are chart-level and sanitize against the active source at
75
+ * runtime. They do not turn `sources` into dashboard composition.
76
+ */
77
+ inputs?: MultiSourceChartDataScopeInputs<TSources>;
51
78
  /**
52
79
  * Named list of chart sources.
53
80
  *
@@ -64,4 +91,4 @@ interface MultiSourceOptions<TSources extends NonEmptyChartSourceOptions = NonEm
64
91
  */
65
92
  type UseChartOptions<T, TSchema extends ChartSchemaDefinition<T, any> | undefined = undefined> = SingleSourceOptions<T, TSchema> | MultiSourceOptions;
66
93
  //#endregion
67
- export { SingleSourceOptions, UseChartOptions };
94
+ export { MultiSourceOptions, SingleSourceOptions, UseChartOptions };
@@ -1,5 +1,12 @@
1
1
  //#region src/core/use-chart-resolvers.ts
2
2
  /**
3
+ * Clone filter state so callers can safely derive the next map without sharing
4
+ * mutable `Set` instances.
5
+ */
6
+ function cloneFilterState(filters) {
7
+ return new Map([...filters].map(([columnId, values]) => [columnId, new Set(values)]));
8
+ }
9
+ /**
3
10
  * Resolve the active X-axis, preferring date columns first.
4
11
  */
5
12
  function resolveXAxisId(xAxisId, activeColumns) {
@@ -21,14 +28,17 @@ function resolveReferenceDateId(referenceDateIdRaw, dateColumns, resolvedXAxisId
21
28
  /**
22
29
  * Remove filters that target columns not present in the active source.
23
30
  */
24
- function sanitizeFilters(filters, activeColumns) {
31
+ function sanitizeFilters(filters, activeColumns, availableValues) {
25
32
  const validColumnIds = new Set(activeColumns.map((column) => column.id));
26
33
  const next = /* @__PURE__ */ new Map();
27
34
  for (const [columnId, values] of filters) {
28
35
  if (!validColumnIds.has(columnId)) continue;
29
- next.set(columnId, new Set(values));
36
+ const allowedValues = availableValues?.get(columnId);
37
+ const sanitizedValues = allowedValues ? new Set([...values].filter((value) => allowedValues.has(value))) : new Set(values);
38
+ if (sanitizedValues.size === 0) continue;
39
+ next.set(columnId, sanitizedValues);
30
40
  }
31
41
  return next;
32
42
  }
33
43
  //#endregion
34
- export { resolveReferenceDateId, resolveXAxisId, sanitizeFilters };
44
+ export { cloneFilterState, resolveReferenceDateId, resolveXAxisId, sanitizeFilters };
@@ -1,13 +1,18 @@
1
1
  import { ChartInstanceFromSchemaDefinition, ChartSchemaDefinition, MultiSourceChartInstance, NonEmptyChartSourceOptions } from "./types.mjs";
2
- import { SingleSourceOptions } from "./use-chart-options.mjs";
2
+ import { MultiSourceOptions, SingleSourceOptions } from "./use-chart-options.mjs";
3
3
 
4
4
  //#region src/core/use-chart.d.ts
5
5
  /**
6
- * Headless React hook that manages all chart configuration, state, and derived/transformed data for chart rendering.
6
+ * Headless React hook that manages one chart's configuration, state, and
7
+ * derived/transformed data for rendering.
7
8
  *
8
- * There are two major usage patterns:
9
- * - Single source: Provide plain `data` and optional `schema`.
10
- * - Multi-source: Provide a `sources` array, each having an `id`, `label`, `data`, and optional `schema`.
9
+ * The stable primary path is single-source:
10
+ * - `useChart({data})` for zero-config inference
11
+ * - `useChart({data, schema})` when one chart needs an explicit contract
12
+ *
13
+ * Multi-source `sources` support exists for source-switching between
14
+ * interchangeable datasets, but it is intentionally separate from the
15
+ * single-chart schema contract and is not dashboard composition.
11
16
  *
12
17
  * @template T - The type of each data record in the dataset.
13
18
  * @template TSchema - Optional explicit schema for inferred single-source columns.
@@ -15,8 +20,11 @@ import { SingleSourceOptions } from "./use-chart-options.mjs";
15
20
  * Chart configuration options. Should provide either:
16
21
  * - `data`, optional `schema`, and (optionally) `sourceLabel` for a single source
17
22
  * - or `sources` array for multiple sources
23
+ * - and optional `inputs` when filters/reference-date/date-range state is
24
+ * driven externally
18
25
  * Any explicit single-source or per-source schema is usually authored with
19
- * `defineChartSchema<Row>()...`. Plain schema objects are also accepted.
26
+ * `defineDataset<Row>().chart(...)`.
27
+ * Builders can be passed directly; plain schema objects are also accepted.
20
28
  *
21
29
  * @returns {ChartInstance}
22
30
  * An object representing chart configuration, state, and all derived data/operations:
@@ -30,6 +38,7 @@ import { SingleSourceOptions } from "./use-chart-options.mjs";
30
38
  * - `metric`, `setMetric`, `availableMetrics`: Current aggregation metric, setter, and options
31
39
  * - `timeBucket`, `setTimeBucket`, `availableTimeBuckets`: Date/time bucketing state, setter, and options
32
40
  * - `isTimeSeries`: Whether the chart is a time series (based on axis)
41
+ * - `dataScopeControl`: Which data-scope slices are controlled externally
33
42
  * - `filters`, `toggleFilter`, `clearFilter`, `clearAllFilters`, `availableFilters`: Current filters and their controls
34
43
  * - `sorting`, `setSorting`: Current sorting config and setter
35
44
  * - `dateRange`, `referenceDateId`, `setReferenceDateId`, `availableDateColumns`, `dateRangeFilter`, `setDateRangeFilter`: Date filtering state and controls
@@ -39,12 +48,7 @@ import { SingleSourceOptions } from "./use-chart-options.mjs";
39
48
  * - `rawData`: The raw input data for the active data source
40
49
  * - `recordCount`: Number of records present in the current data source
41
50
  */
42
- declare function useChart<const TSources extends NonEmptyChartSourceOptions>(options: {
43
- data?: never;
44
- schema?: never;
45
- sourceLabel?: never;
46
- sources: TSources;
47
- }): MultiSourceChartInstance<TSources>;
51
+ declare function useChart<const TSources extends NonEmptyChartSourceOptions>(options: MultiSourceOptions<TSources>): MultiSourceChartInstance<TSources>;
48
52
  declare function useChart<T, const TSchema extends ChartSchemaDefinition<T, any> | undefined = undefined>(options: SingleSourceOptions<T, TSchema>): ChartInstanceFromSchemaDefinition<T, TSchema>;
49
53
  //#endregion
50
54
  export { useChart };