@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.
Files changed (175) hide show
  1. package/README.md +1 -1
  2. package/dist/core/chart-capabilities.d.mts +48 -0
  3. package/dist/core/chart-capabilities.mjs +55 -0
  4. package/dist/core/{colors.d.ts → colors.d.mts} +5 -3
  5. package/dist/core/colors.mjs +55 -0
  6. package/dist/core/config-utils.mjs +79 -0
  7. package/dist/core/date-utils.mjs +49 -0
  8. package/dist/core/define-chart-schema.d.mts +106 -0
  9. package/dist/core/define-chart-schema.mjs +47 -0
  10. package/dist/core/formatting.mjs +349 -0
  11. package/dist/core/infer-columns.d.mts +9 -0
  12. package/dist/core/infer-columns.mjs +481 -0
  13. package/dist/core/metric-utils.d.mts +13 -0
  14. package/dist/core/metric-utils.mjs +121 -0
  15. package/dist/core/pipeline-data-points.mjs +212 -0
  16. package/dist/core/pipeline-helpers.mjs +85 -0
  17. package/dist/core/{pipeline.d.ts → pipeline.d.mts} +21 -24
  18. package/dist/core/pipeline.mjs +153 -0
  19. package/dist/core/types.d.mts +957 -0
  20. package/dist/core/use-chart-options.d.mts +64 -0
  21. package/dist/core/use-chart-options.mjs +7 -0
  22. package/dist/core/use-chart-resolvers.mjs +34 -0
  23. package/dist/core/{use-chart.d.ts → use-chart.d.mts} +12 -9
  24. package/dist/core/use-chart.mjs +299 -0
  25. package/dist/index.d.mts +10 -0
  26. package/dist/index.mjs +8 -0
  27. package/dist/ui/chart-axis-ticks.mjs +65 -0
  28. package/dist/ui/{chart-canvas.d.ts → chart-canvas.d.mts} +13 -6
  29. package/dist/ui/chart-canvas.mjs +461 -0
  30. package/dist/ui/chart-context.d.mts +92 -0
  31. package/dist/ui/chart-context.mjs +112 -0
  32. package/dist/ui/{chart-date-range-badge.d.ts → chart-date-range-badge.d.mts} +10 -4
  33. package/dist/ui/chart-date-range-badge.mjs +49 -0
  34. package/dist/ui/chart-date-range-panel.d.mts +18 -0
  35. package/dist/ui/chart-date-range-panel.mjs +208 -0
  36. package/dist/ui/{chart-date-range.d.ts → chart-date-range.d.mts} +10 -4
  37. package/dist/ui/chart-date-range.mjs +67 -0
  38. package/dist/ui/chart-debug.d.mts +17 -0
  39. package/dist/ui/chart-debug.mjs +169 -0
  40. package/dist/ui/chart-dropdown.mjs +92 -0
  41. package/dist/ui/{chart-filters-panel.d.ts → chart-filters-panel.d.mts} +12 -5
  42. package/dist/ui/chart-filters-panel.mjs +132 -0
  43. package/dist/ui/{chart-filters.d.ts → chart-filters.d.mts} +10 -4
  44. package/dist/ui/chart-filters.mjs +48 -0
  45. package/dist/ui/chart-group-by-selector.d.mts +14 -0
  46. package/dist/ui/chart-group-by-selector.mjs +29 -0
  47. package/dist/ui/{chart-metric-panel.d.ts → chart-metric-panel.d.mts} +12 -5
  48. package/dist/ui/chart-metric-panel.mjs +172 -0
  49. package/dist/ui/chart-metric-selector.d.mts +16 -0
  50. package/dist/ui/chart-metric-selector.mjs +50 -0
  51. package/dist/ui/chart-select.mjs +62 -0
  52. package/dist/ui/{chart-source-switcher.d.ts → chart-source-switcher.d.mts} +10 -4
  53. package/dist/ui/chart-source-switcher.mjs +54 -0
  54. package/dist/ui/chart-time-bucket-selector.d.mts +15 -0
  55. package/dist/ui/chart-time-bucket-selector.mjs +34 -0
  56. package/dist/ui/chart-toolbar-overflow.d.mts +28 -0
  57. package/dist/ui/chart-toolbar-overflow.mjs +209 -0
  58. package/dist/ui/chart-toolbar.d.mts +29 -0
  59. package/dist/ui/chart-toolbar.mjs +56 -0
  60. package/dist/ui/chart-type-selector.d.mts +14 -0
  61. package/dist/ui/chart-type-selector.mjs +33 -0
  62. package/dist/ui/chart-x-axis-selector.d.mts +14 -0
  63. package/dist/ui/chart-x-axis-selector.mjs +25 -0
  64. package/dist/ui/index.d.mts +19 -0
  65. package/dist/ui/index.mjs +18 -0
  66. package/dist/ui/toolbar-types.d.mts +7 -0
  67. package/dist/ui/toolbar-types.mjs +83 -0
  68. package/package.json +11 -10
  69. package/dist/core/chart-capabilities.d.ts +0 -60
  70. package/dist/core/chart-capabilities.d.ts.map +0 -1
  71. package/dist/core/chart-capabilities.js +0 -54
  72. package/dist/core/colors.d.ts.map +0 -1
  73. package/dist/core/colors.js +0 -54
  74. package/dist/core/config-utils.d.ts +0 -43
  75. package/dist/core/config-utils.d.ts.map +0 -1
  76. package/dist/core/config-utils.js +0 -80
  77. package/dist/core/date-utils.d.ts +0 -29
  78. package/dist/core/date-utils.d.ts.map +0 -1
  79. package/dist/core/date-utils.js +0 -58
  80. package/dist/core/define-chart-schema.d.ts +0 -105
  81. package/dist/core/define-chart-schema.d.ts.map +0 -1
  82. package/dist/core/define-chart-schema.js +0 -44
  83. package/dist/core/formatting.d.ts +0 -47
  84. package/dist/core/formatting.d.ts.map +0 -1
  85. package/dist/core/formatting.js +0 -396
  86. package/dist/core/index.d.ts +0 -17
  87. package/dist/core/index.d.ts.map +0 -1
  88. package/dist/core/index.js +0 -12
  89. package/dist/core/infer-columns.d.ts +0 -6
  90. package/dist/core/infer-columns.d.ts.map +0 -1
  91. package/dist/core/infer-columns.js +0 -512
  92. package/dist/core/metric-utils.d.ts +0 -43
  93. package/dist/core/metric-utils.d.ts.map +0 -1
  94. package/dist/core/metric-utils.js +0 -141
  95. package/dist/core/pipeline-data-points.d.ts +0 -23
  96. package/dist/core/pipeline-data-points.d.ts.map +0 -1
  97. package/dist/core/pipeline-data-points.js +0 -235
  98. package/dist/core/pipeline-helpers.d.ts +0 -38
  99. package/dist/core/pipeline-helpers.d.ts.map +0 -1
  100. package/dist/core/pipeline-helpers.js +0 -97
  101. package/dist/core/pipeline.d.ts.map +0 -1
  102. package/dist/core/pipeline.js +0 -156
  103. package/dist/core/types.d.ts +0 -1109
  104. package/dist/core/types.d.ts.map +0 -1
  105. package/dist/core/types.js +0 -14
  106. package/dist/core/use-chart-options.d.ts +0 -66
  107. package/dist/core/use-chart-options.d.ts.map +0 -1
  108. package/dist/core/use-chart-options.js +0 -4
  109. package/dist/core/use-chart-resolvers.d.ts +0 -14
  110. package/dist/core/use-chart-resolvers.d.ts.map +0 -1
  111. package/dist/core/use-chart-resolvers.js +0 -41
  112. package/dist/core/use-chart.d.ts.map +0 -1
  113. package/dist/core/use-chart.js +0 -265
  114. package/dist/index.d.ts +0 -36
  115. package/dist/index.d.ts.map +0 -1
  116. package/dist/index.js +0 -35
  117. package/dist/ui/chart-axis-ticks.d.ts +0 -35
  118. package/dist/ui/chart-axis-ticks.d.ts.map +0 -1
  119. package/dist/ui/chart-axis-ticks.js +0 -79
  120. package/dist/ui/chart-canvas.d.ts.map +0 -1
  121. package/dist/ui/chart-canvas.js +0 -337
  122. package/dist/ui/chart-context.d.ts +0 -89
  123. package/dist/ui/chart-context.d.ts.map +0 -1
  124. package/dist/ui/chart-context.js +0 -128
  125. package/dist/ui/chart-date-range-badge.d.ts.map +0 -1
  126. package/dist/ui/chart-date-range-badge.js +0 -30
  127. package/dist/ui/chart-date-range-panel.d.ts +0 -25
  128. package/dist/ui/chart-date-range-panel.d.ts.map +0 -1
  129. package/dist/ui/chart-date-range-panel.js +0 -125
  130. package/dist/ui/chart-date-range.d.ts.map +0 -1
  131. package/dist/ui/chart-date-range.js +0 -37
  132. package/dist/ui/chart-debug.d.ts +0 -10
  133. package/dist/ui/chart-debug.d.ts.map +0 -1
  134. package/dist/ui/chart-debug.js +0 -126
  135. package/dist/ui/chart-dropdown.d.ts +0 -35
  136. package/dist/ui/chart-dropdown.d.ts.map +0 -1
  137. package/dist/ui/chart-dropdown.js +0 -76
  138. package/dist/ui/chart-filters-panel.d.ts.map +0 -1
  139. package/dist/ui/chart-filters-panel.js +0 -46
  140. package/dist/ui/chart-filters.d.ts.map +0 -1
  141. package/dist/ui/chart-filters.js +0 -26
  142. package/dist/ui/chart-group-by-selector.d.ts +0 -8
  143. package/dist/ui/chart-group-by-selector.d.ts.map +0 -1
  144. package/dist/ui/chart-group-by-selector.js +0 -19
  145. package/dist/ui/chart-metric-panel.d.ts.map +0 -1
  146. package/dist/ui/chart-metric-panel.js +0 -118
  147. package/dist/ui/chart-metric-selector.d.ts +0 -10
  148. package/dist/ui/chart-metric-selector.d.ts.map +0 -1
  149. package/dist/ui/chart-metric-selector.js +0 -27
  150. package/dist/ui/chart-select.d.ts +0 -25
  151. package/dist/ui/chart-select.d.ts.map +0 -1
  152. package/dist/ui/chart-select.js +0 -35
  153. package/dist/ui/chart-source-switcher.d.ts.map +0 -1
  154. package/dist/ui/chart-source-switcher.js +0 -31
  155. package/dist/ui/chart-time-bucket-selector.d.ts +0 -9
  156. package/dist/ui/chart-time-bucket-selector.d.ts.map +0 -1
  157. package/dist/ui/chart-time-bucket-selector.js +0 -25
  158. package/dist/ui/chart-toolbar-overflow.d.ts +0 -29
  159. package/dist/ui/chart-toolbar-overflow.d.ts.map +0 -1
  160. package/dist/ui/chart-toolbar-overflow.js +0 -109
  161. package/dist/ui/chart-toolbar.d.ts +0 -45
  162. package/dist/ui/chart-toolbar.d.ts.map +0 -1
  163. package/dist/ui/chart-toolbar.js +0 -44
  164. package/dist/ui/chart-type-selector.d.ts +0 -8
  165. package/dist/ui/chart-type-selector.d.ts.map +0 -1
  166. package/dist/ui/chart-type-selector.js +0 -22
  167. package/dist/ui/chart-x-axis-selector.d.ts +0 -8
  168. package/dist/ui/chart-x-axis-selector.d.ts.map +0 -1
  169. package/dist/ui/chart-x-axis-selector.js +0 -14
  170. package/dist/ui/index.d.ts +0 -25
  171. package/dist/ui/index.d.ts.map +0 -1
  172. package/dist/ui/index.js +0 -23
  173. package/dist/ui/toolbar-types.d.ts +0 -43
  174. package/dist/ui/toolbar-types.d.ts.map +0 -1
  175. package/dist/ui/toolbar-types.js +0 -50
