@matthieumordrel/chart-studio 0.2.4 → 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.
- package/README.md +42 -42
- package/dist/core/config-utils.mjs +3 -2
- package/dist/core/define-chart-schema.d.mts +26 -94
- package/dist/core/define-chart-schema.mjs +26 -34
- package/dist/core/infer-columns.d.mts +2 -2
- package/dist/core/infer-columns.mjs +4 -2
- package/dist/core/metric-utils.mjs +13 -5
- package/dist/core/schema-builder.mjs +335 -0
- package/dist/core/schema-builder.types.d.mts +279 -0
- package/dist/core/types.d.mts +24 -11
- package/dist/core/use-chart-options.d.mts +7 -4
- package/dist/core/use-chart.d.mts +4 -4
- package/dist/core/use-chart.mjs +20 -15
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +1 -1
- package/dist/ui/chart-canvas.d.mts +7 -3
- package/dist/ui/chart-canvas.mjs +62 -22
- package/dist/ui/chart-context.d.mts +8 -4
- package/dist/ui/chart-debug.d.mts +6 -2
- package/dist/ui/chart-debug.mjs +5 -1
- package/dist/ui/chart-toolbar.d.mts +6 -2
- package/dist/ui/chart-toolbar.mjs +4 -0
- package/dist/ui/percent-stacked.mjs +36 -0
- package/package.json +1 -1
|
@@ -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 };
|
|
@@ -0,0 +1,279 @@
|
|
|
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";
|
|
2
|
+
|
|
3
|
+
//#region src/core/schema-builder.types.d.ts
|
|
4
|
+
type Nullish = null | undefined;
|
|
5
|
+
type NonNullish<T> = Exclude<T, Nullish>;
|
|
6
|
+
type Simplify<T> = { [TKey in keyof T]: T[TKey] } & {};
|
|
7
|
+
type NonEmptyReadonlyArray<TValue> = readonly [TValue, ...TValue[]];
|
|
8
|
+
type FieldKeyMatchingValue<T, TValue> = Extract<{ [TKey in InferableFieldKey<T>]-?: [NonNullish<T[TKey]>] extends [TValue] ? TKey : never }[InferableFieldKey<T>], string>;
|
|
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
|
+
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
|
+
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
|
+
type AggregateSelectionFromList<TAggregates extends readonly NumericAggregateFunction[]> = TAggregates extends readonly [infer TOnlyAggregate extends NumericAggregateFunction] ? TOnlyAggregate : TAggregates;
|
|
16
|
+
type MetricColumnIdFromMetric<TMetric> = Extract<TMetric extends {
|
|
17
|
+
kind: 'aggregate';
|
|
18
|
+
columnId: infer TColumnId extends string;
|
|
19
|
+
} ? TColumnId : never, string>;
|
|
20
|
+
type MetricAggregateFromMetric<TMetric, TColumnId extends string> = Extract<Extract<TMetric, {
|
|
21
|
+
kind: 'aggregate';
|
|
22
|
+
columnId: TColumnId;
|
|
23
|
+
}> extends {
|
|
24
|
+
aggregate: infer TAggregate extends NumericAggregateFunction;
|
|
25
|
+
} ? TAggregate : never, NumericAggregateFunction>;
|
|
26
|
+
type AggregateMetricUnionFromSelection<TColumnId extends string, TAggregates extends readonly NumericAggregateFunction[]> = { [TIndex in keyof TAggregates]: TAggregates[TIndex] extends NumericAggregateFunction ? {
|
|
27
|
+
kind: 'aggregate';
|
|
28
|
+
columnId: TColumnId;
|
|
29
|
+
aggregate: TAggregates[TIndex];
|
|
30
|
+
} : never }[number];
|
|
31
|
+
type AggregateMetricTupleFromSelection<TColumnId extends string, TAggregates extends readonly NumericAggregateFunction[]> = TAggregates extends readonly [infer THead extends NumericAggregateFunction, ...infer TTail extends NumericAggregateFunction[]] ? readonly [{
|
|
32
|
+
kind: 'aggregate';
|
|
33
|
+
columnId: TColumnId;
|
|
34
|
+
aggregate: THead;
|
|
35
|
+
}, ...AggregateMetricTupleFromSelection<TColumnId, TTail>] : readonly [];
|
|
36
|
+
type SelectableMetricUnion<TMetricColumnId extends string, TAllowedMetric> = [TAllowedMetric] extends [never] ? Metric<TMetricColumnId> : TAllowedMetric;
|
|
37
|
+
type VisibleMetricUnion<TMetricColumnId extends string, TAllowedMetric, THiddenMetric> = Exclude<SelectableMetricUnion<TMetricColumnId, TAllowedMetric>, THiddenMetric>;
|
|
38
|
+
type DerivedColumnId<TRow, TColumnId extends string> = TColumnId extends InferableFieldKey<TRow> ? never : TColumnId;
|
|
39
|
+
type ColumnEntryId<TEntry> = TEntry extends {
|
|
40
|
+
id: infer TId extends string;
|
|
41
|
+
} ? TId : never;
|
|
42
|
+
type DuplicateColumnIds<TEntries extends readonly unknown[], TSeen extends string = never> = TEntries extends readonly [infer THead, ...infer TTail] ? ColumnEntryId<THead> extends TSeen ? ColumnEntryId<THead> | DuplicateColumnIds<TTail, TSeen> : DuplicateColumnIds<TTail, TSeen | ColumnEntryId<THead>> : never;
|
|
43
|
+
type DuplicateColumnIdError<TColumnId extends string> = {
|
|
44
|
+
__columnBuilderError__: `Duplicate column id "${TColumnId}"`;
|
|
45
|
+
};
|
|
46
|
+
type StringFieldKey<T> = FieldKeyMatchingValue<T, string>;
|
|
47
|
+
type NumberFieldKey<T> = FieldKeyMatchingValue<T, number>;
|
|
48
|
+
type BooleanFieldKey<T> = FieldKeyMatchingValue<T, boolean>;
|
|
49
|
+
type DateLikeFieldKey<T> = Extract<{ [TKey in InferableFieldKey<T>]-?: [NonNullish<T[TKey]>] extends [string | number | Date] ? TKey : never }[InferableFieldKey<T>], string>;
|
|
50
|
+
type SharedColumnOptions<TRow, TValue> = BaseColumnHint<TRow, TValue>;
|
|
51
|
+
type FieldColumnOptions<TRow, TFieldId extends InferableFieldKey<TRow>> = RawColumnSchemaFor<TRow[TFieldId], TRow>;
|
|
52
|
+
type DateColumnOptions<TRow> = SharedColumnOptions<TRow, string | number | Date>;
|
|
53
|
+
type CategoryColumnOptions<TRow> = SharedColumnOptions<TRow, string>;
|
|
54
|
+
type NumberColumnOptions<TRow> = SharedColumnOptions<TRow, number>;
|
|
55
|
+
type BooleanColumnOptions<TRow> = SharedColumnOptions<TRow, boolean> & {
|
|
56
|
+
trueLabel?: string;
|
|
57
|
+
falseLabel?: string;
|
|
58
|
+
};
|
|
59
|
+
type DerivedDateColumnOptions<TRow> = Omit<DerivedDateColumnSchema<TRow>, 'kind' | 'type'>;
|
|
60
|
+
type DerivedCategoryColumnOptions<TRow> = Omit<DerivedCategoryColumnSchema<TRow>, 'kind' | 'type'>;
|
|
61
|
+
type DerivedBooleanColumnOptions<TRow> = Omit<DerivedBooleanColumnSchema<TRow>, 'kind' | 'type'>;
|
|
62
|
+
type DerivedNumberColumnOptions<TRow> = Omit<DerivedNumberColumnSchema<TRow>, 'kind' | 'type'>;
|
|
63
|
+
type RawFieldColumnEntry<TRow, TFieldId extends InferableFieldKey<TRow>, TColumn> = {
|
|
64
|
+
readonly kind: 'raw';
|
|
65
|
+
readonly id: TFieldId;
|
|
66
|
+
readonly column: TColumn;
|
|
67
|
+
};
|
|
68
|
+
type ExcludedFieldColumnEntry<TRow, TFieldId extends InferableFieldKey<TRow>> = {
|
|
69
|
+
readonly kind: 'exclude';
|
|
70
|
+
readonly id: TFieldId;
|
|
71
|
+
readonly column: false;
|
|
72
|
+
};
|
|
73
|
+
type DerivedColumnEntry<TRow, TColumnId extends string, TColumn extends DerivedDateColumnSchema<TRow> | DerivedCategoryColumnSchema<TRow> | DerivedBooleanColumnSchema<TRow> | DerivedNumberColumnSchema<TRow>> = {
|
|
74
|
+
readonly kind: 'derived';
|
|
75
|
+
readonly id: TColumnId;
|
|
76
|
+
readonly column: TColumn;
|
|
77
|
+
};
|
|
78
|
+
type SchemaColumnEntry<TRow> = RawFieldColumnEntry<TRow, InferableFieldKey<TRow>, unknown> | ExcludedFieldColumnEntry<TRow, InferableFieldKey<TRow>> | DerivedColumnEntry<TRow, string, DerivedDateColumnSchema<TRow> | DerivedCategoryColumnSchema<TRow> | DerivedBooleanColumnSchema<TRow> | DerivedNumberColumnSchema<TRow>>;
|
|
79
|
+
type ValidateColumnEntries<TEntries extends readonly unknown[]> = [DuplicateColumnIds<TEntries>] extends [never] ? unknown : DuplicateColumnIdError<Extract<DuplicateColumnIds<TEntries>, string>>;
|
|
80
|
+
type ColumnsFromEntries<TRow, TEntries extends readonly SchemaColumnEntry<TRow>[]> = Simplify<{ [TEntry in TEntries[number] as TEntry['id']]: TEntry['column'] }>;
|
|
81
|
+
interface ColumnHelper<TRow> {
|
|
82
|
+
/**
|
|
83
|
+
* Override one existing raw field using the raw-field schema surface.
|
|
84
|
+
*
|
|
85
|
+
* Use this when you want the full hint surface for a field, including type
|
|
86
|
+
* overrides on string/number columns.
|
|
87
|
+
*/
|
|
88
|
+
field<const TFieldId extends InferableFieldKey<TRow>>(id: TFieldId, options?: FieldColumnOptions<TRow, TFieldId>): RawFieldColumnEntry<TRow, TFieldId, FieldColumnOptions<TRow, TFieldId>>;
|
|
89
|
+
/**
|
|
90
|
+
* Treat one raw field as a date column.
|
|
91
|
+
*
|
|
92
|
+
* Supports raw `string`, `number`, and `Date` fields that should behave as a
|
|
93
|
+
* time-series axis.
|
|
94
|
+
*/
|
|
95
|
+
date<const TFieldId extends DateLikeFieldKey<TRow>>(id: TFieldId, options?: DateColumnOptions<TRow>): RawFieldColumnEntry<TRow, TFieldId, {
|
|
96
|
+
type: 'date';
|
|
97
|
+
} & DateColumnOptions<TRow>>;
|
|
98
|
+
/**
|
|
99
|
+
* Treat one raw string field as a category column.
|
|
100
|
+
*
|
|
101
|
+
* Category columns are eligible for the X-axis, grouping, and filters.
|
|
102
|
+
*/
|
|
103
|
+
category<const TFieldId extends StringFieldKey<TRow>>(id: TFieldId, options?: CategoryColumnOptions<TRow>): RawFieldColumnEntry<TRow, TFieldId, {
|
|
104
|
+
type: 'category';
|
|
105
|
+
} & CategoryColumnOptions<TRow>>;
|
|
106
|
+
/**
|
|
107
|
+
* Treat one raw numeric field as a number column.
|
|
108
|
+
*
|
|
109
|
+
* Number columns participate in metric aggregation.
|
|
110
|
+
*/
|
|
111
|
+
number<const TFieldId extends NumberFieldKey<TRow>>(id: TFieldId, options?: NumberColumnOptions<TRow>): RawFieldColumnEntry<TRow, TFieldId, {
|
|
112
|
+
type: 'number';
|
|
113
|
+
} & NumberColumnOptions<TRow>>;
|
|
114
|
+
/**
|
|
115
|
+
* Treat one raw boolean field as a boolean column.
|
|
116
|
+
*
|
|
117
|
+
* Boolean columns are useful for grouping and filters.
|
|
118
|
+
*/
|
|
119
|
+
boolean<const TFieldId extends BooleanFieldKey<TRow>>(id: TFieldId, options?: BooleanColumnOptions<TRow>): RawFieldColumnEntry<TRow, TFieldId, {
|
|
120
|
+
type: 'boolean';
|
|
121
|
+
} & BooleanColumnOptions<TRow>>;
|
|
122
|
+
/**
|
|
123
|
+
* Remove one raw field from the chart schema entirely.
|
|
124
|
+
*/
|
|
125
|
+
exclude<const TFieldId extends InferableFieldKey<TRow>>(id: TFieldId): ExcludedFieldColumnEntry<TRow, TFieldId>;
|
|
126
|
+
/** Helper group for declaring new derived columns. */
|
|
127
|
+
readonly derived: {
|
|
128
|
+
/**
|
|
129
|
+
* Add one derived date column.
|
|
130
|
+
*
|
|
131
|
+
* The accessor receives the original row type.
|
|
132
|
+
*/
|
|
133
|
+
date<const TColumnId extends string>(id: DerivedColumnId<TRow, TColumnId>, options: DerivedDateColumnOptions<TRow>): DerivedColumnEntry<TRow, TColumnId, {
|
|
134
|
+
kind: 'derived';
|
|
135
|
+
type: 'date';
|
|
136
|
+
} & DerivedDateColumnOptions<TRow>>;
|
|
137
|
+
/**
|
|
138
|
+
* Add one derived category column.
|
|
139
|
+
*
|
|
140
|
+
* The accessor receives the original row type.
|
|
141
|
+
*/
|
|
142
|
+
category<const TColumnId extends string>(id: DerivedColumnId<TRow, TColumnId>, options: DerivedCategoryColumnOptions<TRow>): DerivedColumnEntry<TRow, TColumnId, {
|
|
143
|
+
kind: 'derived';
|
|
144
|
+
type: 'category';
|
|
145
|
+
} & DerivedCategoryColumnOptions<TRow>>;
|
|
146
|
+
/**
|
|
147
|
+
* Add one derived boolean column.
|
|
148
|
+
*
|
|
149
|
+
* The accessor receives the original row type.
|
|
150
|
+
*/
|
|
151
|
+
boolean<const TColumnId extends string>(id: DerivedColumnId<TRow, TColumnId>, options: DerivedBooleanColumnOptions<TRow>): DerivedColumnEntry<TRow, TColumnId, {
|
|
152
|
+
kind: 'derived';
|
|
153
|
+
type: 'boolean';
|
|
154
|
+
} & DerivedBooleanColumnOptions<TRow>>;
|
|
155
|
+
/**
|
|
156
|
+
* Add one derived numeric column.
|
|
157
|
+
*
|
|
158
|
+
* The accessor receives the original row type.
|
|
159
|
+
*/
|
|
160
|
+
number<const TColumnId extends string>(id: DerivedColumnId<TRow, TColumnId>, options: DerivedNumberColumnOptions<TRow>): DerivedColumnEntry<TRow, TColumnId, {
|
|
161
|
+
kind: 'derived';
|
|
162
|
+
type: 'number';
|
|
163
|
+
} & DerivedNumberColumnOptions<TRow>>;
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
type SelectableControlBuilderConfig<TBuilder> = TBuilder extends SelectableControlBuilder<any, any, any, any, infer TConfig> ? TConfig : never;
|
|
167
|
+
interface SelectableControlBuilder<TOption extends string, TSupportsDefault extends boolean, TAllowedOption extends TOption = TOption, THiddenOption extends TOption = never, TConfig extends object = {}> {
|
|
168
|
+
/**
|
|
169
|
+
* Keep only the listed options visible and selectable.
|
|
170
|
+
*
|
|
171
|
+
* Calling `allowed(...)` again replaces the previous allowed list.
|
|
172
|
+
*/
|
|
173
|
+
allowed<const TOptions extends NonEmptyReadonlyArray<TOption>>(...options: TOptions): SelectableControlBuilder<TOption, TSupportsDefault, Extract<TOptions[number], TOption>, Extract<THiddenOption, Extract<TOptions[number], TOption>>, ReplaceConfigValue<TConfig, 'allowed', TOptions>>;
|
|
174
|
+
/**
|
|
175
|
+
* Hide one or more options after the allowed set is resolved.
|
|
176
|
+
*
|
|
177
|
+
* Hidden options can still exist in the broader schema; they simply stop
|
|
178
|
+
* appearing in the public control surface.
|
|
179
|
+
*/
|
|
180
|
+
hidden<const TOptions extends NonEmptyReadonlyArray<Exclude<TAllowedOption, THiddenOption>>>(...options: TOptions): SelectableControlBuilder<TOption, TSupportsDefault, TAllowedOption, THiddenOption | Extract<TOptions[number], TOption>, AppendConfigValue<TConfig, 'hidden', TOptions[number]>>;
|
|
181
|
+
/**
|
|
182
|
+
* Set the preferred fallback option when the current selection is missing or
|
|
183
|
+
* invalid for the current schema.
|
|
184
|
+
*/
|
|
185
|
+
default: TSupportsDefault extends true ? <const TDefaultOption extends Exclude<TAllowedOption, THiddenOption>>(option: TDefaultOption) => SelectableControlBuilder<TOption, TSupportsDefault, TAllowedOption, THiddenOption, ReplaceConfigValue<TConfig, 'default', TDefaultOption>> : never;
|
|
186
|
+
}
|
|
187
|
+
type MetricBuilderConfig<TBuilder> = TBuilder extends MetricBuilder<any, any, any, infer TConfig> ? TConfig : never;
|
|
188
|
+
interface MetricBuilder<TMetricColumnId extends string, TAllowedMetric = never, THiddenMetric = never, TConfig extends object = {}> {
|
|
189
|
+
/**
|
|
190
|
+
* Allow the built-in row count metric.
|
|
191
|
+
*/
|
|
192
|
+
count(): MetricBuilder<TMetricColumnId, TAllowedMetric | CountMetric, THiddenMetric, AppendConfigValue<TConfig, 'allowed', CountMetric>>;
|
|
193
|
+
/**
|
|
194
|
+
* Allow one or more numeric aggregates for the given column.
|
|
195
|
+
*
|
|
196
|
+
* Example: `aggregate('salary', 'sum', 'avg')`
|
|
197
|
+
*/
|
|
198
|
+
aggregate<const TColumnId extends TMetricColumnId, const TAggregates extends NonEmptyReadonlyArray<NumericAggregateFunction>>(columnId: TColumnId, ...aggregates: TAggregates): MetricBuilder<TMetricColumnId, TAllowedMetric | AggregateMetricUnionFromSelection<TColumnId, TAggregates>, THiddenMetric, AppendConfigValue<TConfig, 'allowed', {
|
|
199
|
+
kind: 'aggregate';
|
|
200
|
+
columnId: TColumnId;
|
|
201
|
+
aggregate: AggregateSelectionFromList<TAggregates>;
|
|
202
|
+
}>>;
|
|
203
|
+
/**
|
|
204
|
+
* Hide the row count metric from the public metric picker.
|
|
205
|
+
*/
|
|
206
|
+
hideCount(...args: CountMetric extends VisibleMetricUnion<TMetricColumnId, TAllowedMetric, THiddenMetric> ? [] : [never]): MetricBuilder<TMetricColumnId, TAllowedMetric, THiddenMetric | CountMetric, AppendConfigValue<TConfig, 'hidden', CountMetric>>;
|
|
207
|
+
/**
|
|
208
|
+
* Hide one or more aggregate metrics for a numeric column.
|
|
209
|
+
*/
|
|
210
|
+
hideAggregate<const TColumnId extends MetricColumnIdFromMetric<VisibleMetricUnion<TMetricColumnId, TAllowedMetric, THiddenMetric>>, const TAggregates extends NonEmptyReadonlyArray<MetricAggregateFromMetric<VisibleMetricUnion<TMetricColumnId, TAllowedMetric, THiddenMetric>, TColumnId>>>(columnId: TColumnId, ...aggregates: TAggregates): MetricBuilder<TMetricColumnId, TAllowedMetric, THiddenMetric | AggregateMetricUnionFromSelection<TColumnId, TAggregates>, AppendManyConfigValues<TConfig, 'hidden', AggregateMetricTupleFromSelection<TColumnId, TAggregates>>>;
|
|
211
|
+
/**
|
|
212
|
+
* Prefer the row count metric when the current metric becomes invalid.
|
|
213
|
+
*/
|
|
214
|
+
defaultCount(...args: CountMetric extends VisibleMetricUnion<TMetricColumnId, TAllowedMetric, THiddenMetric> ? [] : [never]): MetricBuilder<TMetricColumnId, TAllowedMetric, THiddenMetric, ReplaceConfigValue<TConfig, 'default', CountMetric>>;
|
|
215
|
+
/**
|
|
216
|
+
* Prefer one aggregate metric when the current metric becomes invalid.
|
|
217
|
+
*/
|
|
218
|
+
defaultAggregate<const TColumnId extends MetricColumnIdFromMetric<VisibleMetricUnion<TMetricColumnId, TAllowedMetric, THiddenMetric>>, const TAggregate extends MetricAggregateFromMetric<VisibleMetricUnion<TMetricColumnId, TAllowedMetric, THiddenMetric>, TColumnId>>(columnId: TColumnId, aggregate: TAggregate): MetricBuilder<TMetricColumnId, TAllowedMetric, THiddenMetric, ReplaceConfigValue<TConfig, 'default', {
|
|
219
|
+
kind: 'aggregate';
|
|
220
|
+
columnId: TColumnId;
|
|
221
|
+
aggregate: TAggregate;
|
|
222
|
+
}>>;
|
|
223
|
+
}
|
|
224
|
+
type SchemaFromBuilder<TColumns extends Record<string, unknown> | undefined, TXAxis extends XAxisConfig<any> | undefined, TGroupBy extends GroupByConfig<any> | undefined, TFilters extends FiltersConfig<any> | undefined, TMetric extends MetricConfig<any> | undefined, TChartType extends ChartTypeConfig | undefined, TTimeBucket extends TimeBucketConfig | undefined, TConnectNulls extends boolean | undefined> = {
|
|
225
|
+
columns?: Extract<TColumns, Record<string, unknown> | undefined>;
|
|
226
|
+
xAxis?: Extract<TXAxis, XAxisConfig<any> | undefined>;
|
|
227
|
+
groupBy?: Extract<TGroupBy, GroupByConfig<any> | undefined>;
|
|
228
|
+
filters?: Extract<TFilters, FiltersConfig<any> | undefined>;
|
|
229
|
+
metric?: Extract<TMetric, MetricConfig<any> | undefined>;
|
|
230
|
+
chartType?: Extract<TChartType, ChartTypeConfig | undefined>;
|
|
231
|
+
timeBucket?: Extract<TTimeBucket, TimeBucketConfig | undefined>;
|
|
232
|
+
connectNulls?: Extract<TConnectNulls, boolean | undefined>;
|
|
233
|
+
};
|
|
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
|
+
//#endregion
|
|
279
|
+
export { ChartSchemaBuilder };
|