@matthieumordrel/chart-studio 0.2.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/core/chart-capabilities.d.mts +48 -0
- package/dist/core/chart-capabilities.mjs +55 -0
- package/dist/core/{colors.d.ts → colors.d.mts} +5 -3
- package/dist/core/colors.mjs +55 -0
- package/dist/core/config-utils.mjs +79 -0
- package/dist/core/date-utils.mjs +49 -0
- package/dist/core/define-chart-schema.d.mts +106 -0
- package/dist/core/define-chart-schema.mjs +47 -0
- package/dist/core/formatting.mjs +349 -0
- package/dist/core/infer-columns.d.mts +9 -0
- package/dist/core/infer-columns.mjs +481 -0
- package/dist/core/metric-utils.d.mts +13 -0
- package/dist/core/metric-utils.mjs +121 -0
- package/dist/core/pipeline-data-points.mjs +212 -0
- package/dist/core/pipeline-helpers.mjs +85 -0
- package/dist/core/{pipeline.d.ts → pipeline.d.mts} +21 -24
- package/dist/core/pipeline.mjs +153 -0
- package/dist/core/types.d.mts +957 -0
- package/dist/core/use-chart-options.d.mts +64 -0
- package/dist/core/use-chart-options.mjs +7 -0
- package/dist/core/use-chart-resolvers.mjs +34 -0
- package/dist/core/{use-chart.d.ts → use-chart.d.mts} +12 -9
- package/dist/core/use-chart.mjs +299 -0
- package/dist/index.d.mts +10 -0
- package/dist/index.mjs +8 -0
- package/dist/ui/chart-axis-ticks.mjs +65 -0
- package/dist/ui/{chart-canvas.d.ts → chart-canvas.d.mts} +13 -6
- package/dist/ui/chart-canvas.mjs +461 -0
- package/dist/ui/chart-context.d.mts +92 -0
- package/dist/ui/chart-context.mjs +112 -0
- package/dist/ui/{chart-date-range-badge.d.ts → chart-date-range-badge.d.mts} +10 -4
- package/dist/ui/chart-date-range-badge.mjs +49 -0
- package/dist/ui/chart-date-range-panel.d.mts +18 -0
- package/dist/ui/chart-date-range-panel.mjs +208 -0
- package/dist/ui/{chart-date-range.d.ts → chart-date-range.d.mts} +10 -4
- package/dist/ui/chart-date-range.mjs +67 -0
- package/dist/ui/chart-debug.d.mts +17 -0
- package/dist/ui/chart-debug.mjs +169 -0
- package/dist/ui/chart-dropdown.mjs +92 -0
- package/dist/ui/{chart-filters-panel.d.ts → chart-filters-panel.d.mts} +12 -5
- package/dist/ui/chart-filters-panel.mjs +132 -0
- package/dist/ui/{chart-filters.d.ts → chart-filters.d.mts} +10 -4
- package/dist/ui/chart-filters.mjs +48 -0
- package/dist/ui/chart-group-by-selector.d.mts +14 -0
- package/dist/ui/chart-group-by-selector.mjs +29 -0
- package/dist/ui/{chart-metric-panel.d.ts → chart-metric-panel.d.mts} +12 -5
- package/dist/ui/chart-metric-panel.mjs +172 -0
- package/dist/ui/chart-metric-selector.d.mts +16 -0
- package/dist/ui/chart-metric-selector.mjs +50 -0
- package/dist/ui/chart-select.mjs +62 -0
- package/dist/ui/{chart-source-switcher.d.ts → chart-source-switcher.d.mts} +10 -4
- package/dist/ui/chart-source-switcher.mjs +54 -0
- package/dist/ui/chart-time-bucket-selector.d.mts +15 -0
- package/dist/ui/chart-time-bucket-selector.mjs +34 -0
- package/dist/ui/chart-toolbar-overflow.d.mts +28 -0
- package/dist/ui/chart-toolbar-overflow.mjs +209 -0
- package/dist/ui/chart-toolbar.d.mts +29 -0
- package/dist/ui/chart-toolbar.mjs +56 -0
- package/dist/ui/chart-type-selector.d.mts +14 -0
- package/dist/ui/chart-type-selector.mjs +33 -0
- package/dist/ui/chart-x-axis-selector.d.mts +14 -0
- package/dist/ui/chart-x-axis-selector.mjs +25 -0
- package/dist/ui/index.d.mts +19 -0
- package/dist/ui/index.mjs +18 -0
- package/dist/ui/toolbar-types.d.mts +7 -0
- package/dist/ui/toolbar-types.mjs +83 -0
- package/package.json +11 -10
- package/dist/core/chart-capabilities.d.ts +0 -60
- package/dist/core/chart-capabilities.d.ts.map +0 -1
- package/dist/core/chart-capabilities.js +0 -54
- package/dist/core/colors.d.ts.map +0 -1
- package/dist/core/colors.js +0 -54
- package/dist/core/config-utils.d.ts +0 -43
- package/dist/core/config-utils.d.ts.map +0 -1
- package/dist/core/config-utils.js +0 -80
- package/dist/core/date-utils.d.ts +0 -29
- package/dist/core/date-utils.d.ts.map +0 -1
- package/dist/core/date-utils.js +0 -58
- package/dist/core/define-chart-schema.d.ts +0 -105
- package/dist/core/define-chart-schema.d.ts.map +0 -1
- package/dist/core/define-chart-schema.js +0 -44
- package/dist/core/formatting.d.ts +0 -47
- package/dist/core/formatting.d.ts.map +0 -1
- package/dist/core/formatting.js +0 -396
- package/dist/core/index.d.ts +0 -17
- package/dist/core/index.d.ts.map +0 -1
- package/dist/core/index.js +0 -12
- package/dist/core/infer-columns.d.ts +0 -6
- package/dist/core/infer-columns.d.ts.map +0 -1
- package/dist/core/infer-columns.js +0 -512
- package/dist/core/metric-utils.d.ts +0 -43
- package/dist/core/metric-utils.d.ts.map +0 -1
- package/dist/core/metric-utils.js +0 -141
- package/dist/core/pipeline-data-points.d.ts +0 -23
- package/dist/core/pipeline-data-points.d.ts.map +0 -1
- package/dist/core/pipeline-data-points.js +0 -235
- package/dist/core/pipeline-helpers.d.ts +0 -38
- package/dist/core/pipeline-helpers.d.ts.map +0 -1
- package/dist/core/pipeline-helpers.js +0 -97
- package/dist/core/pipeline.d.ts.map +0 -1
- package/dist/core/pipeline.js +0 -156
- package/dist/core/types.d.ts +0 -1109
- package/dist/core/types.d.ts.map +0 -1
- package/dist/core/types.js +0 -14
- package/dist/core/use-chart-options.d.ts +0 -66
- package/dist/core/use-chart-options.d.ts.map +0 -1
- package/dist/core/use-chart-options.js +0 -4
- package/dist/core/use-chart-resolvers.d.ts +0 -14
- package/dist/core/use-chart-resolvers.d.ts.map +0 -1
- package/dist/core/use-chart-resolvers.js +0 -41
- package/dist/core/use-chart.d.ts.map +0 -1
- package/dist/core/use-chart.js +0 -265
- package/dist/index.d.ts +0 -36
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -35
- package/dist/ui/chart-axis-ticks.d.ts +0 -35
- package/dist/ui/chart-axis-ticks.d.ts.map +0 -1
- package/dist/ui/chart-axis-ticks.js +0 -79
- package/dist/ui/chart-canvas.d.ts.map +0 -1
- package/dist/ui/chart-canvas.js +0 -337
- package/dist/ui/chart-context.d.ts +0 -89
- package/dist/ui/chart-context.d.ts.map +0 -1
- package/dist/ui/chart-context.js +0 -128
- package/dist/ui/chart-date-range-badge.d.ts.map +0 -1
- package/dist/ui/chart-date-range-badge.js +0 -30
- package/dist/ui/chart-date-range-panel.d.ts +0 -25
- package/dist/ui/chart-date-range-panel.d.ts.map +0 -1
- package/dist/ui/chart-date-range-panel.js +0 -125
- package/dist/ui/chart-date-range.d.ts.map +0 -1
- package/dist/ui/chart-date-range.js +0 -37
- package/dist/ui/chart-debug.d.ts +0 -10
- package/dist/ui/chart-debug.d.ts.map +0 -1
- package/dist/ui/chart-debug.js +0 -126
- package/dist/ui/chart-dropdown.d.ts +0 -35
- package/dist/ui/chart-dropdown.d.ts.map +0 -1
- package/dist/ui/chart-dropdown.js +0 -76
- package/dist/ui/chart-filters-panel.d.ts.map +0 -1
- package/dist/ui/chart-filters-panel.js +0 -46
- package/dist/ui/chart-filters.d.ts.map +0 -1
- package/dist/ui/chart-filters.js +0 -26
- package/dist/ui/chart-group-by-selector.d.ts +0 -8
- package/dist/ui/chart-group-by-selector.d.ts.map +0 -1
- package/dist/ui/chart-group-by-selector.js +0 -19
- package/dist/ui/chart-metric-panel.d.ts.map +0 -1
- package/dist/ui/chart-metric-panel.js +0 -118
- package/dist/ui/chart-metric-selector.d.ts +0 -10
- package/dist/ui/chart-metric-selector.d.ts.map +0 -1
- package/dist/ui/chart-metric-selector.js +0 -27
- package/dist/ui/chart-select.d.ts +0 -25
- package/dist/ui/chart-select.d.ts.map +0 -1
- package/dist/ui/chart-select.js +0 -35
- package/dist/ui/chart-source-switcher.d.ts.map +0 -1
- package/dist/ui/chart-source-switcher.js +0 -31
- package/dist/ui/chart-time-bucket-selector.d.ts +0 -9
- package/dist/ui/chart-time-bucket-selector.d.ts.map +0 -1
- package/dist/ui/chart-time-bucket-selector.js +0 -25
- package/dist/ui/chart-toolbar-overflow.d.ts +0 -29
- package/dist/ui/chart-toolbar-overflow.d.ts.map +0 -1
- package/dist/ui/chart-toolbar-overflow.js +0 -109
- package/dist/ui/chart-toolbar.d.ts +0 -45
- package/dist/ui/chart-toolbar.d.ts.map +0 -1
- package/dist/ui/chart-toolbar.js +0 -44
- package/dist/ui/chart-type-selector.d.ts +0 -8
- package/dist/ui/chart-type-selector.d.ts.map +0 -1
- package/dist/ui/chart-type-selector.js +0 -22
- package/dist/ui/chart-x-axis-selector.d.ts +0 -8
- package/dist/ui/chart-x-axis-selector.d.ts.map +0 -1
- package/dist/ui/chart-x-axis-selector.js +0 -14
- package/dist/ui/index.d.ts +0 -25
- package/dist/ui/index.d.ts.map +0 -1
- package/dist/ui/index.js +0 -23
- package/dist/ui/toolbar-types.d.ts +0 -43
- package/dist/ui/toolbar-types.d.ts.map +0 -1
- package/dist/ui/toolbar-types.js +0 -50
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Default metric used when no numeric aggregation is selected.
|
|
3
|
-
*/
|
|
4
|
-
export const DEFAULT_METRIC = { kind: 'count' };
|
|
5
|
-
/**
|
|
6
|
-
* Type guard for aggregate metrics.
|
|
7
|
-
*/
|
|
8
|
-
export function isAggregateMetric(metric) {
|
|
9
|
-
return metric.kind === 'aggregate';
|
|
10
|
-
}
|
|
11
|
-
/**
|
|
12
|
-
* Compare two metric definitions for semantic equality.
|
|
13
|
-
*/
|
|
14
|
-
export function isSameMetric(left, right) {
|
|
15
|
-
if (left.kind !== right.kind) {
|
|
16
|
-
return false;
|
|
17
|
-
}
|
|
18
|
-
if (left.kind === 'count') {
|
|
19
|
-
return true;
|
|
20
|
-
}
|
|
21
|
-
return (right.kind === 'aggregate'
|
|
22
|
-
&& left.columnId === right.columnId
|
|
23
|
-
&& left.aggregate === right.aggregate
|
|
24
|
-
&& (left.includeZeros ?? true) === (right.includeZeros ?? true));
|
|
25
|
-
}
|
|
26
|
-
/**
|
|
27
|
-
* Human-readable label for a numeric aggregate.
|
|
28
|
-
*/
|
|
29
|
-
export function getAggregateMetricLabel(columnLabel, aggregate) {
|
|
30
|
-
switch (aggregate) {
|
|
31
|
-
case 'sum':
|
|
32
|
-
return `Sum of ${columnLabel}`;
|
|
33
|
-
case 'avg':
|
|
34
|
-
return `Avg ${columnLabel}`;
|
|
35
|
-
case 'min':
|
|
36
|
-
return `Min ${columnLabel}`;
|
|
37
|
-
case 'max':
|
|
38
|
-
return `Max ${columnLabel}`;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
/**
|
|
42
|
-
* Human-readable label for a metric.
|
|
43
|
-
*/
|
|
44
|
-
export function getMetricLabel(metric, columns) {
|
|
45
|
-
if (!isAggregateMetric(metric)) {
|
|
46
|
-
return 'Count';
|
|
47
|
-
}
|
|
48
|
-
const column = columns.find((candidate) => candidate.type === 'number' && candidate.id === metric.columnId);
|
|
49
|
-
if (!column) {
|
|
50
|
-
return 'Count';
|
|
51
|
-
}
|
|
52
|
-
return getAggregateMetricLabel(column.label, metric.aggregate);
|
|
53
|
-
}
|
|
54
|
-
/**
|
|
55
|
-
* Build the metric options available for a set of columns.
|
|
56
|
-
*/
|
|
57
|
-
export function buildAvailableMetrics(columns) {
|
|
58
|
-
const metrics = [DEFAULT_METRIC];
|
|
59
|
-
const numberColumns = columns.filter((column) => column.type === 'number');
|
|
60
|
-
for (const column of numberColumns) {
|
|
61
|
-
metrics.push({ kind: 'aggregate', columnId: column.id, aggregate: 'sum' }, { kind: 'aggregate', columnId: column.id, aggregate: 'avg' }, { kind: 'aggregate', columnId: column.id, aggregate: 'min' }, { kind: 'aggregate', columnId: column.id, aggregate: 'max' });
|
|
62
|
-
}
|
|
63
|
-
return metrics;
|
|
64
|
-
}
|
|
65
|
-
/**
|
|
66
|
-
* Expand declarative metric restriction entries into the normalized metric list
|
|
67
|
-
* used by the runtime and typed chart state.
|
|
68
|
-
*/
|
|
69
|
-
export function normalizeMetricAllowances(allowedMetrics) {
|
|
70
|
-
if (!allowedMetrics) {
|
|
71
|
-
return undefined;
|
|
72
|
-
}
|
|
73
|
-
const normalized = [];
|
|
74
|
-
for (const allowedMetric of allowedMetrics) {
|
|
75
|
-
if (allowedMetric.kind === 'count') {
|
|
76
|
-
normalized.push(allowedMetric);
|
|
77
|
-
continue;
|
|
78
|
-
}
|
|
79
|
-
const aggregates = Array.isArray(allowedMetric.aggregate)
|
|
80
|
-
? allowedMetric.aggregate
|
|
81
|
-
: [allowedMetric.aggregate];
|
|
82
|
-
for (const aggregate of aggregates) {
|
|
83
|
-
normalized.push({
|
|
84
|
-
kind: 'aggregate',
|
|
85
|
-
columnId: allowedMetric.columnId,
|
|
86
|
-
aggregate,
|
|
87
|
-
includeZeros: allowedMetric.includeZeros,
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
return normalized;
|
|
92
|
-
}
|
|
93
|
-
/**
|
|
94
|
-
* Apply a declarative metric whitelist to the inferred metric options.
|
|
95
|
-
*
|
|
96
|
-
* Metrics are matched structurally so callers can safely pass fresh object
|
|
97
|
-
* literals in `config.metric.allowed`. The final order follows the declarative
|
|
98
|
-
* restriction list, so the first allowed metric becomes the default.
|
|
99
|
-
*/
|
|
100
|
-
export function restrictAvailableMetrics(metrics, config) {
|
|
101
|
-
const normalizedAllowedMetrics = normalizeMetricAllowances(config?.allowed);
|
|
102
|
-
if (!normalizedAllowedMetrics) {
|
|
103
|
-
const hiddenMetrics = config?.hidden;
|
|
104
|
-
if (!hiddenMetrics || hiddenMetrics.length === 0) {
|
|
105
|
-
return [...metrics];
|
|
106
|
-
}
|
|
107
|
-
const visibleMetrics = metrics.filter(metric => hiddenMetrics.every(hiddenMetric => !isSameMetric(metric, hiddenMetric)));
|
|
108
|
-
return visibleMetrics.length > 0 ? visibleMetrics : [metrics[0] ?? DEFAULT_METRIC];
|
|
109
|
-
}
|
|
110
|
-
const allowedMetrics = normalizedAllowedMetrics.filter(allowedMetric => metrics.some(metric => isSameMetric(metric, allowedMetric)));
|
|
111
|
-
const hiddenMetrics = config?.hidden;
|
|
112
|
-
const restricted = !hiddenMetrics || hiddenMetrics.length === 0
|
|
113
|
-
? allowedMetrics
|
|
114
|
-
: allowedMetrics.filter(metric => hiddenMetrics.every(hiddenMetric => !isSameMetric(metric, hiddenMetric)));
|
|
115
|
-
// A chart always needs one active metric. Fall back to the inferred default if
|
|
116
|
-
// the whitelist ends up empty or does not match the active source.
|
|
117
|
-
return restricted.length > 0 ? restricted : [metrics[0] ?? DEFAULT_METRIC];
|
|
118
|
-
}
|
|
119
|
-
/**
|
|
120
|
-
* Validate a metric against the active source.
|
|
121
|
-
*/
|
|
122
|
-
export function resolveMetric(metric, columns, availableMetrics, configuredDefaultMetric) {
|
|
123
|
-
if (availableMetrics && availableMetrics.length > 0) {
|
|
124
|
-
const selectedMetric = availableMetrics.find(candidate => isSameMetric(candidate, metric));
|
|
125
|
-
if (selectedMetric) {
|
|
126
|
-
return selectedMetric;
|
|
127
|
-
}
|
|
128
|
-
const defaultMetric = configuredDefaultMetric
|
|
129
|
-
? availableMetrics.find(candidate => isSameMetric(candidate, configuredDefaultMetric))
|
|
130
|
-
: undefined;
|
|
131
|
-
return defaultMetric ?? availableMetrics[0];
|
|
132
|
-
}
|
|
133
|
-
if (!isAggregateMetric(metric)) {
|
|
134
|
-
return DEFAULT_METRIC;
|
|
135
|
-
}
|
|
136
|
-
const column = columns.find((candidate) => candidate.type === 'number' && candidate.id === metric.columnId);
|
|
137
|
-
if (!column) {
|
|
138
|
-
return DEFAULT_METRIC;
|
|
139
|
-
}
|
|
140
|
-
return metric;
|
|
141
|
-
}
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Bucket and aggregation steps for the chart transformation pipeline.
|
|
3
|
-
*/
|
|
4
|
-
import type { ChartColumn, Metric, NumberColumn, TimeBucket, TransformedDataPoint } from './types.js';
|
|
5
|
-
type DataPointResult = {
|
|
6
|
-
data: TransformedDataPoint[];
|
|
7
|
-
groups: string[];
|
|
8
|
-
};
|
|
9
|
-
/**
|
|
10
|
-
* Build chart-ready data points by bucketing the X-axis, pivoting groups, and
|
|
11
|
-
* aggregating the selected metric.
|
|
12
|
-
*
|
|
13
|
-
* @param items - Filtered source data
|
|
14
|
-
* @param xColumn - Active X-axis column
|
|
15
|
-
* @param groupByColumn - Optional group-by column
|
|
16
|
-
* @param metric - Selected metric configuration
|
|
17
|
-
* @param numberColumns - Number columns available for metric lookup
|
|
18
|
-
* @param timeBucket - Time bucket used for date X-axes
|
|
19
|
-
* @returns Aggregated chart data points and group labels
|
|
20
|
-
*/
|
|
21
|
-
export declare function buildDataPoints<T, TColumnId extends string>(items: T[], xColumn: ChartColumn<T, TColumnId>, groupByColumn: ChartColumn<T, TColumnId> | null, metric: Metric<TColumnId>, numberColumns: NumberColumn<T, TColumnId>[], timeBucket: TimeBucket): DataPointResult;
|
|
22
|
-
export {};
|
|
23
|
-
//# sourceMappingURL=pipeline-data-points.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"pipeline-data-points.d.ts","sourceRoot":"","sources":["../../src/core/pipeline-data-points.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,OAAO,KAAK,EACV,WAAW,EAEX,MAAM,EACN,YAAY,EACZ,UAAU,EACV,oBAAoB,EACrB,MAAM,YAAY,CAAA;AAOnB,KAAK,eAAe,GAAG;IACrB,IAAI,EAAE,oBAAoB,EAAE,CAAA;IAC5B,MAAM,EAAE,MAAM,EAAE,CAAA;CACjB,CAAA;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,SAAS,SAAS,MAAM,EACzD,KAAK,EAAE,CAAC,EAAE,EACV,OAAO,EAAE,WAAW,CAAC,CAAC,EAAE,SAAS,CAAC,EAClC,aAAa,EAAE,WAAW,CAAC,CAAC,EAAE,SAAS,CAAC,GAAG,IAAI,EAC/C,MAAM,EAAE,MAAM,CAAC,SAAS,CAAC,EACzB,aAAa,EAAE,YAAY,CAAC,CAAC,EAAE,SAAS,CAAC,EAAE,EAC3C,UAAU,EAAE,UAAU,GACrB,eAAe,CAkBjB"}
|
|
@@ -1,235 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Bucket and aggregation steps for the chart transformation pipeline.
|
|
3
|
-
*/
|
|
4
|
-
import { isAggregateMetric } from './metric-utils.js';
|
|
5
|
-
import { aggregate, dateBucketKey, dateBucketLabel, getStringValue } from './pipeline-helpers.js';
|
|
6
|
-
/**
|
|
7
|
-
* Build chart-ready data points by bucketing the X-axis, pivoting groups, and
|
|
8
|
-
* aggregating the selected metric.
|
|
9
|
-
*
|
|
10
|
-
* @param items - Filtered source data
|
|
11
|
-
* @param xColumn - Active X-axis column
|
|
12
|
-
* @param groupByColumn - Optional group-by column
|
|
13
|
-
* @param metric - Selected metric configuration
|
|
14
|
-
* @param numberColumns - Number columns available for metric lookup
|
|
15
|
-
* @param timeBucket - Time bucket used for date X-axes
|
|
16
|
-
* @returns Aggregated chart data points and group labels
|
|
17
|
-
*/
|
|
18
|
-
export function buildDataPoints(items, xColumn, groupByColumn, metric, numberColumns, timeBucket) {
|
|
19
|
-
const groupSet = new Set();
|
|
20
|
-
if (groupByColumn) {
|
|
21
|
-
for (const item of items) {
|
|
22
|
-
groupSet.add(getStringValue(item, groupByColumn));
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
const groups = groupByColumn ? [...groupSet].toSorted() : ['value'];
|
|
26
|
-
const metricColumn = isAggregateMetric(metric)
|
|
27
|
-
? (numberColumns.find((column) => column.id === metric.columnId) ?? null)
|
|
28
|
-
: null;
|
|
29
|
-
if (xColumn.type === 'date') {
|
|
30
|
-
return buildTimeBuckets(items, xColumn, groupByColumn, groups, metric, metricColumn, timeBucket);
|
|
31
|
-
}
|
|
32
|
-
return buildCategoryBuckets(items, xColumn, groupByColumn, groups, metric, metricColumn);
|
|
33
|
-
}
|
|
34
|
-
/**
|
|
35
|
-
* Generate a continuous sequence of date buckets from the minimum to maximum
|
|
36
|
-
* date found in the data.
|
|
37
|
-
*
|
|
38
|
-
* @param items - Filtered source data
|
|
39
|
-
* @param xColumn - Active date column
|
|
40
|
-
* @param bucket - Time bucket granularity
|
|
41
|
-
* @returns Every bucket between the first and last date in the dataset
|
|
42
|
-
*/
|
|
43
|
-
function generateBucketsFromData(items, xColumn, bucket) {
|
|
44
|
-
let min = null;
|
|
45
|
-
let max = null;
|
|
46
|
-
for (const item of items) {
|
|
47
|
-
const rawValue = xColumn.accessor(item);
|
|
48
|
-
if (rawValue == null)
|
|
49
|
-
continue;
|
|
50
|
-
const date = new Date(rawValue);
|
|
51
|
-
if (Number.isNaN(date.getTime()))
|
|
52
|
-
continue;
|
|
53
|
-
if (!min || date < min)
|
|
54
|
-
min = date;
|
|
55
|
-
if (!max || date > max)
|
|
56
|
-
max = date;
|
|
57
|
-
}
|
|
58
|
-
if (!min || !max)
|
|
59
|
-
return [];
|
|
60
|
-
const buckets = [];
|
|
61
|
-
const cursor = startOfBucket(min, bucket);
|
|
62
|
-
const seenKeys = new Set();
|
|
63
|
-
const maxBuckets = 500;
|
|
64
|
-
for (let bucketCount = 0; bucketCount < maxBuckets; bucketCount += 1) {
|
|
65
|
-
if (cursor > max)
|
|
66
|
-
break;
|
|
67
|
-
const key = dateBucketKey(cursor, bucket);
|
|
68
|
-
if (!seenKeys.has(key)) {
|
|
69
|
-
seenKeys.add(key);
|
|
70
|
-
buckets.push({ key, label: dateBucketLabel(key, bucket) });
|
|
71
|
-
}
|
|
72
|
-
advanceBucketCursor(cursor, bucket);
|
|
73
|
-
}
|
|
74
|
-
return buckets;
|
|
75
|
-
}
|
|
76
|
-
/**
|
|
77
|
-
* Normalize a date to the start of its containing bucket.
|
|
78
|
-
*
|
|
79
|
-
* @param date - Source date
|
|
80
|
-
* @param bucket - Time bucket granularity
|
|
81
|
-
* @returns Start boundary for the bucket containing the source date
|
|
82
|
-
*/
|
|
83
|
-
function startOfBucket(date, bucket) {
|
|
84
|
-
const normalized = new Date(date);
|
|
85
|
-
normalized.setHours(0, 0, 0, 0);
|
|
86
|
-
switch (bucket) {
|
|
87
|
-
case 'day':
|
|
88
|
-
return normalized;
|
|
89
|
-
case 'week': {
|
|
90
|
-
const day = normalized.getDay();
|
|
91
|
-
normalized.setDate(normalized.getDate() - ((day + 6) % 7));
|
|
92
|
-
return normalized;
|
|
93
|
-
}
|
|
94
|
-
case 'month':
|
|
95
|
-
normalized.setDate(1);
|
|
96
|
-
return normalized;
|
|
97
|
-
case 'quarter':
|
|
98
|
-
normalized.setMonth(Math.floor(normalized.getMonth() / 3) * 3, 1);
|
|
99
|
-
return normalized;
|
|
100
|
-
case 'year':
|
|
101
|
-
normalized.setMonth(0, 1);
|
|
102
|
-
return normalized;
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
/**
|
|
106
|
-
* Advance a mutable cursor by a single bucket interval.
|
|
107
|
-
*
|
|
108
|
-
* @param cursor - Current date cursor
|
|
109
|
-
* @param bucket - Time bucket granularity
|
|
110
|
-
*/
|
|
111
|
-
function advanceBucketCursor(cursor, bucket) {
|
|
112
|
-
switch (bucket) {
|
|
113
|
-
case 'day':
|
|
114
|
-
cursor.setDate(cursor.getDate() + 1);
|
|
115
|
-
break;
|
|
116
|
-
case 'week':
|
|
117
|
-
cursor.setDate(cursor.getDate() + 7);
|
|
118
|
-
break;
|
|
119
|
-
case 'month':
|
|
120
|
-
cursor.setMonth(cursor.getMonth() + 1);
|
|
121
|
-
break;
|
|
122
|
-
case 'quarter':
|
|
123
|
-
cursor.setMonth(cursor.getMonth() + 3);
|
|
124
|
-
break;
|
|
125
|
-
case 'year':
|
|
126
|
-
cursor.setFullYear(cursor.getFullYear() + 1);
|
|
127
|
-
break;
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
/**
|
|
131
|
-
* Build data points for a date X-axis.
|
|
132
|
-
*
|
|
133
|
-
* @param items - Filtered source data
|
|
134
|
-
* @param xColumn - Active date column
|
|
135
|
-
* @param groupByColumn - Optional group-by column
|
|
136
|
-
* @param groups - Resolved group labels
|
|
137
|
-
* @param metric - Selected metric configuration
|
|
138
|
-
* @param metricColumn - Resolved numeric metric column
|
|
139
|
-
* @param timeBucket - Time bucket granularity
|
|
140
|
-
* @returns Aggregated date-bucketed data points
|
|
141
|
-
*/
|
|
142
|
-
function buildTimeBuckets(items, xColumn, groupByColumn, groups, metric, metricColumn, timeBucket) {
|
|
143
|
-
const allBuckets = generateBucketsFromData(items, xColumn, timeBucket);
|
|
144
|
-
const accumulator = new Map();
|
|
145
|
-
for (const { key } of allBuckets) {
|
|
146
|
-
const groupMap = new Map();
|
|
147
|
-
for (const group of groups) {
|
|
148
|
-
groupMap.set(group, []);
|
|
149
|
-
}
|
|
150
|
-
accumulator.set(key, groupMap);
|
|
151
|
-
}
|
|
152
|
-
for (const item of items) {
|
|
153
|
-
const rawValue = xColumn.accessor(item);
|
|
154
|
-
if (rawValue == null)
|
|
155
|
-
continue;
|
|
156
|
-
const date = new Date(rawValue);
|
|
157
|
-
const key = dateBucketKey(date, timeBucket);
|
|
158
|
-
const groupMap = accumulator.get(key);
|
|
159
|
-
if (!groupMap)
|
|
160
|
-
continue;
|
|
161
|
-
const group = groupByColumn ? getStringValue(item, groupByColumn) : 'value';
|
|
162
|
-
const values = groupMap.get(group);
|
|
163
|
-
if (!values)
|
|
164
|
-
continue;
|
|
165
|
-
if (metricColumn) {
|
|
166
|
-
const metricValue = metricColumn.accessor(item);
|
|
167
|
-
if (metricValue != null)
|
|
168
|
-
values.push(metricValue);
|
|
169
|
-
}
|
|
170
|
-
else {
|
|
171
|
-
values.push(1);
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
const data = allBuckets.map(({ key, label }) => {
|
|
175
|
-
const point = { xLabel: label, xKey: key };
|
|
176
|
-
const groupMap = accumulator.get(key);
|
|
177
|
-
for (const group of groups) {
|
|
178
|
-
point[group] = aggregate(groupMap.get(group) ?? [], metric.kind === 'aggregate' ? metric.aggregate : 'count', metric.kind === 'aggregate' ? (metric.includeZeros ?? true) : true);
|
|
179
|
-
}
|
|
180
|
-
return point;
|
|
181
|
-
});
|
|
182
|
-
return { data, groups };
|
|
183
|
-
}
|
|
184
|
-
/**
|
|
185
|
-
* Build data points for a categorical or boolean X-axis.
|
|
186
|
-
*
|
|
187
|
-
* @param items - Filtered source data
|
|
188
|
-
* @param xColumn - Active X-axis column
|
|
189
|
-
* @param groupByColumn - Optional group-by column
|
|
190
|
-
* @param groups - Resolved group labels
|
|
191
|
-
* @param metric - Selected metric configuration
|
|
192
|
-
* @param metricColumn - Resolved numeric metric column
|
|
193
|
-
* @returns Aggregated category-bucketed data points
|
|
194
|
-
*/
|
|
195
|
-
function buildCategoryBuckets(items, xColumn, groupByColumn, groups, metric, metricColumn) {
|
|
196
|
-
const xValues = new Set();
|
|
197
|
-
for (const item of items) {
|
|
198
|
-
xValues.add(getStringValue(item, xColumn));
|
|
199
|
-
}
|
|
200
|
-
const accumulator = new Map();
|
|
201
|
-
for (const xValue of xValues) {
|
|
202
|
-
const groupMap = new Map();
|
|
203
|
-
for (const group of groups) {
|
|
204
|
-
groupMap.set(group, []);
|
|
205
|
-
}
|
|
206
|
-
accumulator.set(xValue, groupMap);
|
|
207
|
-
}
|
|
208
|
-
for (const item of items) {
|
|
209
|
-
const xValue = getStringValue(item, xColumn);
|
|
210
|
-
const groupMap = accumulator.get(xValue);
|
|
211
|
-
if (!groupMap)
|
|
212
|
-
continue;
|
|
213
|
-
const group = groupByColumn ? getStringValue(item, groupByColumn) : 'value';
|
|
214
|
-
const values = groupMap.get(group);
|
|
215
|
-
if (!values)
|
|
216
|
-
continue;
|
|
217
|
-
if (metricColumn) {
|
|
218
|
-
const metricValue = metricColumn.accessor(item);
|
|
219
|
-
if (metricValue != null)
|
|
220
|
-
values.push(metricValue);
|
|
221
|
-
}
|
|
222
|
-
else {
|
|
223
|
-
values.push(1);
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
const data = [...xValues].map((xValue) => {
|
|
227
|
-
const point = { xLabel: xValue, xKey: xValue };
|
|
228
|
-
const groupMap = accumulator.get(xValue);
|
|
229
|
-
for (const group of groups) {
|
|
230
|
-
point[group] = aggregate(groupMap.get(group) ?? [], metric.kind === 'aggregate' ? metric.aggregate : 'count', metric.kind === 'aggregate' ? (metric.includeZeros ?? true) : true);
|
|
231
|
-
}
|
|
232
|
-
return point;
|
|
233
|
-
});
|
|
234
|
-
return { data, groups };
|
|
235
|
-
}
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared helpers for the chart transformation pipeline.
|
|
3
|
-
*/
|
|
4
|
-
import type { AggregateFunction, ChartColumn, TimeBucket } from './types.js';
|
|
5
|
-
/**
|
|
6
|
-
* Format a stable key for a date bucket.
|
|
7
|
-
*
|
|
8
|
-
* @param date - Source date
|
|
9
|
-
* @param bucket - Time bucket granularity
|
|
10
|
-
* @returns Machine-friendly bucket key
|
|
11
|
-
*/
|
|
12
|
-
export declare function dateBucketKey(date: Date, bucket: TimeBucket): string;
|
|
13
|
-
/**
|
|
14
|
-
* Format a bucket key into a human-readable label.
|
|
15
|
-
*
|
|
16
|
-
* @param key - Bucket key
|
|
17
|
-
* @param bucket - Time bucket granularity
|
|
18
|
-
* @returns Display label for the chart axis
|
|
19
|
-
*/
|
|
20
|
-
export declare function dateBucketLabel(key: string, bucket: TimeBucket): string;
|
|
21
|
-
/**
|
|
22
|
-
* Extract a comparable string value from an item using a column definition.
|
|
23
|
-
*
|
|
24
|
-
* @param item - Raw data item
|
|
25
|
-
* @param column - Column definition to read from
|
|
26
|
-
* @returns String value used by filters and groups
|
|
27
|
-
*/
|
|
28
|
-
export declare function getStringValue<T>(item: T, column: ChartColumn<T>): string;
|
|
29
|
-
/**
|
|
30
|
-
* Aggregate numeric values using the requested function.
|
|
31
|
-
*
|
|
32
|
-
* @param values - Numeric values to aggregate
|
|
33
|
-
* @param fn - Aggregation strategy
|
|
34
|
-
* @param includeZeros - Whether zero values should participate in avg/min/max
|
|
35
|
-
* @returns Aggregated numeric result
|
|
36
|
-
*/
|
|
37
|
-
export declare function aggregate(values: number[], fn: AggregateFunction, includeZeros?: boolean): number;
|
|
38
|
-
//# sourceMappingURL=pipeline-helpers.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"pipeline-helpers.d.ts","sourceRoot":"","sources":["../../src/core/pipeline-helpers.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,EAAC,iBAAiB,EAAE,WAAW,EAAE,UAAU,EAAC,MAAM,YAAY,CAAA;AAE1E;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,GAAG,MAAM,CAoBpE;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,GAAG,MAAM,CAEvE;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,EAAE,WAAW,CAAC,CAAC,CAAC,GAAG,MAAM,CAqBzE;AAED;;;;;;;GAOG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,iBAAiB,EAAE,YAAY,UAAO,GAAG,MAAM,CAkB9F"}
|
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared helpers for the chart transformation pipeline.
|
|
3
|
-
*/
|
|
4
|
-
import { formatTimeBucketLabel } from './formatting.js';
|
|
5
|
-
/**
|
|
6
|
-
* Format a stable key for a date bucket.
|
|
7
|
-
*
|
|
8
|
-
* @param date - Source date
|
|
9
|
-
* @param bucket - Time bucket granularity
|
|
10
|
-
* @returns Machine-friendly bucket key
|
|
11
|
-
*/
|
|
12
|
-
export function dateBucketKey(date, bucket) {
|
|
13
|
-
const year = date.getFullYear();
|
|
14
|
-
const month = date.getMonth();
|
|
15
|
-
switch (bucket) {
|
|
16
|
-
case 'day':
|
|
17
|
-
return `${year}-${String(month + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`;
|
|
18
|
-
case 'week': {
|
|
19
|
-
const day = date.getDay();
|
|
20
|
-
const monday = new Date(date);
|
|
21
|
-
monday.setDate(date.getDate() - ((day + 6) % 7));
|
|
22
|
-
return `${monday.getFullYear()}-${String(monday.getMonth() + 1).padStart(2, '0')}-${String(monday.getDate()).padStart(2, '0')}`;
|
|
23
|
-
}
|
|
24
|
-
case 'month':
|
|
25
|
-
return `${year}-${String(month + 1).padStart(2, '0')}`;
|
|
26
|
-
case 'quarter':
|
|
27
|
-
return `${year}-Q${Math.floor(month / 3) + 1}`;
|
|
28
|
-
case 'year':
|
|
29
|
-
return `${year}`;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
/**
|
|
33
|
-
* Format a bucket key into a human-readable label.
|
|
34
|
-
*
|
|
35
|
-
* @param key - Bucket key
|
|
36
|
-
* @param bucket - Time bucket granularity
|
|
37
|
-
* @returns Display label for the chart axis
|
|
38
|
-
*/
|
|
39
|
-
export function dateBucketLabel(key, bucket) {
|
|
40
|
-
return formatTimeBucketLabel(key, bucket, 'axis');
|
|
41
|
-
}
|
|
42
|
-
/**
|
|
43
|
-
* Extract a comparable string value from an item using a column definition.
|
|
44
|
-
*
|
|
45
|
-
* @param item - Raw data item
|
|
46
|
-
* @param column - Column definition to read from
|
|
47
|
-
* @returns String value used by filters and groups
|
|
48
|
-
*/
|
|
49
|
-
export function getStringValue(item, column) {
|
|
50
|
-
switch (column.type) {
|
|
51
|
-
case 'boolean': {
|
|
52
|
-
const value = column.accessor(item);
|
|
53
|
-
if (value === true)
|
|
54
|
-
return column.trueLabel;
|
|
55
|
-
if (value === false)
|
|
56
|
-
return column.falseLabel;
|
|
57
|
-
return 'Unknown';
|
|
58
|
-
}
|
|
59
|
-
case 'category': {
|
|
60
|
-
const value = column.accessor(item);
|
|
61
|
-
return value ?? 'Unknown';
|
|
62
|
-
}
|
|
63
|
-
case 'date': {
|
|
64
|
-
const value = column.accessor(item);
|
|
65
|
-
return value != null ? String(value) : 'Unknown';
|
|
66
|
-
}
|
|
67
|
-
case 'number': {
|
|
68
|
-
const value = column.accessor(item);
|
|
69
|
-
return value != null ? String(value) : 'Unknown';
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
/**
|
|
74
|
-
* Aggregate numeric values using the requested function.
|
|
75
|
-
*
|
|
76
|
-
* @param values - Numeric values to aggregate
|
|
77
|
-
* @param fn - Aggregation strategy
|
|
78
|
-
* @param includeZeros - Whether zero values should participate in avg/min/max
|
|
79
|
-
* @returns Aggregated numeric result
|
|
80
|
-
*/
|
|
81
|
-
export function aggregate(values, fn, includeZeros = true) {
|
|
82
|
-
if (fn === 'count')
|
|
83
|
-
return values.length;
|
|
84
|
-
const effectiveValues = !includeZeros && fn !== 'sum' ? values.filter((value) => value !== 0) : values;
|
|
85
|
-
if (effectiveValues.length === 0)
|
|
86
|
-
return 0;
|
|
87
|
-
switch (fn) {
|
|
88
|
-
case 'sum':
|
|
89
|
-
return effectiveValues.reduce((sum, value) => sum + value, 0);
|
|
90
|
-
case 'avg':
|
|
91
|
-
return effectiveValues.reduce((sum, value) => sum + value, 0) / effectiveValues.length;
|
|
92
|
-
case 'min':
|
|
93
|
-
return Math.min(...effectiveValues);
|
|
94
|
-
case 'max':
|
|
95
|
-
return Math.max(...effectiveValues);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../../src/core/pipeline.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAOH,OAAO,KAAK,EACV,eAAe,EAGf,WAAW,EACX,WAAW,EACX,WAAW,EACX,MAAM,EAEN,UAAU,EACV,UAAU,EACV,oBAAoB,EACrB,MAAM,YAAY,CAAA;AAEnB;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,aAAa,CAAC,CAAC,EAAE,SAAS,SAAS,MAAM,GAAG,MAAM,IAAI;IAChE,IAAI,EAAE,SAAS,CAAC,EAAE,CAAA;IAClB,OAAO,EAAE,SAAS,WAAW,CAAC,CAAC,EAAE,SAAS,CAAC,EAAE,CAAA;IAC7C,OAAO,EAAE,SAAS,CAAA;IAClB,SAAS,EAAE,SAAS,GAAG,IAAI,CAAA;IAC3B,MAAM,EAAE,MAAM,CAAC,SAAS,CAAC,CAAA;IACzB,UAAU,EAAE,UAAU,CAAA;IACtB,OAAO,EAAE,WAAW,CAAC,SAAS,CAAC,CAAA;IAC/B,OAAO,EAAE,UAAU,GAAG,IAAI,CAAA;CAC3B,CAAA;AAED;;;;;;GAMG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,EAAE,oBAAoB,EAAE,CAAA;IAC5B,MAAM,EAAE,WAAW,EAAE,CAAA;IACrB,MAAM,EAAE,MAAM,EAAE,CAAA;CACjB,CAAA;AAED;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAAE,SAAS,SAAS,MAAM,EACtD,IAAI,EAAE,SAAS,CAAC,EAAE,EAClB,OAAO,EAAE,SAAS,WAAW,CAAC,CAAC,EAAE,SAAS,CAAC,EAAE,EAC7C,OAAO,EAAE,WAAW,CAAC,SAAS,CAAC,GAC9B,CAAC,EAAE,CAiBL;AAgED;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,SAAS,SAAS,MAAM,EACrD,KAAK,EAAE,aAAa,CAAC,CAAC,EAAE,SAAS,CAAC,GACjC,cAAc,CA2BhB;AAED;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CAAC,CAAC,EAAE,SAAS,SAAS,MAAM,EACjE,IAAI,EAAE,SAAS,CAAC,EAAE,EAClB,OAAO,EAAE,SAAS,WAAW,CAAC,CAAC,EAAE,SAAS,CAAC,EAAE,GAC5C,eAAe,CAAC,SAAS,CAAC,EAAE,CA+B9B"}
|