@@ -0,0 +1,212 @@
1
+ import { isAggregateMetric } from "./metric-utils.mjs";
2
+ import { aggregate, dateBucketKey, dateBucketLabel, getStringValue } from "./pipeline-helpers.mjs";
3
+ //#region src/core/pipeline-data-points.ts
4
+ /**
5
+ * Bucket and aggregation steps for the chart transformation pipeline.
6
+ */
7
+ /**
8
+ * Build chart-ready data points by bucketing the X-axis, pivoting groups, and
9
+ * aggregating the selected metric.
10
+ *
11
+ * @param items - Filtered source data
12
+ * @param xColumn - Active X-axis column
13
+ * @param groupByColumn - Optional group-by column
14
+ * @param metric - Selected metric configuration
15
+ * @param numberColumns - Number columns available for metric lookup
16
+ * @param timeBucket - Time bucket used for date X-axes
17
+ * @returns Aggregated chart data points and group labels
18
+ */
19
+ function buildDataPoints(items, xColumn, groupByColumn, metric, numberColumns, timeBucket) {
20
+ const groupSet = /* @__PURE__ */ new Set();
21
+ if (groupByColumn) for (const item of items) groupSet.add(getStringValue(item, groupByColumn));
22
+ const groups = groupByColumn ? [...groupSet].toSorted() : ["value"];
23
+ const metricColumn = isAggregateMetric(metric) ? numberColumns.find((column) => column.id === metric.columnId) ?? null : null;
24
+ if (xColumn.type === "date") return buildTimeBuckets(items, xColumn, groupByColumn, groups, metric, metricColumn, timeBucket);
25
+ return buildCategoryBuckets(items, xColumn, groupByColumn, groups, metric, metricColumn);
26
+ }
27
+ /**
28
+ * Generate a continuous sequence of date buckets from the minimum to maximum
29
+ * date found in the data.
30
+ *
31
+ * @param items - Filtered source data
32
+ * @param xColumn - Active date column
33
+ * @param bucket - Time bucket granularity
34
+ * @returns Every bucket between the first and last date in the dataset
35
+ */
36
+ function generateBucketsFromData(items, xColumn, bucket) {
37
+ let min = null;
38
+ let max = null;
39
+ for (const item of items) {
40
+ const rawValue = xColumn.accessor(item);
41
+ if (rawValue == null) continue;
42
+ const date = new Date(rawValue);
43
+ if (Number.isNaN(date.getTime())) continue;
44
+ if (!min || date < min) min = date;
45
+ if (!max || date > max) max = date;
46
+ }
47
+ if (!min || !max) return [];
48
+ const buckets = [];
49
+ const cursor = startOfBucket(min, bucket);
50
+ const seenKeys = /* @__PURE__ */ new Set();
51
+ const maxBuckets = 500;
52
+ for (let bucketCount = 0; bucketCount < maxBuckets; bucketCount += 1) {
53
+ if (cursor > max) break;
54
+ const key = dateBucketKey(cursor, bucket);
55
+ if (!seenKeys.has(key)) {
56
+ seenKeys.add(key);
57
+ buckets.push({
58
+ key,
59
+ label: dateBucketLabel(key, bucket)
60
+ });
61
+ }
62
+ advanceBucketCursor(cursor, bucket);
63
+ }
64
+ return buckets;
65
+ }
66
+ /**
67
+ * Normalize a date to the start of its containing bucket.
68
+ *
69
+ * @param date - Source date
70
+ * @param bucket - Time bucket granularity
71
+ * @returns Start boundary for the bucket containing the source date
72
+ */
73
+ function startOfBucket(date, bucket) {
74
+ const normalized = new Date(date);
75
+ normalized.setHours(0, 0, 0, 0);
76
+ switch (bucket) {
77
+ case "day": return normalized;
78
+ case "week": {
79
+ const day = normalized.getDay();
80
+ normalized.setDate(normalized.getDate() - (day + 6) % 7);
81
+ return normalized;
82
+ }
83
+ case "month":
84
+ normalized.setDate(1);
85
+ return normalized;
86
+ case "quarter":
87
+ normalized.setMonth(Math.floor(normalized.getMonth() / 3) * 3, 1);
88
+ return normalized;
89
+ case "year":
90
+ normalized.setMonth(0, 1);
91
+ return normalized;
92
+ }
93
+ }
94
+ /**
95
+ * Advance a mutable cursor by a single bucket interval.
96
+ *
97
+ * @param cursor - Current date cursor
98
+ * @param bucket - Time bucket granularity
99
+ */
100
+ function advanceBucketCursor(cursor, bucket) {
101
+ switch (bucket) {
102
+ case "day":
103
+ cursor.setDate(cursor.getDate() + 1);
104
+ break;
105
+ case "week":
106
+ cursor.setDate(cursor.getDate() + 7);
107
+ break;
108
+ case "month":
109
+ cursor.setMonth(cursor.getMonth() + 1);
110
+ break;
111
+ case "quarter":
112
+ cursor.setMonth(cursor.getMonth() + 3);
113
+ break;
114
+ case "year":
115
+ cursor.setFullYear(cursor.getFullYear() + 1);
116
+ break;
117
+ }
118
+ }
119
+ /**
120
+ * Build data points for a date X-axis.
121
+ *
122
+ * @param items - Filtered source data
123
+ * @param xColumn - Active date column
124
+ * @param groupByColumn - Optional group-by column
125
+ * @param groups - Resolved group labels
126
+ * @param metric - Selected metric configuration
127
+ * @param metricColumn - Resolved numeric metric column
128
+ * @param timeBucket - Time bucket granularity
129
+ * @returns Aggregated date-bucketed data points
130
+ */
131
+ function buildTimeBuckets(items, xColumn, groupByColumn, groups, metric, metricColumn, timeBucket) {
132
+ const allBuckets = generateBucketsFromData(items, xColumn, timeBucket);
133
+ const accumulator = /* @__PURE__ */ new Map();
134
+ for (const { key } of allBuckets) {
135
+ const groupMap = /* @__PURE__ */ new Map();
136
+ for (const group of groups) groupMap.set(group, []);
137
+ accumulator.set(key, groupMap);
138
+ }
139
+ for (const item of items) {
140
+ const rawValue = xColumn.accessor(item);
141
+ if (rawValue == null) continue;
142
+ const key = dateBucketKey(new Date(rawValue), timeBucket);
143
+ const groupMap = accumulator.get(key);
144
+ if (!groupMap) continue;
145
+ const group = groupByColumn ? getStringValue(item, groupByColumn) : "value";
146
+ const values = groupMap.get(group);
147
+ if (!values) continue;
148
+ if (metricColumn) {
149
+ const metricValue = metricColumn.accessor(item);
150
+ if (metricValue != null) values.push(metricValue);
151
+ } else values.push(1);
152
+ }
153
+ return {
154
+ data: allBuckets.map(({ key, label }) => {
155
+ const point = {
156
+ xLabel: label,
157
+ xKey: key
158
+ };
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);
161
+ return point;
162
+ }),
163
+ groups
164
+ };
165
+ }
166
+ /**
167
+ * Build data points for a categorical or boolean X-axis.
168
+ *
169
+ * @param items - Filtered source data
170
+ * @param xColumn - Active X-axis column
171
+ * @param groupByColumn - Optional group-by column
172
+ * @param groups - Resolved group labels
173
+ * @param metric - Selected metric configuration
174
+ * @param metricColumn - Resolved numeric metric column
175
+ * @returns Aggregated category-bucketed data points
176
+ */
177
+ function buildCategoryBuckets(items, xColumn, groupByColumn, groups, metric, metricColumn) {
178
+ const xValues = /* @__PURE__ */ new Set();
179
+ for (const item of items) xValues.add(getStringValue(item, xColumn));
180
+ const accumulator = /* @__PURE__ */ new Map();
181
+ for (const xValue of xValues) {
182
+ const groupMap = /* @__PURE__ */ new Map();
183
+ for (const group of groups) groupMap.set(group, []);
184
+ accumulator.set(xValue, groupMap);
185
+ }
186
+ for (const item of items) {
187
+ const xValue = getStringValue(item, xColumn);
188
+ const groupMap = accumulator.get(xValue);
189
+ if (!groupMap) continue;
190
+ const group = groupByColumn ? getStringValue(item, groupByColumn) : "value";
191
+ const values = groupMap.get(group);
192
+ if (!values) continue;
193
+ if (metricColumn) {
194
+ const metricValue = metricColumn.accessor(item);
195
+ if (metricValue != null) values.push(metricValue);
196
+ } else values.push(1);
197
+ }
198
+ return {
199
+ data: [...xValues].map((xValue) => {
200
+ const point = {
201
+ xLabel: xValue,
202
+ xKey: xValue
203
+ };
204
+ const groupMap = accumulator.get(xValue);
205
+ for (const group of groups) point[group] = aggregate(groupMap.get(group) ?? [], metric.kind === "aggregate" ? metric.aggregate : "count", metric.kind === "aggregate" ? metric.includeZeros ?? true : true);
206
+ return point;
207
+ }),
208
+ groups
209
+ };
210
+ }
211
+ //#endregion
212
+ export { buildDataPoints };
@@ -0,0 +1,85 @@
1
+ import { formatTimeBucketLabel } from "./formatting.mjs";
2
+ //#region src/core/pipeline-helpers.ts
3
+ /**
4
+ * Shared helpers for the chart transformation pipeline.
5
+ */
6
+ /**
7
+ * Format a stable key for a date bucket.
8
+ *
9
+ * @param date - Source date
10
+ * @param bucket - Time bucket granularity
11
+ * @returns Machine-friendly bucket key
12
+ */
13
+ function dateBucketKey(date, bucket) {
14
+ const year = date.getFullYear();
15
+ const month = date.getMonth();
16
+ switch (bucket) {
17
+ case "day": 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": return `${year}-${String(month + 1).padStart(2, "0")}`;
25
+ case "quarter": return `${year}-Q${Math.floor(month / 3) + 1}`;
26
+ case "year": return `${year}`;
27
+ }
28
+ }
29
+ /**
30
+ * Format a bucket key into a human-readable label.
31
+ *
32
+ * @param key - Bucket key
33
+ * @param bucket - Time bucket granularity
34
+ * @returns Display label for the chart axis
35
+ */
36
+ function dateBucketLabel(key, bucket) {
37
+ return formatTimeBucketLabel(key, bucket, "axis");
38
+ }
39
+ /**
40
+ * Extract a comparable string value from an item using a column definition.
41
+ *
42
+ * @param item - Raw data item
43
+ * @param column - Column definition to read from
44
+ * @returns String value used by filters and groups
45
+ */
46
+ function getStringValue(item, column) {
47
+ switch (column.type) {
48
+ case "boolean": {
49
+ const value = column.accessor(item);
50
+ if (value === true) return column.trueLabel;
51
+ if (value === false) return column.falseLabel;
52
+ return "Unknown";
53
+ }
54
+ case "category": return column.accessor(item) ?? "Unknown";
55
+ case "date": {
56
+ const value = column.accessor(item);
57
+ return value != null ? String(value) : "Unknown";
58
+ }
59
+ case "number": {
60
+ const value = column.accessor(item);
61
+ return value != null ? String(value) : "Unknown";
62
+ }
63
+ }
64
+ }
65
+ /**
66
+ * Aggregate numeric values using the requested function.
67
+ *
68
+ * @param values - Numeric values to aggregate
69
+ * @param fn - Aggregation strategy
70
+ * @param includeZeros - Whether zero values should participate in avg/min/max
71
+ * @returns Aggregated numeric result
72
+ */
73
+ function aggregate(values, fn, includeZeros = true) {
74
+ if (fn === "count") return values.length;
75
+ const effectiveValues = !includeZeros && fn !== "sum" ? values.filter((value) => value !== 0) : values;
76
+ if (effectiveValues.length === 0) return 0;
77
+ switch (fn) {
78
+ case "sum": return effectiveValues.reduce((sum, value) => sum + value, 0);
79
+ case "avg": return effectiveValues.reduce((sum, value) => sum + value, 0) / effectiveValues.length;
80
+ case "min": return Math.min(...effectiveValues);
81
+ case "max": return Math.max(...effectiveValues);
82
+ }
83
+ }
84
+ //#endregion
85
+ export { aggregate, dateBucketKey, dateBucketLabel, getStringValue };
@@ -1,10 +1,6 @@
1
- /**
2
- * Data transformation pipeline.
3
- *
4
- * Pure functions that transform raw data into chart-ready format.
5
- * Pipeline: Raw Data -> Filter -> Bucket by X-axis -> Pivot by groupBy -> Aggregate -> Sort
6
- */
7
- import type { AvailableFilter, ChartColumn, ChartSeries, FilterState, Metric, SortConfig, TimeBucket, TransformedDataPoint } from './types.js';
1
+ import { AvailableFilter, ChartColumn, ChartSeries, FilterState, Metric, SortConfig, TimeBucket, TransformedDataPoint } from "./types.mjs";
2
+
3
+ //#region src/core/pipeline.d.ts
8
4
  /**
9
5
  * Input for the transformation pipeline.
10
6
  *
@@ -17,15 +13,15 @@ import type { AvailableFilter, ChartColumn, ChartSeries, FilterState, Metric, So
17
13
  * @property filters - Active filter state
18
14
  * @property sorting - Sort configuration
19
15
  */
