@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
@@ -1,3 +1,4 @@
1
+ import { resolveChartSchemaDefinition } from "./schema-builder.mjs";
1
2
  //#region src/core/infer-columns.ts
2
3
  const MAX_SAMPLE_COUNT = 50;
3
4
  const DATE_KEY_PATTERN = /(date|time|timestamp|created|updated|start|end|deadline|due|scheduled|posted|published|at)$/i;
@@ -463,7 +464,8 @@ function sortResolvedColumns(columns) {
463
464
  * Resolve chart columns directly from raw data and an optional explicit schema.
464
465
  */
465
466
  function inferColumnsFromData(data, schema) {
466
- const rawColumnSchema = getRawColumnSchemaMap(schema);
467
+ const resolvedSchema = resolveChartSchemaDefinition(schema);
468
+ const rawColumnSchema = getRawColumnSchemaMap(resolvedSchema);
467
469
  const fields = collectFieldKeys(data, rawColumnSchema);
468
470
  const rawFieldIds = new Set(fields);
469
471
  const resolvedColumns = [];
@@ -473,7 +475,7 @@ function inferColumnsFromData(data, schema) {
473
475
  const column = buildRawColumn(typedField, samples, rawColumnSchema?.[typedField]);
474
476
  if (column) resolvedColumns.push(column);
475
477
  }
476
- for (const [key, columnSchema] of getDerivedColumnSchemas(schema, rawFieldIds)) resolvedColumns.push(buildDerivedColumn(key, columnSchema));
478
+ for (const [key, columnSchema] of getDerivedColumnSchemas(resolvedSchema, rawFieldIds)) resolvedColumns.push(buildDerivedColumn(key, columnSchema));
477
479
  if (resolvedColumns.length === 0) warn("No inferable or explicit chart columns were found. Provide non-empty data or schema.columns.");
478
480
  return finalizeResolvedColumns(sortResolvedColumns(resolvedColumns));
479
481
  }
@@ -109,13 +109,21 @@ function restrictAvailableMetrics(metrics, config) {
109
109
  */
110
110
  function resolveMetric(metric, columns, availableMetrics, configuredDefaultMetric) {
111
111
  if (availableMetrics && availableMetrics.length > 0) {
112
- const selectedMetric = availableMetrics.find((candidate) => isSameMetric(candidate, metric));
113
- if (selectedMetric) return selectedMetric;
114
- return (configuredDefaultMetric ? availableMetrics.find((candidate) => isSameMetric(candidate, configuredDefaultMetric)) : void 0) ?? availableMetrics[0];
112
+ if (metric !== null) {
113
+ const selectedMetric = availableMetrics.find((candidate) => isSameMetric(candidate, metric));
114
+ if (selectedMetric) return selectedMetric;
115
+ }
116
+ const defaultMetric = configuredDefaultMetric ? availableMetrics.find((candidate) => isSameMetric(candidate, configuredDefaultMetric)) : void 0;
117
+ if (defaultMetric) return defaultMetric;
118
+ if (metric === null) {
119
+ const countMetric = availableMetrics.find((candidate) => candidate.kind === "count");
120
+ if (countMetric) return countMetric;
121
+ }
122
+ return availableMetrics[0];
115
123
  }
116
- if (!isAggregateMetric(metric)) return DEFAULT_METRIC;
124
+ if (metric === null || !isAggregateMetric(metric)) return DEFAULT_METRIC;
117
125
  if (!columns.find((candidate) => candidate.type === "number" && candidate.id === metric.columnId)) return DEFAULT_METRIC;
118
126
  return metric;
119
127
  }
120
128
  //#endregion
121
- export { DEFAULT_METRIC, buildAvailableMetrics, getMetricLabel, isAggregateMetric, isSameMetric, resolveMetric, restrictAvailableMetrics };
129
+ export { DEFAULT_METRIC, buildAvailableMetrics, getMetricLabel, isAggregateMetric, isSameMetric, normalizeMetricAllowances, resolveMetric, restrictAvailableMetrics };
@@ -157,7 +157,10 @@ function buildTimeBuckets(items, xColumn, groupByColumn, groups, metric, metricC
157
157
  xKey: key
158
158
  };
159
159
  const groupMap = accumulator.get(key);
160
- for (const group of groups) point[group] = aggregate(groupMap.get(group) ?? [], metric.kind === "aggregate" ? metric.aggregate : "count", metric.kind === "aggregate" ? metric.includeZeros ?? true : true);
160
+ for (const group of groups) {
161
+ const values = groupMap.get(group) ?? [];
162
+ point[group] = values.length === 0 ? null : aggregate(values, metric.kind === "aggregate" ? metric.aggregate : "count", metric.kind === "aggregate" ? metric.includeZeros ?? true : true);
163
+ }
161
164
  return point;
162
165
  }),
163
166
  groups
