exploreda 0.0.5 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (208) hide show
  1. package/README.md +144 -0
  2. package/dist/ExplorEda.cjs +4608 -10858
  3. package/dist/ExplorEda.cjs.map +1 -1
  4. package/dist/ExplorEda.css +2332 -1653
  5. package/dist/ExplorEda.css.map +1 -1
  6. package/dist/ExplorEda.d.cts +125 -278
  7. package/dist/ExplorEda.d.ts +125 -278
  8. package/dist/ExplorEda.js +4521 -10782
  9. package/dist/ExplorEda.js.map +1 -1
  10. package/dist/calculations.cjs +18 -0
  11. package/dist/calculations.cjs.map +1 -0
  12. package/dist/calculations.d.cts +63 -0
  13. package/dist/calculations.d.ts +63 -0
  14. package/dist/calculations.js +3 -0
  15. package/dist/calculations.js.map +1 -0
  16. package/dist/charts/bar.cjs +27 -0
  17. package/dist/charts/bar.cjs.map +1 -0
  18. package/dist/charts/bar.d.cts +2 -0
  19. package/dist/charts/bar.d.ts +2 -0
  20. package/dist/charts/bar.js +18 -0
  21. package/dist/charts/bar.js.map +1 -0
  22. package/dist/charts/box-plot.cjs +26 -0
  23. package/dist/charts/box-plot.cjs.map +1 -0
  24. package/dist/charts/box-plot.d.cts +2 -0
  25. package/dist/charts/box-plot.d.ts +2 -0
  26. package/dist/charts/box-plot.js +17 -0
  27. package/dist/charts/box-plot.js.map +1 -0
  28. package/dist/charts/color-legend.cjs +22 -0
  29. package/dist/charts/color-legend.cjs.map +1 -0
  30. package/dist/charts/color-legend.d.cts +2 -0
  31. package/dist/charts/color-legend.d.ts +2 -0
  32. package/dist/charts/color-legend.js +13 -0
  33. package/dist/charts/color-legend.js.map +1 -0
  34. package/dist/charts/data-table.cjs +22 -0
  35. package/dist/charts/data-table.cjs.map +1 -0
  36. package/dist/charts/data-table.d.cts +2 -0
  37. package/dist/charts/data-table.d.ts +2 -0
  38. package/dist/charts/data-table.js +13 -0
  39. package/dist/charts/data-table.js.map +1 -0
  40. package/dist/charts/line.cjs +30 -0
  41. package/dist/charts/line.cjs.map +1 -0
  42. package/dist/charts/line.d.cts +2 -0
  43. package/dist/charts/line.d.ts +2 -0
  44. package/dist/charts/line.js +17 -0
  45. package/dist/charts/line.js.map +1 -0
  46. package/dist/charts/markdown.cjs +16 -0
  47. package/dist/charts/markdown.cjs.map +1 -0
  48. package/dist/charts/markdown.css +77 -0
  49. package/dist/charts/markdown.css.map +1 -0
  50. package/dist/charts/markdown.d.cts +2 -0
  51. package/dist/charts/markdown.d.ts +2 -0
  52. package/dist/charts/markdown.js +7 -0
  53. package/dist/charts/markdown.js.map +1 -0
  54. package/dist/charts/pivot-table.cjs +23 -0
  55. package/dist/charts/pivot-table.cjs.map +1 -0
  56. package/dist/charts/pivot-table.d.cts +2 -0
  57. package/dist/charts/pivot-table.d.ts +2 -0
  58. package/dist/charts/pivot-table.js +14 -0
  59. package/dist/charts/pivot-table.js.map +1 -0
  60. package/dist/charts/row.cjs +27 -0
  61. package/dist/charts/row.cjs.map +1 -0
  62. package/dist/charts/row.d.cts +6 -0
  63. package/dist/charts/row.d.ts +6 -0
  64. package/dist/charts/row.js +18 -0
  65. package/dist/charts/row.js.map +1 -0
  66. package/dist/charts/scatter.cjs +24 -0
  67. package/dist/charts/scatter.cjs.map +1 -0
  68. package/dist/charts/scatter.d.cts +2 -0
  69. package/dist/charts/scatter.d.ts +2 -0
  70. package/dist/charts/scatter.js +15 -0
  71. package/dist/charts/scatter.js.map +1 -0
  72. package/dist/charts/summary-table.cjs +19 -0
  73. package/dist/charts/summary-table.cjs.map +1 -0
  74. package/dist/charts/summary-table.d.cts +2 -0
  75. package/dist/charts/summary-table.d.ts +2 -0
  76. package/dist/charts/summary-table.js +10 -0
  77. package/dist/charts/summary-table.js.map +1 -0
  78. package/dist/charts/three-d-scatter.cjs +24 -0
  79. package/dist/charts/three-d-scatter.cjs.map +1 -0
  80. package/dist/charts/three-d-scatter.d.cts +6 -0
  81. package/dist/charts/three-d-scatter.d.ts +6 -0
  82. package/dist/charts/three-d-scatter.js +15 -0
  83. package/dist/charts/three-d-scatter.js.map +1 -0
  84. package/dist/chunk-3PGYFLE3.cjs +499 -0
  85. package/dist/chunk-3PGYFLE3.cjs.map +1 -0
  86. package/dist/chunk-4HNDKMWF.js +167 -0
  87. package/dist/chunk-4HNDKMWF.js.map +1 -0
  88. package/dist/chunk-4QNENKTV.cjs +1214 -0
  89. package/dist/chunk-4QNENKTV.cjs.map +1 -0
  90. package/dist/chunk-54M32JJB.js +132 -0
  91. package/dist/chunk-54M32JJB.js.map +1 -0
  92. package/dist/chunk-5B23JVH3.cjs +2238 -0
  93. package/dist/chunk-5B23JVH3.cjs.map +1 -0
  94. package/dist/chunk-5LH6NSNL.js +456 -0
  95. package/dist/chunk-5LH6NSNL.js.map +1 -0
  96. package/dist/chunk-6DOKKCJ5.cjs +313 -0
  97. package/dist/chunk-6DOKKCJ5.cjs.map +1 -0
  98. package/dist/chunk-6HZX5CBT.cjs +1074 -0
  99. package/dist/chunk-6HZX5CBT.cjs.map +1 -0
  100. package/dist/chunk-6U5DOIQM.js +1071 -0
  101. package/dist/chunk-6U5DOIQM.js.map +1 -0
  102. package/dist/chunk-7GBZIK2R.cjs +80 -0
  103. package/dist/chunk-7GBZIK2R.cjs.map +1 -0
  104. package/dist/chunk-7PTJNIOD.js +520 -0
  105. package/dist/chunk-7PTJNIOD.js.map +1 -0
  106. package/dist/chunk-AHZYQ7FN.js +319 -0
  107. package/dist/chunk-AHZYQ7FN.js.map +1 -0
  108. package/dist/chunk-AOODAOL4.cjs +55 -0
  109. package/dist/chunk-AOODAOL4.cjs.map +1 -0
  110. package/dist/chunk-ASKFIRMC.cjs +165 -0
  111. package/dist/chunk-ASKFIRMC.cjs.map +1 -0
  112. package/dist/chunk-B7OQUBH4.cjs +171 -0
  113. package/dist/chunk-B7OQUBH4.cjs.map +1 -0
  114. package/dist/chunk-BIMEHDLB.js +72 -0
  115. package/dist/chunk-BIMEHDLB.js.map +1 -0
  116. package/dist/chunk-C62KHV7S.js +1225 -0
  117. package/dist/chunk-C62KHV7S.js.map +1 -0
  118. package/dist/chunk-C7GQT65O.cjs +1119 -0
  119. package/dist/chunk-C7GQT65O.cjs.map +1 -0
  120. package/dist/chunk-CGDWDV4Z.js +1211 -0
  121. package/dist/chunk-CGDWDV4Z.js.map +1 -0
  122. package/dist/chunk-CJQQFNEY.js +69 -0
  123. package/dist/chunk-CJQQFNEY.js.map +1 -0
  124. package/dist/chunk-DHYLSXNR.js +964 -0
  125. package/dist/chunk-DHYLSXNR.js.map +1 -0
  126. package/dist/chunk-DKFA4KOJ.cjs +87 -0
  127. package/dist/chunk-DKFA4KOJ.cjs.map +1 -0
  128. package/dist/chunk-DS5BS7PP.cjs +357 -0
  129. package/dist/chunk-DS5BS7PP.cjs.map +1 -0
  130. package/dist/chunk-EGMFQSGK.js +909 -0
  131. package/dist/chunk-EGMFQSGK.js.map +1 -0
  132. package/dist/chunk-EU5EFHO5.cjs +53 -0
  133. package/dist/chunk-EU5EFHO5.cjs.map +1 -0
  134. package/dist/chunk-FM46QSDM.js +837 -0
  135. package/dist/chunk-FM46QSDM.js.map +1 -0
  136. package/dist/chunk-GV2LRR32.cjs +843 -0
  137. package/dist/chunk-GV2LRR32.cjs.map +1 -0
  138. package/dist/chunk-H3XKFSA5.cjs +322 -0
  139. package/dist/chunk-H3XKFSA5.cjs.map +1 -0
  140. package/dist/chunk-H5AVMMCE.js +347 -0
  141. package/dist/chunk-H5AVMMCE.js.map +1 -0
  142. package/dist/chunk-JDMUN4VK.cjs +986 -0
  143. package/dist/chunk-JDMUN4VK.cjs.map +1 -0
  144. package/dist/chunk-JNO7OXCC.js +2171 -0
  145. package/dist/chunk-JNO7OXCC.js.map +1 -0
  146. package/dist/chunk-JQMG4ZR7.js +111 -0
  147. package/dist/chunk-JQMG4ZR7.js.map +1 -0
  148. package/dist/chunk-L2Q2R43U.cjs +118 -0
  149. package/dist/chunk-L2Q2R43U.cjs.map +1 -0
  150. package/dist/chunk-LBTZVERO.js +466 -0
  151. package/dist/chunk-LBTZVERO.js.map +1 -0
  152. package/dist/chunk-LUUGGTTK.cjs +1227 -0
  153. package/dist/chunk-LUUGGTTK.cjs.map +1 -0
  154. package/dist/chunk-MEEPH7ZC.cjs +911 -0
  155. package/dist/chunk-MEEPH7ZC.cjs.map +1 -0
  156. package/dist/chunk-MJPDJ5AS.js +184 -0
  157. package/dist/chunk-MJPDJ5AS.js.map +1 -0
  158. package/dist/chunk-MJROUIDZ.cjs +219 -0
  159. package/dist/chunk-MJROUIDZ.cjs.map +1 -0
  160. package/dist/chunk-MUWSBYJS.js +528 -0
  161. package/dist/chunk-MUWSBYJS.js.map +1 -0
  162. package/dist/chunk-NDF5LCST.js +1088 -0
  163. package/dist/chunk-NDF5LCST.js.map +1 -0
  164. package/dist/chunk-NGWTPED4.js +311 -0
  165. package/dist/chunk-NGWTPED4.js.map +1 -0
  166. package/dist/chunk-PP4VCONW.js +739 -0
  167. package/dist/chunk-PP4VCONW.js.map +1 -0
  168. package/dist/chunk-PRMC2A6P.cjs +488 -0
  169. package/dist/chunk-PRMC2A6P.cjs.map +1 -0
  170. package/dist/chunk-PST6UU6S.cjs +742 -0
  171. package/dist/chunk-PST6UU6S.cjs.map +1 -0
  172. package/dist/chunk-Q7LK2UWH.js +33 -0
  173. package/dist/chunk-Q7LK2UWH.js.map +1 -0
  174. package/dist/chunk-SFV5LZPD.js +162 -0
  175. package/dist/chunk-SFV5LZPD.js.map +1 -0
  176. package/dist/chunk-SKXGYJJG.cjs +370 -0
  177. package/dist/chunk-SKXGYJJG.cjs.map +1 -0
  178. package/dist/chunk-SWLNKY6R.js +477 -0
  179. package/dist/chunk-SWLNKY6R.js.map +1 -0
  180. package/dist/chunk-TGVLNHUB.cjs +483 -0
  181. package/dist/chunk-TGVLNHUB.cjs.map +1 -0
  182. package/dist/chunk-TLLRW6S3.js +74 -0
  183. package/dist/chunk-TLLRW6S3.js.map +1 -0
  184. package/dist/chunk-TRAIXZ6M.cjs +140 -0
  185. package/dist/chunk-TRAIXZ6M.cjs.map +1 -0
  186. package/dist/chunk-U74A5VK5.cjs +74 -0
  187. package/dist/chunk-U74A5VK5.cjs.map +1 -0
  188. package/dist/chunk-UAVDLBTA.cjs +74 -0
  189. package/dist/chunk-UAVDLBTA.cjs.map +1 -0
  190. package/dist/chunk-UJWS6TRV.js +354 -0
  191. package/dist/chunk-UJWS6TRV.js.map +1 -0
  192. package/dist/chunk-UUJMUPB3.cjs +531 -0
  193. package/dist/chunk-UUJMUPB3.cjs.map +1 -0
  194. package/dist/chunk-VTKHZIJB.cjs +549 -0
  195. package/dist/chunk-VTKHZIJB.cjs.map +1 -0
  196. package/dist/chunk-YXPAWUXO.js +51 -0
  197. package/dist/chunk-YXPAWUXO.js.map +1 -0
  198. package/dist/chunk-ZD64SCZB.js +80 -0
  199. package/dist/chunk-ZD64SCZB.js.map +1 -0
  200. package/dist/core.cjs +28 -0
  201. package/dist/core.cjs.map +1 -0
  202. package/dist/core.d.cts +22 -0
  203. package/dist/core.d.ts +22 -0
  204. package/dist/core.js +3 -0
  205. package/dist/core.js.map +1 -0
  206. package/dist/dataLayerState-B4XmQjQN.d.cts +258 -0
  207. package/dist/dataLayerState-B4XmQjQN.d.ts +258 -0
  208. package/package.json +32 -52