20
- export type PipelineInput<T, TColumnId extends string = string> = {
21
- data: readonly T[];
22
- columns: readonly ChartColumn<T, TColumnId>[];
23
- xAxisId: TColumnId;
24
- groupById: TColumnId | null;
25
- metric: Metric<TColumnId>;
26
- timeBucket: TimeBucket;
27
- filters: FilterState<TColumnId>;
28
- sorting: SortConfig | null;
16
+ type PipelineInput<T, TColumnId extends string = string> = {
17
+ data: readonly T[];
18
+ columns: readonly ChartColumn<T, TColumnId>[];
19
+ xAxisId: TColumnId;
20
+ groupById: TColumnId | null;
21
+ metric: Metric<TColumnId>;
22
+ timeBucket: TimeBucket;
23
+ filters: FilterState<TColumnId>;
24
+ sorting: SortConfig | null;
29
25
  };
30
26
  /**
31
27
  * Output of the transformation pipeline.
@@ -34,10 +30,10 @@ export type PipelineInput<T, TColumnId extends string = string> = {
34
30
  * @property series - Series definitions for rendering
35
31
  * @property groups - Unique group labels
36
32
  */
37
- export type PipelineOutput = {
38
- data: TransformedDataPoint[];
39
- series: ChartSeries[];
40
- groups: string[];
33
+ type PipelineOutput = {
34
+ data: TransformedDataPoint[];
35
+ series: ChartSeries[];
36
+ groups: string[];
41
37
  };
42
38
  /**
43
39
  * Apply active filters to the raw data.
@@ -48,7 +44,7 @@ export type PipelineOutput = {
48
44
  * @param filters - Active filter state
49
45
  * @returns Filtered data items
50
46
  */
51
- export declare function applyFilters<T, TColumnId extends string>(data: readonly T[], columns: readonly ChartColumn<T, TColumnId>[], filters: FilterState<TColumnId>): T[];
47
+ declare function applyFilters<T, TColumnId extends string>(data: readonly T[], columns: readonly ChartColumn<T, TColumnId>[], filters: FilterState<TColumnId>): T[];
52
48
  /**
53
49
  * Run the full transformation pipeline.
54
50
  *
@@ -57,7 +53,7 @@ export declare function applyFilters<T, TColumnId extends string>(data: readonly
57
53
  * @param input - Pipeline configuration and source data
58
54
  * @returns Transformed data, series metadata, and group labels
59
55
  */
60
- export declare function runPipeline<T, TColumnId extends string>(input: PipelineInput<T, TColumnId>): PipelineOutput;
56
+ declare function runPipeline<T, TColumnId extends string>(input: PipelineInput<T, TColumnId>): PipelineOutput;
61
57
  /**
62
58
  * Extract available filter options from data for every category and boolean
63
59
  * column.
@@ -66,5 +62,6 @@ export declare function runPipeline<T, TColumnId extends string>(input: Pipeline
66
62
  * @param columns - Column definitions
67
63
  * @returns Filter metadata and option counts per filterable column
68
64
  */
69
- export declare function extractAvailableFilters<T, TColumnId extends string>(data: readonly T[], columns: readonly ChartColumn<T, TColumnId>[]): AvailableFilter<TColumnId>[];
70
- //# sourceMappingURL=pipeline.d.ts.map
65
+ declare function extractAvailableFilters<T, TColumnId extends string>(data: readonly T[], columns: readonly ChartColumn<T, TColumnId>[]): AvailableFilter<TColumnId>[];
66
+ //#endregion
67
+ export { PipelineInput, PipelineOutput, applyFilters, extractAvailableFilters, runPipeline };
@@ -0,0 +1,153 @@
1
+ import { getSeriesColor } from "./colors.mjs";
2
+ import { getMetricLabel } from "./metric-utils.mjs";
3
+ import { formatChartValue } from "./formatting.mjs";
4
+ import { getStringValue } from "./pipeline-helpers.mjs";
5
+ import { buildDataPoints } from "./pipeline-data-points.mjs";
6
+ //#region src/core/pipeline.ts
7
+ /**
8
+ * Data transformation pipeline.
9
+ *
10
+ * Pure functions that transform raw data into chart-ready format.
11
+ * Pipeline: Raw Data -> Filter -> Bucket by X-axis -> Pivot by groupBy -> Aggregate -> Sort
12
+ */
13
+ /**
14
+ * Apply active filters to the raw data.
15
+ * Filters are AND-combined across columns and OR-combined within a column.
16
+ *
17
+ * @param data - Raw data items
18
+ * @param columns - Column definitions used to read values
19
+ * @param filters - Active filter state
20
+ * @returns Filtered data items
21
+ */
22
+ function applyFilters(data, columns, filters) {
23
+ if (filters.size === 0) return [...data];
24
+ return data.filter((item) => {
25
+ for (const [columnId, activeValues] of filters) {
26
+ if (activeValues.size === 0) continue;
27
+ const column = columns.find((candidate) => candidate.id === columnId);
28
+ if (!column) continue;
29
+ if (!activeValues.has(getStringValue(item, column))) return false;
30
+ }
31
+ return true;
32
+ });
33
+ }
34
+ /**
35
+ * Apply sorting to transformed data points.
36
+ *
37
+ * @param data - Aggregated chart data points
38
+ * @param sorting - Explicit sort configuration
39
+ * @param isTimeSeries - Whether the X-axis is date-based
40
+ * @returns Sorted chart data points
41
+ */
42
+ function applySorting(data, sorting, isTimeSeries) {
43
+ if (isTimeSeries) return data.toSorted((a, b) => String(a["xKey"]).localeCompare(String(b["xKey"])));
44
+ if (!sorting) return data.toSorted((a, b) => getPointTotal(b) - getPointTotal(a));
45
+ const direction = sorting.direction === "asc" ? 1 : -1;
46
+ return data.toSorted((a, b) => {
47
+ const aValue = a[sorting.key] ?? 0;
48
+ const bValue = b[sorting.key] ?? 0;
49
+ if (typeof aValue === "number" && typeof bValue === "number") return (aValue - bValue) * direction;
50
+ return String(aValue).localeCompare(String(bValue)) * direction;
51
+ });
52
+ }
53
+ /**
54
+ * Sum all numeric series values for a transformed data point.
55
+ *
56
+ * @param point - Transformed chart data point
57
+ * @returns Combined numeric total across series keys
58
+ */
59
+ function getPointTotal(point) {
60
+ return Object.entries(point).filter(([key]) => key !== "xLabel" && key !== "xKey").reduce((sum, [, value]) => sum + (typeof value === "number" ? value : 0), 0);
61
+ }
62
+ /**
63
+ * Build recharts series definitions from group labels.
64
+ *
65
+ * @param groups - Group labels returned by the pivot step
66
+ * @param metricLabel - Human-readable label for the selected metric
67
+ * @param useShadcn - Whether to use shadcn-compatible color variables
68
+ * @returns Recharts series metadata
69
+ */
70
+ function buildSeries(groups, metricLabel, useShadcn = true) {
71
+ return groups.map((group, index) => ({
72
+ dataKey: group,
73
+ label: group === "value" ? metricLabel : group,
74
+ color: getSeriesColor(index, useShadcn)
75
+ }));
76
+ }
77
+ /**
78
+ * Run the full transformation pipeline.
79
+ *
80
+ * Raw Data -> Filter -> Bucket -> Pivot -> Aggregate -> Sort -> Output
81
+ *
82
+ * @param input - Pipeline configuration and source data
83
+ * @returns Transformed data, series metadata, and group labels
84
+ */
85
+ function runPipeline(input) {
86
+ const { data, columns, xAxisId, groupById, metric, timeBucket, filters, sorting } = input;
87
+ const xColumn = columns.find((column) => column.id === xAxisId);
88
+ if (!xColumn) return {
89
+ data: [],
90
+ series: [],
91
+ groups: []
92
+ };
93
+ const groupByColumn = groupById ? columns.find((column) => column.id === groupById) ?? null : null;
94
+ const numberColumns = columns.filter((column) => column.type === "number");
95
+ const { data: points, groups } = buildDataPoints(applyFilters(data, columns, filters), xColumn, groupByColumn, metric, numberColumns, timeBucket);
96
+ return {
97
+ data: applySorting(points, sorting, xColumn.type === "date"),
98
+ series: buildSeries(groups, getMetricLabel(metric, columns)),
99
+ groups
100
+ };
101
+ }
102
+ /**
103
+ * Extract available filter options from data for every category and boolean
104
+ * column.
105
+ *
106
+ * @param data - Raw data items
107
+ * @param columns - Column definitions
108
+ * @returns Filter metadata and option counts per filterable column
109
+ */
110
+ function extractAvailableFilters(data, columns) {
111
+ return columns.filter((column) => column.type === "category" || column.type === "boolean").map((column) => {
112
+ const counts = /* @__PURE__ */ new Map();
113
+ for (const item of data) {
114
+ const value = getStringValue(item, column);
115
+ const formattedLabel = formatFilterOptionLabel(item, column);
116
+ const existing = counts.get(value);
117
+ if (existing) {
118
+ counts.set(value, {
119
+ count: existing.count + 1,
120
+ label: existing.label
121
+ });
122
+ continue;
123
+ }
124
+ counts.set(value, {
125
+ count: 1,
126
+ label: formattedLabel
127
+ });
128
+ }
129
+ return {
130
+ columnId: column.id,
131
+ label: column.label,
132
+ type: column.type,
133
+ options: [...counts.entries()].toSorted(([, left], [, right]) => right.count - left.count).map(([value, option]) => ({
134
+ value,
135
+ label: option.label,
136
+ count: option.count
137
+ }))
138
+ };
139
+ });
140
+ }
141
+ /**
142
+ * Derive one human-facing filter option label from the typed column value while
143
+ * keeping the underlying filter state keyed by the stable string value.
144
+ */
145
+ function formatFilterOptionLabel(item, column) {
146
+ return formatChartValue(column.accessor(item), {
147
+ column,
148
+ surface: "tooltip",
149
+ item
150
+ });
151
+ }
152
+ //#endregion
153
+ export { applyFilters, extractAvailableFilters, runPipeline };