@@ -0,0 +1,335 @@
1
+ import { isSameMetric, normalizeMetricAllowances } from "./metric-utils.mjs";
2
+ //#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
+ const COLUMN_HELPER = {
6
+ field(id, options = {}) {
7
+ return {
8
+ kind: "raw",
9
+ id,
10
+ column: options
11
+ };
12
+ },
13
+ date(id, options = {}) {
14
+ return {
15
+ kind: "raw",
16
+ id,
17
+ column: {
18
+ type: "date",
19
+ ...options
20
+ }
21
+ };
22
+ },
23
+ category(id, options = {}) {
24
+ return {
25
+ kind: "raw",
26
+ id,
27
+ column: {
28
+ type: "category",
29
+ ...options
30
+ }
31
+ };
32
+ },
33
+ number(id, options = {}) {
34
+ return {
35
+ kind: "raw",
36
+ id,
37
+ column: {
38
+ type: "number",
39
+ ...options
40
+ }
41
+ };
42
+ },
43
+ boolean(id, options = {}) {
44
+ return {
45
+ kind: "raw",
46
+ id,
47
+ column: {
48
+ type: "boolean",
49
+ ...options
50
+ }
51
+ };
52
+ },
53
+ exclude(id) {
54
+ return {
55
+ kind: "exclude",
56
+ id,
57
+ column: false
58
+ };
59
+ },
60
+ derived: {
61
+ date(id, options) {
62
+ return {
63
+ kind: "derived",
64
+ id,
65
+ column: {
66
+ kind: "derived",
67
+ type: "date",
68
+ ...options
69
+ }
70
+ };
71
+ },
72
+ category(id, options) {
73
+ return {
74
+ kind: "derived",
75
+ id,
76
+ column: {
77
+ kind: "derived",
78
+ type: "category",
79
+ ...options
80
+ }
81
+ };
82
+ },
83
+ boolean(id, options) {
84
+ return {
85
+ kind: "derived",
86
+ id,
87
+ column: {
88
+ kind: "derived",
89
+ type: "boolean",
90
+ ...options
91
+ }
92
+ };
93
+ },
94
+ number(id, options) {
95
+ return {
96
+ kind: "derived",
97
+ id,
98
+ column: {
99
+ kind: "derived",
100
+ type: "number",
101
+ ...options
102
+ }
103
+ };
104
+ }
105
+ }
106
+ };
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
+ function buildColumnsMap(entries) {
243
+ const columns = {};
244
+ for (const entry of entries) {
245
+ if (entry.id in columns) throw new Error(`Duplicate chart schema column id: "${entry.id}"`);
246
+ columns[entry.id] = entry.column;
247
+ }
248
+ return columns;
249
+ }
250
+ function assertColumnEntries(entries) {
251
+ if (!Array.isArray(entries)) throw new TypeError("defineChartSchema().columns(...) must return an array of column entries.");
252
+ }
253
+ function resolveChartSchemaDefinition(schema) {
254
+ if (!schema) return;
255
+ if (typeof schema === "object" && "build" in schema && typeof schema.build === "function") return schema.build();
256
+ return schema;
257
+ }
258
+ function createChartSchemaBuilder(state = {}) {
259
+ 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
+ },
269
+ xAxis(defineXAxis) {
270
+ const builder = defineXAxis(createSelectableControlBuilder({}, true));
271
+ return createChartSchemaBuilder({
272
+ ...state,
273
+ xAxis: getSelectableControlConfig(builder)
274
+ });
275
+ },
276
+ groupBy(defineGroupBy) {
277
+ const builder = defineGroupBy(createSelectableControlBuilder({}, true));
278
+ return createChartSchemaBuilder({
279
+ ...state,
280
+ groupBy: getSelectableControlConfig(builder)
281
+ });
282
+ },
283
+ filters(defineFilters) {
284
+ const builder = defineFilters(createSelectableControlBuilder({}, false));
285
+ return createChartSchemaBuilder({
286
+ ...state,
287
+ filters: getSelectableControlConfig(builder)
288
+ });
289
+ },
290
+ metric(defineMetric) {
291
+ const builder = defineMetric(createMetricBuilder());
292
+ return createChartSchemaBuilder({
293
+ ...state,
294
+ metric: getMetricBuilderConfig(builder)
295
+ });
296
+ },
297
+ chartType(defineChartType) {
298
+ const builder = defineChartType(createSelectableControlBuilder({}, true));
299
+ return createChartSchemaBuilder({
300
+ ...state,
301
+ chartType: getSelectableControlConfig(builder)
302
+ });
303
+ },
304
+ timeBucket(defineTimeBucket) {
305
+ const builder = defineTimeBucket(createSelectableControlBuilder({}, true));
306
+ return createChartSchemaBuilder({
307
+ ...state,
308
+ timeBucket: getSelectableControlConfig(builder)
309
+ });
310
+ },
311
+ connectNulls(value) {
312
+ return createChartSchemaBuilder({
313
+ ...state,
314
+ connectNulls: value
315
+ });
316
+ },
317
+ build() {
318
+ if (cachedSchema) return cachedSchema;
319
+ cachedSchema = {
320
+ ...state.columns !== void 0 ? { columns: state.columns } : {},
321
+ ...state.xAxis !== void 0 ? { xAxis: state.xAxis } : {},
322
+ ...state.groupBy !== void 0 ? { groupBy: state.groupBy } : {},
323
+ ...state.filters !== void 0 ? { filters: state.filters } : {},
324
+ ...state.metric !== void 0 ? { metric: state.metric } : {},
325
+ ...state.chartType !== void 0 ? { chartType: state.chartType } : {},
326
+ ...state.timeBucket !== void 0 ? { timeBucket: state.timeBucket } : {},
327
+ ...state.connectNulls !== void 0 ? { connectNulls: state.connectNulls } : {},
328
+ __chartSchemaBrand: "chart-schema-definition"
329
+ };
330
+ return cachedSchema;
331
+ }
332
+ };
333
+ }
334
+ //#endregion
335
+ export { createChartSchemaBuilder, resolveChartSchemaDefinition };