@@ -0,0 +1,909 @@
1
+ import { DEFAULT_CHART_SETTINGS } from './chunk-CJQQFNEY.js';
2
+ import { FieldSelector, ComboBox } from './chunk-SFV5LZPD.js';
3
+ import { useGetLiveIds } from './chunk-ZD64SCZB.js';
4
+ import { applyFilter } from './chunk-YXPAWUXO.js';
5
+ import { useColumnNames, multi_select_default } from './chunk-SWLNKY6R.js';
6
+ import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, Label } from './chunk-MJPDJ5AS.js';
7
+ import { useDataLayer, categoryIncludes, getChartSummary, categoryKey, cn, ActionTooltip, hasFieldDisplayFormat, categoryValue, categoryLabel, categoryEqual, numericInputs } from './chunk-JNO7OXCC.js';
8
+ import { Table, Filter } from 'lucide-react';
9
+ import { useMemo, useState, useRef, useEffect, useCallback } from 'react';
10
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
11
+
12
+ // src/components/charts/PivotTable/utils/calculations.ts
13
+ var numericAggregations = /* @__PURE__ */ new Set([
14
+ "sum",
15
+ "avg",
16
+ "min",
17
+ "max",
18
+ "median",
19
+ "stddev",
20
+ "variance"
21
+ ]);
22
+ function aggregate(name, values) {
23
+ const includedIndexes = /* @__PURE__ */ new Set();
24
+ const numeric = numericAggregations.has(name) ? numericInputs(values) : { inputs: [], exclusions: [] };
25
+ numeric.inputs.forEach(({ index }) => includedIndexes.add(index));
26
+ if (name === "count") {
27
+ values.forEach((_, index) => includedIndexes.add(index));
28
+ return { value: values.length, includedIndexes, numericExclusions: [] };
29
+ }
30
+ if (name === "countUnique") {
31
+ values.forEach((_, index) => includedIndexes.add(index));
32
+ return {
33
+ value: new Set(values).size,
34
+ includedIndexes,
35
+ numericExclusions: []
36
+ };
37
+ }
38
+ if (name === "mode") {
39
+ values.forEach((_, index) => includedIndexes.add(index));
40
+ const counts = /* @__PURE__ */ new Map();
41
+ values.forEach((value2) => {
42
+ const key = value2 ?? null;
43
+ counts.set(key, (counts.get(key) || 0) + 1);
44
+ });
45
+ let maxCount = 0;
46
+ let value = void 0;
47
+ counts.forEach((count, candidate) => {
48
+ if (count > maxCount) {
49
+ maxCount = count;
50
+ value = candidate === null ? void 0 : candidate;
51
+ }
52
+ });
53
+ return { value, includedIndexes, numericExclusions: [] };
54
+ }
55
+ if (name === "singleValue") {
56
+ if (values.length === 0) {
57
+ return { value: void 0, includedIndexes, numericExclusions: [] };
58
+ }
59
+ const uniqueValues = new Set(values.map((value) => categoryKey(value)));
60
+ if (uniqueValues.size !== 1) {
61
+ throw new Error("Multiple values found when single value expected");
62
+ }
63
+ values.forEach((_, index) => includedIndexes.add(index));
64
+ return { value: values[0], includedIndexes, numericExclusions: [] };
65
+ }
66
+ if (!numericAggregations.has(name)) {
67
+ throw new Error(`Unsupported aggregation: ${name}`);
68
+ }
69
+ const numbers = numeric.inputs.map(({ value }) => value);
70
+ if (numbers.length === 0) {
71
+ return {
72
+ value: void 0,
73
+ includedIndexes,
74
+ numericExclusions: numeric.exclusions
75
+ };
76
+ }
77
+ if (name === "sum") {
78
+ return {
79
+ value: numbers.reduce((sum, value) => sum + value, 0),
80
+ includedIndexes,
81
+ numericExclusions: numeric.exclusions
82
+ };
83
+ }
84
+ if (name === "avg") {
85
+ return {
86
+ value: numbers.reduce((sum, value) => sum + value, 0) / numbers.length,
87
+ includedIndexes,
88
+ numericExclusions: numeric.exclusions
89
+ };
90
+ }
91
+ if (name === "min") {
92
+ return {
93
+ value: Math.min(...numbers),
94
+ includedIndexes,
95
+ numericExclusions: numeric.exclusions
96
+ };
97
+ }
98
+ if (name === "max") {
99
+ return {
100
+ value: Math.max(...numbers),
101
+ includedIndexes,
102
+ numericExclusions: numeric.exclusions
103
+ };
104
+ }
105
+ if (name === "median") {
106
+ const sorted = [...numbers].sort((a, b) => a - b);
107
+ const mid = Math.floor(sorted.length / 2);
108
+ return {
109
+ value: sorted.length % 2 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2,
110
+ includedIndexes,
111
+ numericExclusions: numeric.exclusions
112
+ };
113
+ }
114
+ const average = numbers.reduce((sum, value) => sum + value, 0) / numbers.length;
115
+ const squareDiffs = numbers.map((value) => (value - average) ** 2);
116
+ const variance = squareDiffs.reduce((sum, value) => sum + value, 0) / squareDiffs.length;
117
+ return {
118
+ value: name === "stddev" ? Math.sqrt(variance) : variance,
119
+ includedIndexes,
120
+ numericExclusions: numeric.exclusions
121
+ };
122
+ }
123
+ function generateHeaders(data, field) {
124
+ if (!field) {
125
+ return [];
126
+ }
127
+ const uniqueValues = /* @__PURE__ */ new Map();
128
+ data.forEach((row) => {
129
+ const value = categoryValue(row[field]);
130
+ const key = categoryKey(value);
131
+ if (!uniqueValues.has(key)) {
132
+ uniqueValues.set(key, value);
133
+ }
134
+ });
135
+ return Array.from(uniqueValues.values()).sort((left, right) => categoryKey(left).localeCompare(categoryKey(right))).map((value) => ({
136
+ label: categoryLabel(value),
137
+ field,
138
+ value,
139
+ children: [],
140
+ span: 1,
141
+ depth: 0
142
+ }));
143
+ }
144
+ function makeContributors(rows, groupingKeys, valueField, result) {
145
+ const exclusionByIndex = new Map(
146
+ result.numericExclusions.map((exclusion) => [
147
+ exclusion.index,
148
+ exclusion.reason
149
+ ])
150
+ );
151
+ const contributors = rows.map((row, index) => ({
152
+ sourceId: row.__ID,
153
+ groupingKeys,
154
+ input: row[valueField],
155
+ included: result.includedIndexes.has(index),
156
+ exclusionReason: exclusionByIndex.get(index)
157
+ }));
158
+ return {
159
+ contributors,
160
+ numericExclusions: result.numericExclusions.map(({ index, reason }) => ({
161
+ sourceId: rows[index]?.__ID,
162
+ value: rows[index]?.[valueField],
163
+ reason
164
+ }))
165
+ };
166
+ }
167
+ function generateCell(rows, groupingKeys, columnField, columnValue, isTotal, valueField) {
168
+ const values = rows.map((row) => row[valueField.field]);
169
+ let result;
170
+ let error;
171
+ try {
172
+ result = aggregate(valueField.aggregation, values);
173
+ } catch (caught) {
174
+ result = {
175
+ value: void 0,
176
+ includedIndexes: /* @__PURE__ */ new Set(),
177
+ numericExclusions: []
178
+ };
179
+ error = caught instanceof Error ? caught.message : "Aggregation failed";
180
+ }
181
+ const { contributors, numericExclusions } = makeContributors(
182
+ rows,
183
+ groupingKeys,
184
+ valueField.field,
185
+ result
186
+ );
187
+ const status = error ? "error" : rows.length === 0 ? "empty" : result.numericExclusions.length > 0 && result.includedIndexes.size === 0 ? "invalid" : "ok";
188
+ return {
189
+ key: {
190
+ columnField,
191
+ columnValue,
192
+ valueField: valueField.field,
193
+ isTotal
194
+ },
195
+ value: result.value,
196
+ rawValue: result.value,
197
+ aggregation: valueField.aggregation,
198
+ status,
199
+ error,
200
+ contributors,
201
+ numericExclusions
202
+ };
203
+ }
204
+ function generateCells(rowData, columnHeaders, rowKeys, hasColumnField, valueFields) {
205
+ const cells = [];
206
+ const headers = hasColumnField ? columnHeaders : [{ field: "total", value: "total" }];
207
+ headers.forEach((header) => {
208
+ const columnData = hasColumnField ? rowData.filter(
209
+ (row) => categoryEqual(categoryValue(row[header.field]), header.value)
210
+ ) : rowData;
211
+ const groupingKeys = !hasColumnField ? rowKeys : [...rowKeys, { field: header.field, value: header.value }];
212
+ valueFields.forEach((valueField) => {
213
+ cells.push(
214
+ generateCell(
215
+ columnData,
216
+ groupingKeys,
217
+ hasColumnField ? header.field : "total",
218
+ header.value,
219
+ !hasColumnField,
220
+ valueField
221
+ )
222
+ );
223
+ });
224
+ });
225
+ return cells;
226
+ }
227
+ function generateRows(data, rowFields, columnField, valueFields) {
228
+ const rows = [];
229
+ const columnHeaders = generateHeaders(data, columnField);
230
+ const rowGroups = /* @__PURE__ */ new Map();
231
+ data.forEach((item) => {
232
+ const keyString = JSON.stringify(
233
+ rowFields.map((field) => categoryKey(categoryValue(item[field])))
234
+ );
235
+ if (!rowGroups.has(keyString)) {
236
+ rowGroups.set(keyString, []);
237
+ }
238
+ rowGroups.get(keyString).push(item);
239
+ });
240
+ rowGroups.forEach((groupData) => {
241
+ const keys = rowFields.map((field) => ({
242
+ field,
243
+ value: categoryValue(groupData[0][field])
244
+ }));
245
+ const headers = keys.map((key, index) => ({
246
+ label: categoryLabel(key.value),
247
+ field: key.field,
248
+ value: key.value,
249
+ span: 1,
250
+ depth: index
251
+ }));
252
+ rows.push({
253
+ keys,
254
+ headers,
255
+ cells: generateCells(
256
+ groupData,
257
+ columnHeaders,
258
+ keys,
259
+ Boolean(columnField),
260
+ valueFields
261
+ )
262
+ });
263
+ });
264
+ return rows;
265
+ }
266
+ function calculatePivotData(data, settings) {
267
+ return {
268
+ headers: generateHeaders(data, settings.columnField),
269
+ rows: generateRows(
270
+ data,
271
+ settings.rowFields,
272
+ settings.columnField,
273
+ settings.valueFields
274
+ )
275
+ };
276
+ }
277
+ function displayValue(value) {
278
+ if (value === void 0 || value === null || value === "") {
279
+ return "Missing";
280
+ }
281
+ if (typeof value === "number") {
282
+ return String(value);
283
+ }
284
+ if (typeof value === "string") {
285
+ return JSON.stringify(value);
286
+ }
287
+ return String(value);
288
+ }
289
+ function displayExactValue(value) {
290
+ if (value === void 0) {
291
+ return "undefined";
292
+ }
293
+ if (value === null) {
294
+ return "null";
295
+ }
296
+ if (typeof value === "string") {
297
+ return JSON.stringify(value);
298
+ }
299
+ return String(value);
300
+ }
301
+ function displayCellValue(cell) {
302
+ if (cell.status === "empty") {
303
+ return "No rows";
304
+ }
305
+ if (cell.status === "error") {
306
+ return "Conflict";
307
+ }
308
+ if (cell.status === "invalid") {
309
+ return "No valid numbers";
310
+ }
311
+ return displayValue(cell.value);
312
+ }
313
+ function cellName(cell, rowHeaders) {
314
+ const rowLabel = rowHeaders.map((header) => header.label).join(" / ");
315
+ const columnLabel = cell.key.isTotal ? "Total" : displayValue(cell.key.columnValue);
316
+ return [rowLabel, columnLabel, cell.key.valueField].filter(Boolean).join(" \xB7 ");
317
+ }
318
+ function PivotTable({ settings, height, facetIds }) {
319
+ const getColumnData = useDataLayer((state) => state.getColumnData);
320
+ const getFieldLabel = useDataLayer((state) => state.getFieldLabel);
321
+ const formatFieldValue = useDataLayer((state) => state.formatFieldValue);
322
+ const fieldSettings = useDataLayer((state) => state.fieldSettings);
323
+ const updateChart = useDataLayer((state) => state.updateChart);
324
+ const allLiveIds = useGetLiveIds(settings);
325
+ const pivotData = useMemo(() => {
326
+ const liveIds = facetIds ? allLiveIds.filter((id) => facetIds.includes(id)) : allLiveIds;
327
+ const allFields = /* @__PURE__ */ new Set([
328
+ ...settings.rowFields,
329
+ settings.columnField,
330
+ ...settings.valueFields.map((f) => f.field)
331
+ ]);
332
+ const fieldData = {};
333
+ allFields.forEach((field) => {
334
+ fieldData[field] = getColumnData(field);
335
+ });
336
+ const data = liveIds.map((id) => {
337
+ const row = {
338
+ __ID: id
339
+ };
340
+ allFields.forEach((field) => {
341
+ row[field] = fieldData[field]?.[id];
342
+ });
343
+ return row;
344
+ });
345
+ return calculatePivotData(data, settings);
346
+ }, [facetIds, allLiveIds, settings, getColumnData]);
347
+ const [selectedCell, setSelectedCell] = useState(null);
348
+ const lastTrigger = useRef(null);
349
+ const [contributorPage, setContributorPage] = useState(0);
350
+ useEffect(() => {
351
+ setSelectedCell(null);
352
+ }, [pivotData, settings]);
353
+ useEffect(() => {
354
+ setContributorPage(0);
355
+ }, [selectedCell]);
356
+ const handleFilterClick = useCallback(
357
+ (field, value) => {
358
+ const currentFilters = settings.filters || [];
359
+ const existingFilterIndex = currentFilters.findIndex(
360
+ (f) => f.type === "value" && f.field === field && categoryIncludes(f.values, value)
361
+ );
362
+ let newFilters;
363
+ if (existingFilterIndex >= 0) {
364
+ newFilters = [
365
+ ...currentFilters.slice(0, existingFilterIndex),
366
+ ...currentFilters.slice(existingFilterIndex + 1)
367
+ ];
368
+ } else {
369
+ const newFilter = {
370
+ type: "value",
371
+ field,
372
+ values: [value]
373
+ };
374
+ newFilters = [...currentFilters, newFilter];
375
+ }
376
+ updateChart(settings.id, {
377
+ ...settings,
378
+ filters: newFilters
379
+ });
380
+ },
381
+ [settings, updateChart]
382
+ );
383
+ const isValueFiltered = useCallback(
384
+ (field, value) => {
385
+ const currentFilters = settings.filters || [];
386
+ const fieldFilters = currentFilters.filter((f) => f.field === field);
387
+ if (fieldFilters.length === 0) {
388
+ return false;
389
+ }
390
+ return fieldFilters.some((filter) => applyFilter(value, filter));
391
+ },
392
+ [settings.filters]
393
+ );
394
+ const isCellFiltered = useCallback(
395
+ (rowHeaders, cellKey) => {
396
+ const currentFilters = settings.filters || [];
397
+ if (currentFilters.length === 0) {
398
+ return false;
399
+ }
400
+ const rowFieldFilters = rowHeaders.map((header) => {
401
+ const fieldFilters = currentFilters.filter(
402
+ (f) => f.field === header.field
403
+ );
404
+ return {
405
+ header,
406
+ filters: fieldFilters
407
+ };
408
+ });
409
+ const hasRowFilters = rowFieldFilters.some((rf) => rf.filters.length > 0);
410
+ const rowMatches = !hasRowFilters || rowFieldFilters.every(
411
+ ({ header, filters }) => filters.length === 0 || filters.some((filter) => applyFilter(header.value, filter))
412
+ );
413
+ const columnFilters = currentFilters.filter(
414
+ (f) => !cellKey.isTotal && f.field === cellKey.columnField
415
+ );
416
+ const hasColumnFilters = columnFilters.length > 0;
417
+ const columnMatches = !hasColumnFilters || columnFilters.some(
418
+ (filter) => applyFilter(cellKey.columnValue, filter)
419
+ );
420
+ return rowMatches && columnMatches;
421
+ },
422
+ [settings.filters]
423
+ );
424
+ const filterButton = (header) => /* @__PURE__ */ jsxs(
425
+ "button",
426
+ {
427
+ type: "button",
428
+ className: "eda-pivot-filter",
429
+ "aria-label": `Filter ${header.field} by ${header.label}`,
430
+ "aria-pressed": isValueFiltered(header.field, header.value),
431
+ onClick: () => handleFilterClick(header.field, header.value),
432
+ children: [
433
+ /* @__PURE__ */ jsx("span", { children: hasFieldDisplayFormat(fieldSettings[header.field]) ? formatFieldValue?.(header.field, header.value) ?? header.label : header.label }),
434
+ /* @__PURE__ */ jsx(Filter, { "aria-hidden": "true" })
435
+ ]
436
+ }
437
+ );
438
+ const valueLabel = (field, aggregation, label) => label || `${getFieldLabel?.(field) ?? field} (${aggregation})`;
439
+ const displayPivotCell = (cell) => {
440
+ if (cell.status !== "ok") return displayCellValue(cell);
441
+ return hasFieldDisplayFormat(fieldSettings[cell.key.valueField]) ? formatFieldValue?.(cell.key.valueField, cell.value) ?? displayCellValue(cell) : displayCellValue(cell);
442
+ };
443
+ const selectedCellName = selectedCell ? cellName(selectedCell.cell, selectedCell.rowHeaders) : "";
444
+ const valueColumnCount = settings.valueFields.length * (settings.columnField ? pivotData.headers.length : 1);
445
+ const contributorPageSize = 50;
446
+ const contributorCount = selectedCell?.cell.contributors.length ?? 0;
447
+ const contributorPageCount = Math.max(
448
+ 1,
449
+ Math.ceil(contributorCount / contributorPageSize)
450
+ );
451
+ const contributorStart = contributorPage * contributorPageSize;
452
+ const visibleContributors = selectedCell?.cell.contributors.slice(
453
+ contributorStart,
454
+ contributorStart + contributorPageSize
455
+ ) ?? [];
456
+ return /* @__PURE__ */ jsxs(
457
+ "div",
458
+ {
459
+ className: "w-full min-h-0 overflow-auto",
460
+ style: { height },
461
+ tabIndex: 0,
462
+ "aria-label": "Pivot table, scroll to see more",
463
+ children: [
464
+ /* @__PURE__ */ jsxs(
465
+ "table",
466
+ {
467
+ className: "eda-pivot-table",
468
+ style: {
469
+ minWidth: settings.rowFields.length * 140 + valueColumnCount * 112
470
+ },
471
+ children: [
472
+ /* @__PURE__ */ jsx("caption", { className: "sr-only", children: getChartSummary(settings) }),
473
+ /* @__PURE__ */ jsxs("colgroup", { children: [
474
+ settings.rowFields.map((field) => /* @__PURE__ */ jsx("col", { style: { width: 140 } }, field)),
475
+ Array.from({ length: valueColumnCount }, (_, index) => /* @__PURE__ */ jsx("col", {}, index))
476
+ ] }),
477
+ /* @__PURE__ */ jsxs("thead", { className: "sticky top-0 z-30", children: [
478
+ /* @__PURE__ */ jsxs("tr", { children: [
479
+ settings.rowFields.map((field, index) => /* @__PURE__ */ jsx(
480
+ "th",
481
+ {
482
+ scope: "col",
483
+ rowSpan: settings.columnField ? 2 : 1,
484
+ className: "eda-pivot-row-label",
485
+ style: { left: index * 140, zIndex: 20 - index },
486
+ children: getFieldLabel?.(field) ?? field
487
+ },
488
+ field
489
+ )),
490
+ settings.columnField ? pivotData.headers.map((header) => /* @__PURE__ */ jsx(
491
+ "th",
492
+ {
493
+ scope: "colgroup",
494
+ colSpan: settings.valueFields.length,
495
+ children: filterButton(header)
496
+ },
497
+ `${header.field}-${categoryKey(header.value)}`
498
+ )) : settings.valueFields.map((valueField) => /* @__PURE__ */ jsx("th", { scope: "col", children: valueLabel(
499
+ valueField.field,
500
+ valueField.aggregation,
501
+ valueField.label
502
+ ) }, valueField.field))
503
+ ] }),
504
+ settings.columnField && /* @__PURE__ */ jsx("tr", { children: pivotData.headers.flatMap(
505
+ (header) => settings.valueFields.map((valueField) => /* @__PURE__ */ jsx(
506
+ "th",
507
+ {
508
+ scope: "col",
509
+ children: valueLabel(
510
+ valueField.field,
511
+ valueField.aggregation,
512
+ valueField.label
513
+ )
514
+ },
515
+ `${header.field}-${categoryKey(header.value)}-${valueField.field}`
516
+ ))
517
+ ) })
518
+ ] }),
519
+ /* @__PURE__ */ jsxs("tbody", { children: [
520
+ pivotData.rows.map((row) => /* @__PURE__ */ jsxs(
521
+ "tr",
522
+ {
523
+ children: [
524
+ row.headers.map((header, index) => /* @__PURE__ */ jsx(
525
+ "th",
526
+ {
527
+ scope: "row",
528
+ className: "eda-pivot-row-label",
529
+ style: { left: index * 140, zIndex: 10 - index },
530
+ children: filterButton(header)
531
+ },
532
+ `${header.field}-${categoryKey(header.value)}`
533
+ )),
534
+ row.cells.map((cell) => /* @__PURE__ */ jsx(
535
+ "td",
536
+ {
537
+ className: cn(
538
+ isCellFiltered(row.headers, cell.key) && "is-selected"
539
+ ),
540
+ children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end gap-2", children: [
541
+ /* @__PURE__ */ jsx(
542
+ ActionTooltip,
543
+ {
544
+ content: cell.error ?? cellName(cell, row.headers),
545
+ children: /* @__PURE__ */ jsx("span", { tabIndex: cell.error ? 0 : void 0, children: displayPivotCell(cell) })
546
+ }
547
+ ),
548
+ /* @__PURE__ */ jsx(
549
+ "button",
550
+ {
551
+ type: "button",
552
+ className: "rounded px-1 text-[10px] text-muted-foreground underline-offset-2 hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
553
+ "aria-haspopup": "dialog",
554
+ "aria-label": `Inspect ${cellName(cell, row.headers)}`,
555
+ onClick: (event) => {
556
+ lastTrigger.current = event.currentTarget;
557
+ setSelectedCell({ cell, rowHeaders: row.headers });
558
+ },
559
+ children: "Inspect"
560
+ }
561
+ )
562
+ ] })
563
+ },
564
+ `${cell.key.isTotal ? "total" : cell.key.columnField}-${categoryKey(cell.key.columnValue)}-${cell.key.valueField}`
565
+ ))
566
+ ]
567
+ },
568
+ JSON.stringify(
569
+ row.keys.map((key) => [key.field, categoryKey(key.value)])
570
+ )
571
+ )),
572
+ pivotData.rows.length === 0 && /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx(
573
+ "td",
574
+ {
575
+ colSpan: settings.rowFields.length + valueColumnCount,
576
+ className: "text-muted-foreground",
577
+ children: "No matching rows"
578
+ }
579
+ ) })
580
+ ] })
581
+ ]
582
+ }
583
+ ),
584
+ /* @__PURE__ */ jsx(
585
+ Dialog,
586
+ {
587
+ open: selectedCell !== null,
588
+ onOpenChange: (open) => {
589
+ if (!open) {
590
+ setSelectedCell(null);
591
+ window.setTimeout(() => lastTrigger.current?.focus(), 0);
592
+ }
593
+ },
594
+ children: /* @__PURE__ */ jsx(DialogContent, { className: "max-h-[80vh] max-w-3xl overflow-y-auto", children: selectedCell && /* @__PURE__ */ jsxs(Fragment, { children: [
595
+ /* @__PURE__ */ jsxs(DialogHeader, { children: [
596
+ /* @__PURE__ */ jsxs(DialogTitle, { children: [
597
+ "Inspect ",
598
+ selectedCellName
599
+ ] }),
600
+ /* @__PURE__ */ jsx(DialogDescription, { children: "This cell uses the current filtered rows and the same aggregation as the table." })
601
+ ] }),
602
+ /* @__PURE__ */ jsxs("div", { className: "grid gap-2 text-sm sm:grid-cols-3", children: [
603
+ /* @__PURE__ */ jsxs("div", { children: [
604
+ /* @__PURE__ */ jsx("div", { className: "text-muted-foreground", children: "Aggregation" }),
605
+ /* @__PURE__ */ jsx("div", { children: selectedCell.cell.aggregation })
606
+ ] }),
607
+ /* @__PURE__ */ jsxs("div", { children: [
608
+ /* @__PURE__ */ jsx("div", { className: "text-muted-foreground", children: "Result" }),
609
+ /* @__PURE__ */ jsx("div", { children: displayPivotCell(selectedCell.cell) })
610
+ ] }),
611
+ /* @__PURE__ */ jsxs("div", { children: [
612
+ /* @__PURE__ */ jsx("div", { className: "text-muted-foreground", children: "Contributors" }),
613
+ /* @__PURE__ */ jsx("div", { children: selectedCell.cell.contributors.length })
614
+ ] })
615
+ ] }),
616
+ selectedCell.cell.error && /* @__PURE__ */ jsxs(
617
+ "p",
618
+ {
619
+ role: "alert",
620
+ className: "rounded border border-destructive/50 p-2 text-sm text-destructive",
621
+ children: [
622
+ selectedCell.cell.error,
623
+ ". The other pivot cells remain available."
624
+ ]
625
+ }
626
+ ),
627
+ selectedCell.cell.numericExclusions.length > 0 && /* @__PURE__ */ jsxs("p", { className: "text-sm text-muted-foreground", children: [
628
+ selectedCell.cell.numericExclusions.length,
629
+ " numeric value(s) excluded; the result uses the",
630
+ " ",
631
+ selectedCell.cell.contributors.filter(
632
+ (contributor) => contributor.included
633
+ ).length,
634
+ " ",
635
+ "valid value(s)."
636
+ ] }),
637
+ /* @__PURE__ */ jsxs("div", { className: "overflow-auto rounded border", children: [
638
+ /* @__PURE__ */ jsxs("table", { className: "w-full text-left text-sm", children: [
639
+ /* @__PURE__ */ jsx("caption", { className: "sr-only", children: "Pivot cell contributors" }),
640
+ /* @__PURE__ */ jsx("thead", { className: "bg-muted", children: /* @__PURE__ */ jsxs("tr", { children: [
641
+ /* @__PURE__ */ jsx("th", { className: "p-2", scope: "col", children: "Source row ID (zero-based)" }),
642
+ /* @__PURE__ */ jsx("th", { className: "p-2", scope: "col", children: "Grouping keys" }),
643
+ /* @__PURE__ */ jsx("th", { className: "p-2", scope: "col", children: "Input" }),
644
+ /* @__PURE__ */ jsx("th", { className: "p-2", scope: "col", children: "Used" })
645
+ ] }) }),
646
+ /* @__PURE__ */ jsx("tbody", { children: visibleContributors.map((contributor, index) => /* @__PURE__ */ jsxs(
647
+ "tr",
648
+ {
649
+ className: "border-t",
650
+ children: [
651
+ /* @__PURE__ */ jsx("td", { className: "p-2", children: contributor.sourceId ?? "Unknown" }),
652
+ /* @__PURE__ */ jsx("td", { className: "p-2", children: contributor.groupingKeys.map(
653
+ (key) => `${key.field}=${displayValue(key.value)}`
654
+ ).join(", ") }),
655
+ /* @__PURE__ */ jsx("td", { className: "p-2", children: displayExactValue(contributor.input) }),
656
+ /* @__PURE__ */ jsx("td", { className: "p-2", children: contributor.included ? "Yes" : contributor.exclusionReason || "No" })
657
+ ]
658
+ },
659
+ `${contributor.sourceId ?? "unknown"}-${index}`
660
+ )) })
661
+ ] }),
662
+ contributorCount === 0 && /* @__PURE__ */ jsx("p", { className: "p-3 text-sm text-muted-foreground", children: "No rows match this cell." })
663
+ ] }),
664
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-3 text-sm text-muted-foreground", children: [
665
+ /* @__PURE__ */ jsxs("span", { children: [
666
+ "Showing ",
667
+ contributorCount === 0 ? 0 : contributorStart + 1,
668
+ "\u2013",
669
+ Math.min(
670
+ contributorStart + contributorPageSize,
671
+ contributorCount
672
+ ),
673
+ " ",
674
+ "of ",
675
+ contributorCount,
676
+ " source rows"
677
+ ] }),
678
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
679
+ /* @__PURE__ */ jsx(
680
+ "button",
681
+ {
682
+ type: "button",
683
+ className: "rounded border px-2 py-1 disabled:cursor-not-allowed disabled:opacity-50",
684
+ disabled: contributorPage === 0,
685
+ onClick: () => setContributorPage((page) => page - 1),
686
+ children: "Previous"
687
+ }
688
+ ),
689
+ /* @__PURE__ */ jsx(
690
+ "button",
691
+ {
692
+ type: "button",
693
+ className: "rounded border px-2 py-1 disabled:cursor-not-allowed disabled:opacity-50",
694
+ disabled: contributorPage >= contributorPageCount - 1,
695
+ onClick: () => setContributorPage((page) => page + 1),
696
+ children: "Next"
697
+ }
698
+ )
699
+ ] })
700
+ ] })
701
+ ] }) })
702
+ }
703
+ )
704
+ ]
705
+ }
706
+ );
707
+ }
708
+ var AGGREGATION_OPTIONS = [
709
+ { label: "Sum", value: "sum" },
710
+ { label: "Count", value: "count" },
711
+ { label: "Average", value: "avg" },
712
+ { label: "Min", value: "min" },
713
+ { label: "Max", value: "max" },
714
+ { label: "Median", value: "median" },
715
+ { label: "Mode", value: "mode" },
716
+ { label: "StdDev", value: "stddev" },
717
+ { label: "Variance", value: "variance" },
718
+ { label: "Count Unique", value: "countUnique" },
719
+ { label: "Single Value", value: "singleValue" }
720
+ ];
721
+ function PivotTableSettingsPanel({
722
+ settings,
723
+ onSettingsChange
724
+ }) {
725
+ const availableFields = useColumnNames();
726
+ const fieldOptions = availableFields.map((f) => ({
727
+ label: f,
728
+ value: f
729
+ }));
730
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
731
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
732
+ /* @__PURE__ */ jsx(Label, { children: "Row Fields" }),
733
+ /* @__PURE__ */ jsx("div", { className: "max-w-[400px]", children: /* @__PURE__ */ jsx(
734
+ multi_select_default,
735
+ {
736
+ options: fieldOptions,
737
+ value: settings.rowFields.map((f) => ({
738
+ label: f,
739
+ value: f
740
+ })),
741
+ onChange: (values) => onSettingsChange({
742
+ ...settings,
743
+ rowFields: values.map((v) => v.value)
744
+ })
745
+ }
746
+ ) })
747
+ ] }),
748
+ /* @__PURE__ */ jsx("div", { className: "space-y-2", children: /* @__PURE__ */ jsx("div", { className: "max-w-[400px]", children: /* @__PURE__ */ jsx(
749
+ FieldSelector,
750
+ {
751
+ label: "Column Field",
752
+ value: settings.columnField,
753
+ onChange: (value) => onSettingsChange({ ...settings, columnField: value })
754
+ }
755
+ ) }) }),
756
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
757
+ /* @__PURE__ */ jsx(Label, { children: "Value Fields" }),
758
+ /* @__PURE__ */ jsxs("div", { className: "max-w-[400px] space-y-2", children: [
759
+ settings.valueFields.map((valueField, index) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
760
+ /* @__PURE__ */ jsx("div", { className: "flex-1", children: /* @__PURE__ */ jsx(
761
+ ComboBox,
762
+ {
763
+ options: fieldOptions,
764
+ value: fieldOptions.find((f) => f.value === valueField.field),
765
+ onChange: (option) => {
766
+ const newValueFields = [...settings.valueFields];
767
+ newValueFields[index] = {
768
+ ...valueField,
769
+ field: option?.value || valueField.field
770
+ };
771
+ onSettingsChange({
772
+ ...settings,
773
+ valueFields: newValueFields
774
+ });
775
+ },
776
+ optionToString: (option) => option.label
777
+ }
778
+ ) }),
779
+ /* @__PURE__ */ jsx("div", { className: "flex-1", children: /* @__PURE__ */ jsx(
780
+ ComboBox,
781
+ {
782
+ options: AGGREGATION_OPTIONS,
783
+ value: AGGREGATION_OPTIONS.find(
784
+ (o) => o.value === valueField.aggregation
785
+ ),
786
+ onChange: (option) => {
787
+ if (!option) {
788
+ return;
789
+ }
790
+ const newValueFields = [...settings.valueFields];
791
+ newValueFields[index] = {
792
+ ...valueField,
793
+ aggregation: option.value
794
+ };
795
+ onSettingsChange({
796
+ ...settings,
797
+ valueFields: newValueFields
798
+ });
799
+ },
800
+ optionToString: (option) => option.label
801
+ }
802
+ ) }),
803
+ /* @__PURE__ */ jsx(
804
+ "button",
805
+ {
806
+ className: "p-2 hover:bg-accent rounded",
807
+ onClick: () => {
808
+ const newValueFields = settings.valueFields.filter(
809
+ (_, i) => i !== index
810
+ );
811
+ onSettingsChange({
812
+ ...settings,
813
+ valueFields: newValueFields
814
+ });
815
+ },
816
+ children: "\xD7"
817
+ }
818
+ )
819
+ ] }, index)),
820
+ /* @__PURE__ */ jsx(
821
+ "button",
822
+ {
823
+ className: "text-sm text-primary hover:text-primary/80",
824
+ onClick: () => {
825
+ const field = availableFields[0];
826
+ if (!field) {
827
+ return;
828
+ }
829
+ onSettingsChange({
830
+ ...settings,
831
+ valueFields: [
832
+ ...settings.valueFields,
833
+ {
834
+ field,
835
+ aggregation: "count"
836
+ }
837
+ ]
838
+ });
839
+ },
840
+ children: "+ Add Value Field"
841
+ }
842
+ )
843
+ ] })
844
+ ] })
845
+ ] });
846
+ }
847
+
848
+ // src/components/charts/PivotTable/definition.ts
849
+ var VALID_AGGREGATIONS = /* @__PURE__ */ new Set([
850
+ "sum",
851
+ "count",
852
+ "avg",
853
+ "min",
854
+ "max",
855
+ "median",
856
+ "mode",
857
+ "stddev",
858
+ "variance",
859
+ "countUnique",
860
+ "singleValue"
861
+ ]);
862
+ var pivotTableDefinition = {
863
+ type: "pivot",
864
+ name: "Pivot Table",
865
+ description: "Interactive pivot table for data analysis",
866
+ icon: Table,
867
+ component: PivotTable,
868
+ settingsPanel: PivotTableSettingsPanel,
869
+ createDefaultSettings: (layout) => ({
870
+ ...DEFAULT_CHART_SETTINGS,
871
+ id: crypto.randomUUID(),
872
+ type: "pivot",
873
+ title: "Pivot Table",
874
+ layout,
875
+ margin: { top: 0, right: 0, bottom: 0, left: 0 },
876
+ rowFields: [],
877
+ columnField: "",
878
+ valueFields: [],
879
+ filters: []
880
+ }),
881
+ validateSettings: (settings) => {
882
+ const rowFields = Array.isArray(settings.rowFields) && settings.rowFields.every(
883
+ (field) => typeof field === "string" && field.trim().length > 0
884
+ );
885
+ const columnField = typeof settings.columnField === "string" && (settings.columnField === "" || settings.columnField.trim().length > 0);
886
+ const valueFields = Array.isArray(settings.valueFields) && settings.valueFields.length > 0 && settings.valueFields.every(
887
+ (valueField) => typeof valueField.field === "string" && valueField.field.trim().length > 0 && VALID_AGGREGATIONS.has(valueField.aggregation)
888
+ );
889
+ return rowFields && columnField && valueFields && new Set(settings.rowFields).size === settings.rowFields.length && (!settings.columnField || !settings.rowFields.includes(settings.columnField));
890
+ },
891
+ getFilterFunction: (settings, fieldGetter) => {
892
+ const fields = [...settings.rowFields, settings.columnField];
893
+ const filters = settings.filters.filter(
894
+ (filter) => filter.type === "value" && fields.includes(filter.field)
895
+ );
896
+ const activeFields = [...new Set(filters.map((filter) => filter.field))];
897
+ const groups = activeFields.map((field) => ({
898
+ data: fieldGetter(field),
899
+ filters: filters.filter((filter) => filter.field === field)
900
+ }));
901
+ return (id) => groups.every(
902
+ (group) => group.filters.some((filter) => applyFilter(group.data[id], filter))
903
+ );
904
+ }
905
+ };
906
+
907
+ export { pivotTableDefinition };
908
+ //# sourceMappingURL=chunk-EGMFQSGK.js.map
909
+ //# sourceMappingURL=chunk-EGMFQSGK.js.map