@prismiq/react 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 (82) hide show
  1. package/README.md +88 -0
  2. package/dist/CustomSQLEditor-BXB4rf1q.d.cts +1297 -0
  3. package/dist/CustomSQLEditor-DYeId0Gp.d.ts +1297 -0
  4. package/dist/DashboardDialog-B3vYC5Gs.d.ts +1106 -0
  5. package/dist/DashboardDialog-LHmrtNQU.d.cts +1106 -0
  6. package/dist/accessibility-2yy5yqRR.d.cts +145 -0
  7. package/dist/accessibility-2yy5yqRR.d.ts +145 -0
  8. package/dist/charts/index.cjs +110 -0
  9. package/dist/charts/index.cjs.map +1 -0
  10. package/dist/charts/index.d.cts +2 -0
  11. package/dist/charts/index.d.ts +2 -0
  12. package/dist/charts/index.js +5 -0
  13. package/dist/charts/index.js.map +1 -0
  14. package/dist/chunk-2H5WTH4K.js +2409 -0
  15. package/dist/chunk-2H5WTH4K.js.map +1 -0
  16. package/dist/chunk-4AVL6GQK.cjs +470 -0
  17. package/dist/chunk-4AVL6GQK.cjs.map +1 -0
  18. package/dist/chunk-EX74SI67.js +455 -0
  19. package/dist/chunk-EX74SI67.js.map +1 -0
  20. package/dist/chunk-FEABEF3J.cjs +7543 -0
  21. package/dist/chunk-FEABEF3J.cjs.map +1 -0
  22. package/dist/chunk-JTCBZDHY.js +126 -0
  23. package/dist/chunk-JTCBZDHY.js.map +1 -0
  24. package/dist/chunk-LMTG3LRC.cjs +326 -0
  25. package/dist/chunk-LMTG3LRC.cjs.map +1 -0
  26. package/dist/chunk-MDXGGZSW.cjs +273 -0
  27. package/dist/chunk-MDXGGZSW.cjs.map +1 -0
  28. package/dist/chunk-MOAEEF5P.js +7510 -0
  29. package/dist/chunk-MOAEEF5P.js.map +1 -0
  30. package/dist/chunk-NK7HKX2J.cjs +2459 -0
  31. package/dist/chunk-NK7HKX2J.cjs.map +1 -0
  32. package/dist/chunk-NY6TZLST.cjs +8781 -0
  33. package/dist/chunk-NY6TZLST.cjs.map +1 -0
  34. package/dist/chunk-T6STUE7E.js +321 -0
  35. package/dist/chunk-T6STUE7E.js.map +1 -0
  36. package/dist/chunk-TRW7DKLP.cjs +141 -0
  37. package/dist/chunk-TRW7DKLP.cjs.map +1 -0
  38. package/dist/chunk-UPYINBZU.js +8706 -0
  39. package/dist/chunk-UPYINBZU.js.map +1 -0
  40. package/dist/chunk-WWTT2OJ5.js +246 -0
  41. package/dist/chunk-WWTT2OJ5.js.map +1 -0
  42. package/dist/components/index.cjs +222 -0
  43. package/dist/components/index.cjs.map +1 -0
  44. package/dist/components/index.d.cts +207 -0
  45. package/dist/components/index.d.ts +207 -0
  46. package/dist/components/index.js +5 -0
  47. package/dist/components/index.js.map +1 -0
  48. package/dist/dashboard/index.cjs +140 -0
  49. package/dist/dashboard/index.cjs.map +1 -0
  50. package/dist/dashboard/index.d.cts +302 -0
  51. package/dist/dashboard/index.d.ts +302 -0
  52. package/dist/dashboard/index.js +7 -0
  53. package/dist/dashboard/index.js.map +1 -0
  54. package/dist/export/index.cjs +32 -0
  55. package/dist/export/index.cjs.map +1 -0
  56. package/dist/export/index.d.cts +197 -0
  57. package/dist/export/index.d.ts +197 -0
  58. package/dist/export/index.js +3 -0
  59. package/dist/export/index.js.map +1 -0
  60. package/dist/index-C-Qcuu4Y.d.cts +821 -0
  61. package/dist/index-rPc7ijt8.d.ts +821 -0
  62. package/dist/index.cjs +1486 -0
  63. package/dist/index.cjs.map +1 -0
  64. package/dist/index.d.cts +1435 -0
  65. package/dist/index.d.ts +1435 -0
  66. package/dist/index.js +926 -0
  67. package/dist/index.js.map +1 -0
  68. package/dist/ssr/index.cjs +64 -0
  69. package/dist/ssr/index.cjs.map +1 -0
  70. package/dist/ssr/index.d.cts +213 -0
  71. package/dist/ssr/index.d.ts +213 -0
  72. package/dist/ssr/index.js +3 -0
  73. package/dist/ssr/index.js.map +1 -0
  74. package/dist/types-WrCbOeAV.d.cts +569 -0
  75. package/dist/types-WrCbOeAV.d.ts +569 -0
  76. package/dist/utils/index.cjs +64 -0
  77. package/dist/utils/index.cjs.map +1 -0
  78. package/dist/utils/index.d.cts +112 -0
  79. package/dist/utils/index.d.ts +112 -0
  80. package/dist/utils/index.js +3 -0
  81. package/dist/utils/index.js.map +1 -0
  82. package/package.json +110 -0
@@ -0,0 +1,2409 @@
1
+ import { useTheme } from './chunk-T6STUE7E.js';
2
+ import { createDateFormatter } from './chunk-EX74SI67.js';
3
+ import { useRef, useCallback, useState, useEffect, useMemo } from 'react';
4
+ import ReactEChartsCore from 'echarts-for-react/esm/core';
5
+ import * as echarts from 'echarts/core';
6
+ import { BarChart, LineChart, PieChart, ScatterChart } from 'echarts/charts';
7
+ import { TitleComponent, TooltipComponent, GridComponent, LegendComponent, MarkLineComponent, MarkPointComponent, DataZoomComponent } from 'echarts/components';
8
+ import { CanvasRenderer } from 'echarts/renderers';
9
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
10
+ import ReactEChartsCore2 from 'echarts-for-react/lib/core';
11
+
12
+ // src/charts/utils.ts
13
+ function queryResultToChartData(result, xColumn, yColumns, seriesColumn) {
14
+ const xIndex = result.columns.indexOf(xColumn);
15
+ if (xIndex === -1) {
16
+ return { categories: [], series: [] };
17
+ }
18
+ if (seriesColumn) {
19
+ const seriesIndex = result.columns.indexOf(seriesColumn);
20
+ if (seriesIndex === -1) {
21
+ return { categories: [], series: [] };
22
+ }
23
+ const xValuesSet = /* @__PURE__ */ new Set();
24
+ result.rows.forEach((row) => {
25
+ const xValue = row[xIndex];
26
+ xValuesSet.add(xValue === null ? "" : String(xValue));
27
+ });
28
+ const categories2 = Array.from(xValuesSet).sort();
29
+ const seriesNamesSet = /* @__PURE__ */ new Set();
30
+ result.rows.forEach((row) => {
31
+ const seriesValue = row[seriesIndex];
32
+ seriesNamesSet.add(seriesValue === null ? "" : String(seriesValue));
33
+ });
34
+ const seriesNames = Array.from(seriesNamesSet).sort();
35
+ const series2 = yColumns.flatMap((yColName) => {
36
+ const yIndex = result.columns.indexOf(yColName);
37
+ if (yIndex === -1) {
38
+ return [];
39
+ }
40
+ return seriesNames.map((seriesName) => {
41
+ const data = categories2.map((category) => {
42
+ const row = result.rows.find((r) => {
43
+ const rowSeriesValue = r[seriesIndex];
44
+ const rowXValue = r[xIndex];
45
+ return String(rowSeriesValue) === seriesName && String(rowXValue) === category;
46
+ });
47
+ if (!row) {
48
+ return null;
49
+ }
50
+ const value = row[yIndex];
51
+ if (value === null || value === void 0) {
52
+ return null;
53
+ }
54
+ return typeof value === "number" ? value : Number(value);
55
+ });
56
+ return {
57
+ name: seriesName,
58
+ data
59
+ };
60
+ });
61
+ });
62
+ return { categories: categories2, series: series2 };
63
+ }
64
+ const categories = result.rows.map((row) => {
65
+ const value = row[xIndex];
66
+ return value === null ? "" : String(value);
67
+ });
68
+ const series = yColumns.map((colName) => {
69
+ const yIndex = result.columns.indexOf(colName);
70
+ if (yIndex === -1) {
71
+ return null;
72
+ }
73
+ const data = result.rows.map((row) => {
74
+ const value = row[yIndex];
75
+ if (value === null || value === void 0) {
76
+ return null;
77
+ }
78
+ return typeof value === "number" ? value : Number(value);
79
+ });
80
+ return {
81
+ name: colName,
82
+ data
83
+ };
84
+ }).filter((s) => s !== null);
85
+ return { categories, series };
86
+ }
87
+ function dataPointsToChartData(data, xColumn, yColumns, seriesColumn) {
88
+ if (seriesColumn) {
89
+ const xValuesSet = /* @__PURE__ */ new Set();
90
+ data.forEach((point) => {
91
+ const xValue = point[xColumn];
92
+ xValuesSet.add(xValue === null ? "" : String(xValue));
93
+ });
94
+ const categories2 = Array.from(xValuesSet).sort();
95
+ const seriesNamesSet = /* @__PURE__ */ new Set();
96
+ data.forEach((point) => {
97
+ const seriesValue = point[seriesColumn];
98
+ seriesNamesSet.add(seriesValue === null ? "" : String(seriesValue));
99
+ });
100
+ const seriesNames = Array.from(seriesNamesSet).sort();
101
+ const series2 = yColumns.flatMap((yColName) => {
102
+ return seriesNames.map((seriesName) => {
103
+ const seriesData = categories2.map((category) => {
104
+ const point = data.find((p) => {
105
+ const pSeriesValue = p[seriesColumn];
106
+ const pXValue = p[xColumn];
107
+ return String(pSeriesValue) === seriesName && String(pXValue) === category;
108
+ });
109
+ if (!point) {
110
+ return null;
111
+ }
112
+ const value = point[yColName];
113
+ if (value === null || value === void 0) {
114
+ return null;
115
+ }
116
+ return typeof value === "number" ? value : Number(value);
117
+ });
118
+ return {
119
+ name: seriesName,
120
+ data: seriesData
121
+ };
122
+ });
123
+ });
124
+ return { categories: categories2, series: series2 };
125
+ }
126
+ const categories = data.map((point) => {
127
+ const value = point[xColumn];
128
+ return value === null ? "" : String(value);
129
+ });
130
+ const series = yColumns.map((colName) => ({
131
+ name: colName,
132
+ data: data.map((point) => {
133
+ const value = point[colName];
134
+ if (value === null || value === void 0) {
135
+ return null;
136
+ }
137
+ return typeof value === "number" ? value : Number(value);
138
+ })
139
+ }));
140
+ return { categories, series };
141
+ }
142
+ function isQueryResult(data) {
143
+ return data !== null && typeof data === "object" && "columns" in data && "rows" in data && Array.isArray(data.columns) && Array.isArray(data.rows);
144
+ }
145
+ function toChartData(data, xColumn, yColumns, seriesColumn) {
146
+ if (isQueryResult(data)) {
147
+ return queryResultToChartData(data, xColumn, yColumns, seriesColumn);
148
+ }
149
+ return dataPointsToChartData(data, xColumn, yColumns, seriesColumn);
150
+ }
151
+ function createChartTheme(theme) {
152
+ return {
153
+ color: theme.chart.colors,
154
+ backgroundColor: "transparent",
155
+ textStyle: {
156
+ fontFamily: theme.fonts.sans,
157
+ color: theme.colors.text
158
+ },
159
+ title: {
160
+ textStyle: {
161
+ color: theme.colors.text,
162
+ fontSize: 16,
163
+ fontWeight: 600
164
+ }
165
+ },
166
+ legend: {
167
+ textStyle: {
168
+ color: theme.colors.textMuted,
169
+ fontSize: 12
170
+ }
171
+ },
172
+ xAxis: {
173
+ axisLine: {
174
+ show: true,
175
+ lineStyle: { color: theme.chart.axisColor }
176
+ },
177
+ axisTick: {
178
+ show: true,
179
+ lineStyle: { color: theme.chart.axisColor }
180
+ },
181
+ axisLabel: {
182
+ color: theme.colors.textMuted,
183
+ fontSize: 11
184
+ },
185
+ splitLine: {
186
+ show: false,
187
+ lineStyle: { color: theme.chart.gridColor }
188
+ }
189
+ },
190
+ yAxis: {
191
+ axisLine: {
192
+ show: false,
193
+ lineStyle: { color: theme.chart.axisColor }
194
+ },
195
+ axisTick: {
196
+ show: false,
197
+ lineStyle: { color: theme.chart.axisColor }
198
+ },
199
+ axisLabel: {
200
+ color: theme.colors.textMuted,
201
+ fontSize: 11
202
+ },
203
+ splitLine: {
204
+ show: true,
205
+ lineStyle: { color: theme.chart.gridColor, type: "dashed" }
206
+ }
207
+ },
208
+ tooltip: {
209
+ backgroundColor: theme.chart.tooltipBackground,
210
+ borderColor: theme.colors.border,
211
+ borderWidth: 1,
212
+ textStyle: {
213
+ color: theme.name === "dark" ? "#f9fafb" : "#ffffff",
214
+ fontSize: 12
215
+ },
216
+ padding: [8, 12]
217
+ },
218
+ grid: {
219
+ left: 60,
220
+ right: 20,
221
+ top: 40,
222
+ bottom: 40,
223
+ containLabel: false
224
+ }
225
+ };
226
+ }
227
+ function applyThemeToOption(option, theme) {
228
+ const chartTheme = createChartTheme(theme);
229
+ return {
230
+ ...option,
231
+ // Disable animations to prevent sizing issues with notMerge=false
232
+ // Charts will still look good but won't animate on data changes
233
+ animation: false,
234
+ color: option.color || chartTheme.color,
235
+ backgroundColor: chartTheme.backgroundColor,
236
+ textStyle: {
237
+ ...chartTheme.textStyle,
238
+ ...typeof option.textStyle === "object" ? option.textStyle : {}
239
+ },
240
+ tooltip: {
241
+ ...chartTheme.tooltip,
242
+ ...typeof option.tooltip === "object" ? option.tooltip : {}
243
+ },
244
+ xAxis: mergeAxisConfig(option.xAxis, chartTheme.xAxis),
245
+ yAxis: mergeAxisConfig(option.yAxis, chartTheme.yAxis),
246
+ grid: {
247
+ ...chartTheme.grid,
248
+ ...typeof option.grid === "object" ? option.grid : {}
249
+ }
250
+ };
251
+ }
252
+ function mergeAxisConfig(axisOption, themeAxis) {
253
+ if (Array.isArray(axisOption)) {
254
+ return axisOption.map(
255
+ (axis) => mergeAxisConfig(axis, themeAxis)
256
+ );
257
+ }
258
+ if (typeof axisOption === "object" && axisOption !== null) {
259
+ return {
260
+ ...themeAxis,
261
+ ...axisOption,
262
+ axisLine: {
263
+ ...themeAxis.axisLine,
264
+ ...axisOption.axisLine || {}
265
+ },
266
+ axisLabel: {
267
+ ...themeAxis.axisLabel,
268
+ ...axisOption.axisLabel || {}
269
+ },
270
+ splitLine: {
271
+ ...themeAxis.splitLine,
272
+ ...axisOption.splitLine || {}
273
+ }
274
+ };
275
+ }
276
+ return themeAxis;
277
+ }
278
+ function formatAxisLabel(value, format, options) {
279
+ const { currencySymbol = "$", decimals = 2, compactNotation } = options || {};
280
+ switch (format) {
281
+ case "currency":
282
+ if (compactNotation) {
283
+ return `${currencySymbol}${formatCompactAtThreshold(value, compactNotation, decimals)}`;
284
+ }
285
+ return `${currencySymbol}${value.toLocaleString(void 0, {
286
+ minimumFractionDigits: decimals,
287
+ maximumFractionDigits: decimals
288
+ })}`;
289
+ case "percent":
290
+ return `${(value * 100).toFixed(decimals)}%`;
291
+ case "compact":
292
+ return formatCompact(value, decimals);
293
+ case "number":
294
+ default:
295
+ return value.toLocaleString(void 0, {
296
+ minimumFractionDigits: decimals,
297
+ maximumFractionDigits: decimals
298
+ });
299
+ }
300
+ }
301
+ function formatCompact(value, decimals = 1) {
302
+ const absValue = Math.abs(value);
303
+ const sign = value < 0 ? "-" : "";
304
+ if (absValue >= 1e9) {
305
+ return `${sign}${(absValue / 1e9).toFixed(decimals)}B`;
306
+ }
307
+ if (absValue >= 1e6) {
308
+ return `${sign}${(absValue / 1e6).toFixed(decimals)}M`;
309
+ }
310
+ if (absValue >= 1e3) {
311
+ return `${sign}${(absValue / 1e3).toFixed(decimals)}K`;
312
+ }
313
+ return `${sign}${absValue.toFixed(decimals)}`;
314
+ }
315
+ function formatCompactAtThreshold(value, notation, decimals = 0) {
316
+ const absValue = Math.abs(value);
317
+ const sign = value < 0 ? "-" : "";
318
+ const thresholds = {
319
+ K: { threshold: 1e3, divisor: 1e3 },
320
+ M: { threshold: 1e6, divisor: 1e6 },
321
+ B: { threshold: 1e9, divisor: 1e9 },
322
+ T: { threshold: 1e12, divisor: 1e12 }
323
+ };
324
+ const config = thresholds[notation];
325
+ if (absValue >= config.threshold) {
326
+ const formatted = (absValue / config.divisor).toFixed(decimals);
327
+ return `${sign}${formatted}${notation}`;
328
+ }
329
+ return `${sign}${absValue.toLocaleString(void 0, {
330
+ minimumFractionDigits: decimals,
331
+ maximumFractionDigits: decimals
332
+ })}`;
333
+ }
334
+ function formatMetricValue(value, format = "number", options) {
335
+ if (typeof value === "string") {
336
+ return value;
337
+ }
338
+ const { currencySymbol = "$", decimals = 0, compactNotation } = options || {};
339
+ switch (format) {
340
+ case "currency":
341
+ if (compactNotation) {
342
+ const compactValue = formatCompactAtThreshold(value, compactNotation, decimals);
343
+ return `${currencySymbol}${compactValue}`;
344
+ }
345
+ return `${currencySymbol}${value.toLocaleString(void 0, {
346
+ minimumFractionDigits: decimals,
347
+ maximumFractionDigits: decimals
348
+ })}`;
349
+ case "percent":
350
+ return `${(value * 100).toFixed(decimals)}%`;
351
+ case "compact":
352
+ return formatCompact(value, decimals);
353
+ case "number":
354
+ default:
355
+ if (compactNotation) {
356
+ return formatCompactAtThreshold(value, compactNotation, decimals);
357
+ }
358
+ return value.toLocaleString(void 0, {
359
+ minimumFractionDigits: decimals,
360
+ maximumFractionDigits: decimals
361
+ });
362
+ }
363
+ }
364
+ var DEFAULT_COLOR = "#3b82f6";
365
+ function getChartColors(theme, count) {
366
+ const themeColors = theme.chart.colors;
367
+ if (themeColors.length === 0) {
368
+ return Array(count).fill(DEFAULT_COLOR);
369
+ }
370
+ const result = [];
371
+ for (let i = 0; i < count; i++) {
372
+ const color = themeColors[i % themeColors.length];
373
+ result.push(color ?? DEFAULT_COLOR);
374
+ }
375
+ return result;
376
+ }
377
+ function createGradientColor(color, opacity = 0.2) {
378
+ return {
379
+ type: "linear",
380
+ x: 0,
381
+ y: 0,
382
+ x2: 0,
383
+ y2: 1,
384
+ colorStops: [
385
+ { offset: 0, color: adjustColorOpacity(color, opacity) },
386
+ { offset: 1, color: adjustColorOpacity(color, 0.02) }
387
+ ]
388
+ };
389
+ }
390
+ function adjustColorOpacity(hexColor, opacity) {
391
+ const hex = hexColor.replace("#", "");
392
+ const r = parseInt(hex.substring(0, 2), 16);
393
+ const g = parseInt(hex.substring(2, 4), 16);
394
+ const b = parseInt(hex.substring(4, 6), 16);
395
+ return `rgba(${r}, ${g}, ${b}, ${opacity})`;
396
+ }
397
+ function createMarkLines(lines, theme) {
398
+ return {
399
+ silent: true,
400
+ symbol: "none",
401
+ data: lines.map((line) => ({
402
+ yAxis: line.value,
403
+ label: {
404
+ show: !!line.label,
405
+ formatter: line.label || "",
406
+ position: "end",
407
+ color: line.color || theme.colors.textMuted,
408
+ fontSize: 11
409
+ },
410
+ lineStyle: {
411
+ color: line.color || theme.colors.textMuted,
412
+ type: line.lineStyle || "dashed",
413
+ width: 1
414
+ }
415
+ }))
416
+ };
417
+ }
418
+ function isChartDataEmpty(data) {
419
+ if (isQueryResult(data)) {
420
+ return data.rows.length === 0;
421
+ }
422
+ return data.length === 0;
423
+ }
424
+ function createEmptyStateGraphic(message = "No data available") {
425
+ return {
426
+ type: "text",
427
+ left: "center",
428
+ top: "middle",
429
+ style: {
430
+ text: message,
431
+ fontSize: 14,
432
+ fill: "#9ca3af"
433
+ }
434
+ };
435
+ }
436
+ echarts.use([
437
+ BarChart,
438
+ LineChart,
439
+ PieChart,
440
+ ScatterChart,
441
+ TitleComponent,
442
+ TooltipComponent,
443
+ GridComponent,
444
+ LegendComponent,
445
+ MarkLineComponent,
446
+ MarkPointComponent,
447
+ DataZoomComponent,
448
+ CanvasRenderer
449
+ ]);
450
+ function EChartWrapper({
451
+ option,
452
+ loading = false,
453
+ height = 300,
454
+ width = "100%",
455
+ theme: themeOverride,
456
+ onEvents,
457
+ className
458
+ }) {
459
+ const { theme, resolvedMode } = useTheme();
460
+ const chartRef = useRef(null);
461
+ const prevOptionRef = useRef("");
462
+ const themedOption = applyThemeToOption(option, theme);
463
+ const shouldSetOption = useCallback(
464
+ (_prevProps, props) => {
465
+ const newOptionString = JSON.stringify(props.option);
466
+ if (prevOptionRef.current === newOptionString) {
467
+ return false;
468
+ }
469
+ prevOptionRef.current = newOptionString;
470
+ return true;
471
+ },
472
+ []
473
+ );
474
+ const effectiveTheme = themeOverride || resolvedMode;
475
+ const handleResize = useCallback(() => {
476
+ if (chartRef.current) {
477
+ const chartInstance = chartRef.current.getEchartsInstance();
478
+ chartInstance?.resize();
479
+ }
480
+ }, []);
481
+ const containerStyle = {
482
+ width: typeof width === "number" ? `${width}px` : width,
483
+ height: typeof height === "number" ? `${height}px` : height,
484
+ position: "relative"
485
+ };
486
+ const loadingStyle = {
487
+ position: "absolute",
488
+ top: 0,
489
+ left: 0,
490
+ right: 0,
491
+ bottom: 0,
492
+ display: "flex",
493
+ alignItems: "center",
494
+ justifyContent: "center",
495
+ backgroundColor: theme.colors.surface,
496
+ opacity: 0.9,
497
+ zIndex: 10
498
+ };
499
+ const spinnerStyle = {
500
+ width: "32px",
501
+ height: "32px",
502
+ border: `3px solid ${theme.colors.border}`,
503
+ borderTopColor: theme.colors.primary,
504
+ borderRadius: "50%",
505
+ animation: "prismiq-spin 0.8s linear infinite"
506
+ };
507
+ return /* @__PURE__ */ jsxs("div", { style: containerStyle, className, children: [
508
+ /* @__PURE__ */ jsx("style", { children: `
509
+ @keyframes prismiq-spin {
510
+ to { transform: rotate(360deg); }
511
+ }
512
+ ` }),
513
+ loading && /* @__PURE__ */ jsx("div", { style: loadingStyle, children: /* @__PURE__ */ jsx("div", { style: spinnerStyle }) }),
514
+ /* @__PURE__ */ jsx(
515
+ ReactEChartsCore,
516
+ {
517
+ ref: chartRef,
518
+ echarts,
519
+ option: themedOption,
520
+ notMerge: false,
521
+ lazyUpdate: true,
522
+ shouldSetOption,
523
+ theme: effectiveTheme === "dark" ? "dark" : void 0,
524
+ onEvents,
525
+ style: { width: "100%", height: "100%" },
526
+ opts: { renderer: "canvas" },
527
+ onChartReady: handleResize
528
+ }
529
+ )
530
+ ] });
531
+ }
532
+ function TrendIndicator({
533
+ trend,
534
+ trendPositive = "up",
535
+ size = "md"
536
+ }) {
537
+ const { theme } = useTheme();
538
+ const isPositive = trend.direction === "up" && trendPositive === "up" || trend.direction === "down" && trendPositive === "down";
539
+ const isNegative = trend.direction === "up" && trendPositive === "down" || trend.direction === "down" && trendPositive === "up";
540
+ const getColor = () => {
541
+ if (trend.direction === "flat") {
542
+ return theme.colors.textMuted;
543
+ }
544
+ if (isPositive) {
545
+ return theme.colors.success;
546
+ }
547
+ if (isNegative) {
548
+ return theme.colors.error;
549
+ }
550
+ return theme.colors.textMuted;
551
+ };
552
+ const getArrow = () => {
553
+ switch (trend.direction) {
554
+ case "up":
555
+ return "\u2191";
556
+ // up arrow
557
+ case "down":
558
+ return "\u2193";
559
+ // down arrow
560
+ case "flat":
561
+ default:
562
+ return "\u2192";
563
+ }
564
+ };
565
+ const sizeStyles = {
566
+ sm: { fontSize: theme.fontSizes.xs },
567
+ md: { fontSize: theme.fontSizes.sm },
568
+ lg: { fontSize: theme.fontSizes.base }
569
+ };
570
+ const containerStyle = {
571
+ display: "inline-flex",
572
+ alignItems: "center",
573
+ gap: "4px",
574
+ color: getColor(),
575
+ ...sizeStyles[size]
576
+ };
577
+ const arrowStyle = {
578
+ fontWeight: 600
579
+ };
580
+ const valueStyle = {
581
+ fontWeight: 500
582
+ };
583
+ const labelStyle = {
584
+ color: theme.colors.textMuted,
585
+ marginLeft: "4px"
586
+ };
587
+ return /* @__PURE__ */ jsxs("span", { style: containerStyle, children: [
588
+ /* @__PURE__ */ jsx("span", { style: arrowStyle, children: getArrow() }),
589
+ /* @__PURE__ */ jsxs("span", { style: valueStyle, children: [
590
+ Math.abs(trend.value).toFixed(1),
591
+ "%"
592
+ ] }),
593
+ trend.label && /* @__PURE__ */ jsx("span", { style: labelStyle, children: trend.label })
594
+ ] });
595
+ }
596
+ echarts.use([LineChart, GridComponent, CanvasRenderer]);
597
+ function Sparkline({
598
+ data,
599
+ color,
600
+ height = 40,
601
+ width = "100%"
602
+ }) {
603
+ const { theme } = useTheme();
604
+ const lineColor = color || theme.chart.colors[0] || theme.colors.primary;
605
+ if (!data || data.length === 0) {
606
+ return null;
607
+ }
608
+ const option = {
609
+ animation: false,
610
+ grid: {
611
+ left: 0,
612
+ right: 0,
613
+ top: 0,
614
+ bottom: 0
615
+ },
616
+ xAxis: {
617
+ type: "category",
618
+ show: false,
619
+ data: data.map((_, i) => i)
620
+ },
621
+ yAxis: {
622
+ type: "value",
623
+ show: false,
624
+ min: "dataMin",
625
+ max: "dataMax"
626
+ },
627
+ series: [
628
+ {
629
+ type: "line",
630
+ data,
631
+ smooth: true,
632
+ symbol: "none",
633
+ lineStyle: {
634
+ color: lineColor,
635
+ width: 2
636
+ },
637
+ areaStyle: {
638
+ color: createGradientColor(lineColor, 0.3)
639
+ }
640
+ }
641
+ ]
642
+ };
643
+ return /* @__PURE__ */ jsx(
644
+ ReactEChartsCore2,
645
+ {
646
+ echarts: echarts,
647
+ option,
648
+ style: { width, height },
649
+ opts: { renderer: "canvas" },
650
+ notMerge: true,
651
+ lazyUpdate: true
652
+ }
653
+ );
654
+ }
655
+ function MetricCard({
656
+ title,
657
+ value,
658
+ format = "number",
659
+ currencySymbol = "$",
660
+ decimals = 0,
661
+ compactNotation,
662
+ trend,
663
+ trendPositive = "up",
664
+ sparklineData,
665
+ sparklineColor,
666
+ size = "md",
667
+ loading = false,
668
+ centered = false,
669
+ className,
670
+ onClick
671
+ }) {
672
+ const { theme } = useTheme();
673
+ const containerRef = useRef(null);
674
+ const [containerSize, setContainerSize] = useState({ width: 300, height: 200 });
675
+ useEffect(() => {
676
+ const measureSize = () => {
677
+ if (containerRef.current) {
678
+ setContainerSize({
679
+ width: containerRef.current.offsetWidth,
680
+ height: containerRef.current.offsetHeight
681
+ });
682
+ }
683
+ };
684
+ measureSize();
685
+ window.addEventListener("resize", measureSize);
686
+ const resizeObserver = new ResizeObserver(measureSize);
687
+ if (containerRef.current) {
688
+ resizeObserver.observe(containerRef.current);
689
+ }
690
+ return () => {
691
+ window.removeEventListener("resize", measureSize);
692
+ resizeObserver.disconnect();
693
+ };
694
+ }, []);
695
+ const formattedValueForSizing = formatMetricValue(value, format, {
696
+ currencySymbol,
697
+ decimals,
698
+ compactNotation
699
+ });
700
+ const calculateResponsiveFontSize = () => {
701
+ if (!centered) {
702
+ const sizeMap = {
703
+ sm: theme.fontSizes.xl,
704
+ md: theme.fontSizes["2xl"],
705
+ lg: "28px"
706
+ };
707
+ return sizeMap[size];
708
+ }
709
+ const { width: containerWidth, height: containerHeight } = containerSize;
710
+ const availableWidth = containerWidth - 32;
711
+ const charCount = formattedValueForSizing.length || 1;
712
+ const maxFontByWidth = availableWidth / (charCount * 0.6);
713
+ const maxFontByHeight = containerHeight * 0.25;
714
+ const scaledSize = Math.max(24, Math.min(96, Math.min(maxFontByWidth, maxFontByHeight)));
715
+ return `${scaledSize}px`;
716
+ };
717
+ const sizeConfig = {
718
+ sm: {
719
+ padding: theme.spacing.md,
720
+ titleSize: theme.fontSizes.xs,
721
+ valueSize: theme.fontSizes.xl,
722
+ sparklineHeight: 32
723
+ },
724
+ md: {
725
+ padding: theme.spacing.lg,
726
+ titleSize: theme.fontSizes.sm,
727
+ valueSize: theme.fontSizes["2xl"],
728
+ sparklineHeight: 40
729
+ },
730
+ lg: {
731
+ padding: theme.spacing.xl,
732
+ titleSize: theme.fontSizes.base,
733
+ valueSize: "28px",
734
+ sparklineHeight: 50
735
+ }
736
+ };
737
+ const config = sizeConfig[size];
738
+ const responsiveValueSize = calculateResponsiveFontSize();
739
+ const containerStyle = {
740
+ backgroundColor: theme.colors.surface,
741
+ border: `1px solid ${theme.colors.border}`,
742
+ borderRadius: theme.radius.lg,
743
+ padding: centered ? theme.spacing.md : config.padding,
744
+ cursor: onClick ? "pointer" : "default",
745
+ transition: "box-shadow 0.2s ease, border-color 0.2s ease",
746
+ position: "relative",
747
+ overflow: "hidden",
748
+ ...centered && {
749
+ display: "flex",
750
+ alignItems: "center",
751
+ justifyContent: "center",
752
+ height: "100%"
753
+ }
754
+ };
755
+ const headerStyle = {
756
+ display: "flex",
757
+ justifyContent: "space-between",
758
+ alignItems: "flex-start",
759
+ marginBottom: theme.spacing.sm
760
+ };
761
+ const titleStyle = {
762
+ fontSize: config.titleSize,
763
+ fontWeight: 500,
764
+ color: theme.colors.textMuted,
765
+ margin: 0
766
+ };
767
+ const valueStyle = {
768
+ fontSize: responsiveValueSize,
769
+ fontWeight: 600,
770
+ color: theme.colors.text,
771
+ margin: 0,
772
+ lineHeight: 1.2,
773
+ ...centered && {
774
+ textAlign: "center"
775
+ }
776
+ };
777
+ const sparklineContainerStyle = {
778
+ marginTop: theme.spacing.md
779
+ };
780
+ const skeletonStyle = {
781
+ backgroundColor: theme.colors.border,
782
+ borderRadius: theme.radius.sm,
783
+ animation: "prismiq-pulse 1.5s ease-in-out infinite"
784
+ };
785
+ const formattedValue = formattedValueForSizing;
786
+ return /* @__PURE__ */ jsxs(
787
+ "div",
788
+ {
789
+ ref: containerRef,
790
+ style: containerStyle,
791
+ className,
792
+ onClick,
793
+ role: onClick ? "button" : void 0,
794
+ tabIndex: onClick ? 0 : void 0,
795
+ onKeyDown: onClick ? (e) => {
796
+ if (e.key === "Enter" || e.key === " ") {
797
+ e.preventDefault();
798
+ onClick();
799
+ }
800
+ } : void 0,
801
+ children: [
802
+ /* @__PURE__ */ jsx("style", { children: `
803
+ @keyframes prismiq-pulse {
804
+ 0%, 100% { opacity: 1; }
805
+ 50% { opacity: 0.5; }
806
+ }
807
+ ` }),
808
+ centered ? loading ? /* @__PURE__ */ jsx(
809
+ "div",
810
+ {
811
+ style: {
812
+ ...skeletonStyle,
813
+ width: "120px",
814
+ height: "32px"
815
+ }
816
+ }
817
+ ) : /* @__PURE__ */ jsx("p", { style: valueStyle, children: formattedValue }) : /* @__PURE__ */ jsxs(Fragment, { children: [
818
+ /* @__PURE__ */ jsxs("div", { style: headerStyle, children: [
819
+ loading ? /* @__PURE__ */ jsx("div", { style: { ...skeletonStyle, width: "80px", height: "14px" } }) : /* @__PURE__ */ jsx("h3", { style: titleStyle, children: title }),
820
+ trend && !loading && /* @__PURE__ */ jsx(
821
+ TrendIndicator,
822
+ {
823
+ trend,
824
+ trendPositive,
825
+ size
826
+ }
827
+ )
828
+ ] }),
829
+ loading ? /* @__PURE__ */ jsx(
830
+ "div",
831
+ {
832
+ style: {
833
+ ...skeletonStyle,
834
+ width: "120px",
835
+ height: size === "sm" ? "20px" : size === "lg" ? "32px" : "24px",
836
+ marginTop: theme.spacing.xs
837
+ }
838
+ }
839
+ ) : /* @__PURE__ */ jsx("p", { style: valueStyle, children: formattedValue }),
840
+ sparklineData && sparklineData.length > 0 && !loading && /* @__PURE__ */ jsx("div", { style: sparklineContainerStyle, children: /* @__PURE__ */ jsx(
841
+ Sparkline,
842
+ {
843
+ data: sparklineData,
844
+ color: sparklineColor,
845
+ height: config.sparklineHeight
846
+ }
847
+ ) }),
848
+ sparklineData && loading && /* @__PURE__ */ jsx(
849
+ "div",
850
+ {
851
+ style: {
852
+ ...skeletonStyle,
853
+ width: "100%",
854
+ height: `${config.sparklineHeight}px`,
855
+ marginTop: theme.spacing.md
856
+ }
857
+ }
858
+ )
859
+ ] })
860
+ ]
861
+ }
862
+ );
863
+ }
864
+ function BarChart2({
865
+ data,
866
+ xAxis,
867
+ yAxis,
868
+ orientation = "vertical",
869
+ stacked = false,
870
+ showDataLabels = false,
871
+ showLegend = true,
872
+ legendPosition = "top",
873
+ referenceLines,
874
+ colors,
875
+ xAxisLabel,
876
+ yAxisLabel,
877
+ xAxisFormat,
878
+ yAxisFormat = "number",
879
+ currencySymbol = "$",
880
+ compactNotation,
881
+ decimalDigits = 0,
882
+ loading = false,
883
+ error,
884
+ height = 300,
885
+ width = "100%",
886
+ className,
887
+ onDataPointClick,
888
+ crossFilter,
889
+ selectedValue
890
+ }) {
891
+ const { theme } = useTheme();
892
+ const yColumns = useMemo(
893
+ () => Array.isArray(yAxis) ? yAxis : [yAxis],
894
+ [yAxis]
895
+ );
896
+ const chartData = useMemo(
897
+ () => toChartData(data, xAxis, yColumns),
898
+ [data, xAxis, yColumns]
899
+ );
900
+ const dateFormatter = useMemo(
901
+ () => xAxisFormat ? createDateFormatter(xAxisFormat) : null,
902
+ [xAxisFormat]
903
+ );
904
+ const formattedCategories = useMemo(() => {
905
+ if (!dateFormatter) return chartData.categories;
906
+ return chartData.categories.map((cat) => dateFormatter(cat));
907
+ }, [chartData.categories, dateFormatter]);
908
+ const seriesColors = useMemo(
909
+ () => colors || getChartColors(theme, yColumns.length),
910
+ [colors, theme, yColumns.length]
911
+ );
912
+ const isEmpty = isChartDataEmpty(data);
913
+ const option = useMemo(() => {
914
+ if (isEmpty) {
915
+ return {
916
+ graphic: createEmptyStateGraphic()
917
+ };
918
+ }
919
+ const isHorizontal = orientation === "horizontal";
920
+ const categoryAxis = {
921
+ type: "category",
922
+ data: formattedCategories,
923
+ name: isHorizontal ? yAxisLabel : xAxisLabel,
924
+ nameLocation: "middle",
925
+ nameGap: 35,
926
+ axisLabel: {
927
+ rotate: isHorizontal ? 0 : formattedCategories.length > 10 ? 45 : 0,
928
+ interval: 0,
929
+ hideOverlap: true
930
+ }
931
+ };
932
+ const valueAxis = {
933
+ type: "value",
934
+ name: isHorizontal ? xAxisLabel : yAxisLabel,
935
+ nameLocation: "middle",
936
+ nameGap: 50,
937
+ axisLabel: {
938
+ formatter: (value) => formatAxisLabel(value, yAxisFormat, {
939
+ currencySymbol,
940
+ decimals: decimalDigits,
941
+ compactNotation
942
+ })
943
+ }
944
+ };
945
+ const series = chartData.series.map((s, index) => {
946
+ const baseColor = seriesColors[index] ?? theme.colors.primary;
947
+ return {
948
+ type: "bar",
949
+ name: s.name,
950
+ data: s.data.map((value, dataIndex) => {
951
+ const categoryValue = chartData.categories[dataIndex];
952
+ const isSelected = selectedValue != null && categoryValue === selectedValue;
953
+ const isOther = selectedValue != null && categoryValue !== selectedValue;
954
+ return {
955
+ value,
956
+ itemStyle: {
957
+ color: baseColor,
958
+ opacity: isOther ? 0.3 : 1,
959
+ borderRadius: stacked ? 0 : [4, 4, 0, 0],
960
+ // Highlight selected bar
961
+ ...isSelected && {
962
+ borderColor: theme.colors.primary,
963
+ borderWidth: 2
964
+ }
965
+ }
966
+ };
967
+ }),
968
+ stack: stacked ? "stack" : void 0,
969
+ label: showDataLabels ? {
970
+ show: true,
971
+ position: isHorizontal ? "right" : "top",
972
+ formatter: (params) => {
973
+ const rawValue = typeof params.value === "object" && params.value !== null ? params.value.value : params.value;
974
+ return rawValue !== null && rawValue !== void 0 ? formatAxisLabel(rawValue, yAxisFormat, {
975
+ currencySymbol,
976
+ decimals: decimalDigits,
977
+ compactNotation
978
+ }) : "";
979
+ },
980
+ fontSize: 10,
981
+ color: theme.colors.textMuted
982
+ } : void 0,
983
+ emphasis: {
984
+ focus: "series",
985
+ itemStyle: {
986
+ shadowBlur: 10,
987
+ shadowOffsetX: 0,
988
+ shadowColor: "rgba(0, 0, 0, 0.2)"
989
+ }
990
+ },
991
+ markLine: index === 0 && referenceLines ? createMarkLines(referenceLines, theme) : void 0
992
+ };
993
+ });
994
+ const legend = showLegend ? {
995
+ show: yColumns.length > 1,
996
+ data: chartData.series.map((s) => s.name),
997
+ selectedMode: "multiple",
998
+ [legendPosition === "left" || legendPosition === "right" ? "orient" : "orient"]: legendPosition === "left" || legendPosition === "right" ? "vertical" : "horizontal",
999
+ [legendPosition]: legendPosition === "left" || legendPosition === "right" ? 10 : legendPosition === "top" ? 10 : void 0,
1000
+ bottom: legendPosition === "bottom" ? 10 : void 0
1001
+ } : void 0;
1002
+ const grid = {
1003
+ left: legendPosition === "left" ? 100 : 60,
1004
+ right: legendPosition === "right" ? 100 : 20,
1005
+ top: legendPosition === "top" ? 50 : 40,
1006
+ bottom: legendPosition === "bottom" ? 50 : 40,
1007
+ containLabel: false
1008
+ };
1009
+ return {
1010
+ tooltip: {
1011
+ trigger: "axis",
1012
+ axisPointer: {
1013
+ type: "shadow"
1014
+ },
1015
+ formatter: (params) => {
1016
+ if (!Array.isArray(params) || params.length === 0) return "";
1017
+ const firstParam = params[0];
1018
+ if (!firstParam) return "";
1019
+ const header = `<div style="font-weight: 600; margin-bottom: 4px;">${firstParam.name}</div>`;
1020
+ const items = params.map(
1021
+ (p) => {
1022
+ const rawValue = typeof p.value === "object" && p.value !== null ? p.value.value : p.value;
1023
+ return `<div>${p.marker} ${p.seriesName}: ${rawValue !== null && rawValue !== void 0 ? formatAxisLabel(rawValue, yAxisFormat, {
1024
+ currencySymbol,
1025
+ decimals: decimalDigits,
1026
+ compactNotation
1027
+ }) : "-"}</div>`;
1028
+ }
1029
+ ).join("");
1030
+ return header + items;
1031
+ }
1032
+ },
1033
+ legend,
1034
+ grid,
1035
+ xAxis: isHorizontal ? valueAxis : categoryAxis,
1036
+ yAxis: isHorizontal ? categoryAxis : valueAxis,
1037
+ series
1038
+ };
1039
+ }, [
1040
+ isEmpty,
1041
+ orientation,
1042
+ chartData,
1043
+ formattedCategories,
1044
+ xAxisLabel,
1045
+ yAxisLabel,
1046
+ yAxisFormat,
1047
+ stacked,
1048
+ seriesColors,
1049
+ showDataLabels,
1050
+ showLegend,
1051
+ legendPosition,
1052
+ referenceLines,
1053
+ theme,
1054
+ yColumns.length,
1055
+ selectedValue,
1056
+ crossFilter?.enabled
1057
+ ]);
1058
+ const handleClick = useCallback(
1059
+ (params) => {
1060
+ const p = params;
1061
+ const rawValue = typeof p.value === "object" ? p.value?.value : p.value;
1062
+ const clickParams = {
1063
+ seriesName: p.seriesName ?? "",
1064
+ dataIndex: p.dataIndex ?? 0,
1065
+ value: rawValue ?? 0,
1066
+ name: p.name ?? ""
1067
+ };
1068
+ onDataPointClick?.(clickParams);
1069
+ },
1070
+ [onDataPointClick]
1071
+ );
1072
+ const handleEvents = useMemo(() => {
1073
+ if (!onDataPointClick && !crossFilter?.enabled) return void 0;
1074
+ return {
1075
+ click: handleClick
1076
+ };
1077
+ }, [onDataPointClick, crossFilter?.enabled, handleClick]);
1078
+ if (error) {
1079
+ return /* @__PURE__ */ jsxs(
1080
+ "div",
1081
+ {
1082
+ style: {
1083
+ display: "flex",
1084
+ alignItems: "center",
1085
+ justifyContent: "center",
1086
+ height: typeof height === "number" ? `${height}px` : height,
1087
+ width: typeof width === "number" ? `${width}px` : width,
1088
+ color: theme.colors.error,
1089
+ fontSize: theme.fontSizes.sm
1090
+ },
1091
+ className,
1092
+ children: [
1093
+ "Error loading chart: ",
1094
+ error.message
1095
+ ]
1096
+ }
1097
+ );
1098
+ }
1099
+ return /* @__PURE__ */ jsx(
1100
+ EChartWrapper,
1101
+ {
1102
+ option,
1103
+ loading,
1104
+ height,
1105
+ width,
1106
+ className,
1107
+ onEvents: handleEvents
1108
+ }
1109
+ );
1110
+ }
1111
+ function LineChart3({
1112
+ data,
1113
+ xAxis,
1114
+ yAxis,
1115
+ seriesColumn,
1116
+ maxSeries,
1117
+ smooth = false,
1118
+ showArea = false,
1119
+ showPoints = false,
1120
+ showDataLabels = false,
1121
+ showLegend = true,
1122
+ referenceLines,
1123
+ colors,
1124
+ xAxisLabel,
1125
+ yAxisLabel,
1126
+ xAxisFormat,
1127
+ yAxisFormat = "number",
1128
+ loading = false,
1129
+ error,
1130
+ height = 300,
1131
+ width = "100%",
1132
+ className,
1133
+ onDataPointClick,
1134
+ crossFilter,
1135
+ selectedValue
1136
+ }) {
1137
+ const { theme } = useTheme();
1138
+ const yColumns = useMemo(
1139
+ () => Array.isArray(yAxis) ? yAxis : [yAxis],
1140
+ [yAxis]
1141
+ );
1142
+ const rawChartData = useMemo(
1143
+ () => toChartData(data, xAxis, yColumns, seriesColumn),
1144
+ [data, xAxis, yColumns, seriesColumn]
1145
+ );
1146
+ const chartData = useMemo(() => {
1147
+ if (!maxSeries || rawChartData.series.length <= maxSeries) {
1148
+ return rawChartData;
1149
+ }
1150
+ const seriesWithTotals = rawChartData.series.map((s) => ({
1151
+ ...s,
1152
+ total: s.data.reduce((sum, val) => sum + (val ?? 0), 0)
1153
+ }));
1154
+ const topSeries = seriesWithTotals.sort((a, b) => (b.total ?? 0) - (a.total ?? 0)).slice(0, maxSeries).map(({ total: _total, ...rest }) => rest);
1155
+ return {
1156
+ categories: rawChartData.categories,
1157
+ series: topSeries
1158
+ };
1159
+ }, [rawChartData, maxSeries]);
1160
+ const dateFormatter = useMemo(
1161
+ () => xAxisFormat ? createDateFormatter(xAxisFormat) : null,
1162
+ [xAxisFormat]
1163
+ );
1164
+ const formattedCategories = useMemo(() => {
1165
+ if (!dateFormatter) {
1166
+ return chartData.categories;
1167
+ }
1168
+ return chartData.categories.map((cat) => dateFormatter(cat));
1169
+ }, [chartData.categories, dateFormatter]);
1170
+ const seriesColors = useMemo(
1171
+ () => colors || getChartColors(theme, chartData.series.length),
1172
+ [colors, theme, chartData.series.length]
1173
+ );
1174
+ const isEmpty = isChartDataEmpty(data);
1175
+ const option = useMemo(() => {
1176
+ if (isEmpty) {
1177
+ return {
1178
+ graphic: createEmptyStateGraphic()
1179
+ };
1180
+ }
1181
+ const series = chartData.series.map((s, index) => {
1182
+ const color = seriesColors[index] ?? theme.colors.primary;
1183
+ const seriesData = s.data.map((value, dataIndex) => {
1184
+ const categoryValue = chartData.categories[dataIndex];
1185
+ const isSelected = selectedValue != null && categoryValue === selectedValue;
1186
+ const isOther = selectedValue != null && categoryValue !== selectedValue;
1187
+ return {
1188
+ value,
1189
+ itemStyle: {
1190
+ color,
1191
+ opacity: isOther ? 0.3 : 1,
1192
+ // Highlight selected point
1193
+ ...isSelected && {
1194
+ borderColor: theme.colors.primary,
1195
+ borderWidth: 3
1196
+ }
1197
+ },
1198
+ symbolSize: isSelected ? 10 : showPoints ? 6 : 0
1199
+ };
1200
+ });
1201
+ return {
1202
+ type: "line",
1203
+ name: s.name,
1204
+ data: seriesData,
1205
+ smooth: smooth ? 0.3 : false,
1206
+ symbol: showPoints || selectedValue != null ? "circle" : "none",
1207
+ symbolSize: showPoints ? 6 : 0,
1208
+ itemStyle: {
1209
+ color
1210
+ },
1211
+ lineStyle: {
1212
+ color,
1213
+ width: 2,
1214
+ opacity: selectedValue != null ? 0.5 : 1
1215
+ },
1216
+ areaStyle: showArea ? {
1217
+ color: createGradientColor(color, selectedValue != null ? 0.15 : 0.3)
1218
+ } : void 0,
1219
+ label: showDataLabels ? {
1220
+ show: true,
1221
+ position: "top",
1222
+ formatter: (params) => params.value !== null ? formatAxisLabel(params.value, yAxisFormat) : "",
1223
+ fontSize: 10,
1224
+ color: theme.colors.textMuted
1225
+ } : void 0,
1226
+ emphasis: {
1227
+ focus: "series",
1228
+ itemStyle: {
1229
+ shadowBlur: 10,
1230
+ shadowOffsetX: 0,
1231
+ shadowColor: "rgba(0, 0, 0, 0.2)"
1232
+ }
1233
+ },
1234
+ markLine: index === 0 && referenceLines ? createMarkLines(referenceLines, theme) : void 0,
1235
+ // Enable cursor pointer when cross-filter is enabled
1236
+ cursor: crossFilter?.enabled ? "pointer" : "default"
1237
+ };
1238
+ });
1239
+ const legend = showLegend ? {
1240
+ show: chartData.series.length > 1,
1241
+ data: chartData.series.map((s) => s.name),
1242
+ selectedMode: "multiple",
1243
+ top: 10
1244
+ } : void 0;
1245
+ return {
1246
+ tooltip: {
1247
+ trigger: "axis",
1248
+ axisPointer: {
1249
+ type: "cross",
1250
+ label: {
1251
+ backgroundColor: theme.chart.tooltipBackground
1252
+ }
1253
+ },
1254
+ formatter: (params) => {
1255
+ if (!Array.isArray(params) || params.length === 0) return "";
1256
+ const firstParam = params[0];
1257
+ if (!firstParam) return "";
1258
+ const header = `<div style="font-weight: 600; margin-bottom: 4px;">${firstParam.name}</div>`;
1259
+ const items = params.map(
1260
+ (p) => `<div>${p.marker} ${p.seriesName}: ${p.value !== null ? formatAxisLabel(p.value, yAxisFormat) : "-"}</div>`
1261
+ ).join("");
1262
+ return header + items;
1263
+ }
1264
+ },
1265
+ legend,
1266
+ grid: {
1267
+ left: 60,
1268
+ right: 20,
1269
+ top: legend ? 50 : 40,
1270
+ bottom: 40
1271
+ },
1272
+ xAxis: {
1273
+ type: "category",
1274
+ data: formattedCategories,
1275
+ name: xAxisLabel,
1276
+ nameLocation: "middle",
1277
+ nameGap: 35,
1278
+ axisLabel: {
1279
+ rotate: formattedCategories.length > 10 ? 45 : 0,
1280
+ interval: 0,
1281
+ hideOverlap: true
1282
+ },
1283
+ boundaryGap: false
1284
+ },
1285
+ yAxis: {
1286
+ type: "value",
1287
+ name: yAxisLabel,
1288
+ nameLocation: "middle",
1289
+ nameGap: 50,
1290
+ axisLabel: {
1291
+ formatter: (value) => formatAxisLabel(value, yAxisFormat)
1292
+ }
1293
+ },
1294
+ series
1295
+ };
1296
+ }, [
1297
+ isEmpty,
1298
+ chartData,
1299
+ formattedCategories,
1300
+ smooth,
1301
+ showPoints,
1302
+ showArea,
1303
+ showDataLabels,
1304
+ showLegend,
1305
+ referenceLines,
1306
+ seriesColors,
1307
+ xAxisLabel,
1308
+ yAxisLabel,
1309
+ yAxisFormat,
1310
+ theme,
1311
+ selectedValue,
1312
+ crossFilter?.enabled
1313
+ ]);
1314
+ const handleClick = useCallback(
1315
+ (params) => {
1316
+ const p = params;
1317
+ const rawValue = typeof p.value === "object" ? p.value?.value : p.value;
1318
+ const clickParams = {
1319
+ seriesName: p.seriesName ?? "",
1320
+ dataIndex: p.dataIndex ?? 0,
1321
+ value: rawValue ?? 0,
1322
+ name: p.name ?? ""
1323
+ };
1324
+ onDataPointClick?.(clickParams);
1325
+ },
1326
+ [onDataPointClick]
1327
+ );
1328
+ const handleEvents = useMemo(() => {
1329
+ if (!onDataPointClick && !crossFilter?.enabled) return void 0;
1330
+ return {
1331
+ click: handleClick
1332
+ };
1333
+ }, [onDataPointClick, crossFilter?.enabled, handleClick]);
1334
+ if (error) {
1335
+ return /* @__PURE__ */ jsxs(
1336
+ "div",
1337
+ {
1338
+ style: {
1339
+ display: "flex",
1340
+ alignItems: "center",
1341
+ justifyContent: "center",
1342
+ height: typeof height === "number" ? `${height}px` : height,
1343
+ width: typeof width === "number" ? `${width}px` : width,
1344
+ color: theme.colors.error,
1345
+ fontSize: theme.fontSizes.sm
1346
+ },
1347
+ className,
1348
+ children: [
1349
+ "Error loading chart: ",
1350
+ error.message
1351
+ ]
1352
+ }
1353
+ );
1354
+ }
1355
+ return /* @__PURE__ */ jsx(
1356
+ EChartWrapper,
1357
+ {
1358
+ option,
1359
+ loading,
1360
+ height,
1361
+ width,
1362
+ className,
1363
+ onEvents: handleEvents
1364
+ }
1365
+ );
1366
+ }
1367
+ function AreaChart({
1368
+ data,
1369
+ xAxis,
1370
+ yAxis,
1371
+ stacked = true,
1372
+ stackType = "normal",
1373
+ smooth = false,
1374
+ showLegend = true,
1375
+ opacity = 0.7,
1376
+ colors,
1377
+ xAxisLabel,
1378
+ yAxisLabel,
1379
+ xAxisFormat,
1380
+ loading = false,
1381
+ error,
1382
+ height = 300,
1383
+ width = "100%",
1384
+ className,
1385
+ onDataPointClick,
1386
+ crossFilter,
1387
+ selectedValue
1388
+ }) {
1389
+ const { theme } = useTheme();
1390
+ const yColumns = useMemo(
1391
+ () => Array.isArray(yAxis) ? yAxis : [yAxis],
1392
+ [yAxis]
1393
+ );
1394
+ const chartData = useMemo(
1395
+ () => toChartData(data, xAxis, yColumns),
1396
+ [data, xAxis, yColumns]
1397
+ );
1398
+ const dateFormatter = useMemo(
1399
+ () => xAxisFormat ? createDateFormatter(xAxisFormat) : null,
1400
+ [xAxisFormat]
1401
+ );
1402
+ const formattedCategories = useMemo(() => {
1403
+ if (!dateFormatter) return chartData.categories;
1404
+ return chartData.categories.map((cat) => dateFormatter(cat));
1405
+ }, [chartData.categories, dateFormatter]);
1406
+ const seriesColors = useMemo(
1407
+ () => colors || getChartColors(theme, yColumns.length),
1408
+ [colors, theme, yColumns.length]
1409
+ );
1410
+ const isEmpty = isChartDataEmpty(data);
1411
+ const option = useMemo(() => {
1412
+ if (isEmpty) {
1413
+ return {
1414
+ graphic: createEmptyStateGraphic()
1415
+ };
1416
+ }
1417
+ let processedData = chartData;
1418
+ if (stacked && stackType === "percent") {
1419
+ const totals = chartData.categories.map((_, catIndex) => {
1420
+ let total = 0;
1421
+ chartData.series.forEach((s) => {
1422
+ const val = s.data[catIndex];
1423
+ if (val !== null && val !== void 0) {
1424
+ total += val;
1425
+ }
1426
+ });
1427
+ return total;
1428
+ });
1429
+ processedData = {
1430
+ categories: chartData.categories,
1431
+ series: chartData.series.map((s) => ({
1432
+ ...s,
1433
+ data: s.data.map((val, catIndex) => {
1434
+ if (val === null) return null;
1435
+ const total = totals[catIndex] ?? 1;
1436
+ return total > 0 ? val / total * 100 : 0;
1437
+ })
1438
+ }))
1439
+ };
1440
+ }
1441
+ const series = processedData.series.map((s, index) => {
1442
+ const color = seriesColors[index] ?? theme.colors.primary;
1443
+ const seriesData = s.data.map((value, dataIndex) => {
1444
+ const categoryValue = processedData.categories[dataIndex];
1445
+ const isSelected = selectedValue != null && categoryValue === selectedValue;
1446
+ const isOther = selectedValue != null && categoryValue !== selectedValue;
1447
+ return {
1448
+ value,
1449
+ itemStyle: {
1450
+ opacity: isOther ? 0.3 : 1
1451
+ },
1452
+ // Show marker on selected point
1453
+ symbol: isSelected ? "circle" : "none",
1454
+ symbolSize: isSelected ? 8 : 0
1455
+ };
1456
+ });
1457
+ const baseOpacity = selectedValue != null ? opacity * 0.5 : opacity;
1458
+ return {
1459
+ type: "line",
1460
+ name: s.name,
1461
+ data: seriesData,
1462
+ stack: stacked ? "stack" : void 0,
1463
+ smooth: smooth ? 0.3 : false,
1464
+ symbol: selectedValue != null ? "circle" : "none",
1465
+ symbolSize: 6,
1466
+ itemStyle: {
1467
+ color
1468
+ },
1469
+ lineStyle: {
1470
+ color,
1471
+ width: 1
1472
+ },
1473
+ areaStyle: {
1474
+ color: stacked ? adjustColorOpacity(color, baseOpacity) : createGradientColor(color, baseOpacity)
1475
+ },
1476
+ emphasis: {
1477
+ focus: "series"
1478
+ },
1479
+ // Enable cursor pointer when cross-filter is enabled
1480
+ cursor: crossFilter?.enabled ? "pointer" : "default"
1481
+ };
1482
+ });
1483
+ const legend = showLegend ? {
1484
+ show: yColumns.length > 1,
1485
+ data: processedData.series.map((s) => s.name),
1486
+ selectedMode: "multiple",
1487
+ top: 10
1488
+ } : void 0;
1489
+ const yAxisFormat = stacked && stackType === "percent" ? "percent" : "number";
1490
+ return {
1491
+ tooltip: {
1492
+ trigger: "axis",
1493
+ axisPointer: {
1494
+ type: "cross",
1495
+ label: {
1496
+ backgroundColor: theme.chart.tooltipBackground
1497
+ }
1498
+ },
1499
+ formatter: (params) => {
1500
+ if (!Array.isArray(params) || params.length === 0) return "";
1501
+ const firstParam = params[0];
1502
+ if (!firstParam) return "";
1503
+ const header = `<div style="font-weight: 600; margin-bottom: 4px;">${firstParam.name}</div>`;
1504
+ const items = params.map((p) => {
1505
+ if (p.value === null) {
1506
+ return `<div>${p.marker} ${p.seriesName}: -</div>`;
1507
+ }
1508
+ const formatted = stackType === "percent" ? `${p.value.toFixed(1)}%` : formatAxisLabel(p.value, "compact");
1509
+ return `<div>${p.marker} ${p.seriesName}: ${formatted}</div>`;
1510
+ }).join("");
1511
+ return header + items;
1512
+ }
1513
+ },
1514
+ legend,
1515
+ grid: {
1516
+ left: 60,
1517
+ right: 20,
1518
+ top: legend ? 50 : 40,
1519
+ bottom: 40
1520
+ },
1521
+ xAxis: {
1522
+ type: "category",
1523
+ data: formattedCategories,
1524
+ name: xAxisLabel,
1525
+ nameLocation: "middle",
1526
+ nameGap: 35,
1527
+ axisLabel: {
1528
+ rotate: formattedCategories.length > 10 ? 45 : 0,
1529
+ interval: 0,
1530
+ hideOverlap: true
1531
+ },
1532
+ boundaryGap: false
1533
+ },
1534
+ yAxis: {
1535
+ type: "value",
1536
+ name: yAxisLabel,
1537
+ nameLocation: "middle",
1538
+ nameGap: 50,
1539
+ max: stackType === "percent" ? 100 : void 0,
1540
+ axisLabel: {
1541
+ formatter: (value) => {
1542
+ if (stackType === "percent") {
1543
+ return `${value.toFixed(0)}%`;
1544
+ }
1545
+ return formatAxisLabel(value, yAxisFormat);
1546
+ }
1547
+ }
1548
+ },
1549
+ series
1550
+ };
1551
+ }, [
1552
+ isEmpty,
1553
+ chartData,
1554
+ formattedCategories,
1555
+ stacked,
1556
+ stackType,
1557
+ smooth,
1558
+ showLegend,
1559
+ opacity,
1560
+ seriesColors,
1561
+ xAxisLabel,
1562
+ yAxisLabel,
1563
+ theme,
1564
+ yColumns.length,
1565
+ selectedValue,
1566
+ crossFilter?.enabled
1567
+ ]);
1568
+ const handleClick = useCallback(
1569
+ (params) => {
1570
+ const p = params;
1571
+ const rawValue = typeof p.value === "object" ? p.value?.value : p.value;
1572
+ const clickParams = {
1573
+ seriesName: p.seriesName ?? "",
1574
+ dataIndex: p.dataIndex ?? 0,
1575
+ value: rawValue ?? 0,
1576
+ name: p.name ?? ""
1577
+ };
1578
+ onDataPointClick?.(clickParams);
1579
+ },
1580
+ [onDataPointClick]
1581
+ );
1582
+ const handleEvents = useMemo(() => {
1583
+ if (!onDataPointClick && !crossFilter?.enabled) return void 0;
1584
+ return {
1585
+ click: handleClick
1586
+ };
1587
+ }, [onDataPointClick, crossFilter?.enabled, handleClick]);
1588
+ if (error) {
1589
+ return /* @__PURE__ */ jsxs(
1590
+ "div",
1591
+ {
1592
+ style: {
1593
+ display: "flex",
1594
+ alignItems: "center",
1595
+ justifyContent: "center",
1596
+ height: typeof height === "number" ? `${height}px` : height,
1597
+ width: typeof width === "number" ? `${width}px` : width,
1598
+ color: theme.colors.error,
1599
+ fontSize: theme.fontSizes.sm
1600
+ },
1601
+ className,
1602
+ children: [
1603
+ "Error loading chart: ",
1604
+ error.message
1605
+ ]
1606
+ }
1607
+ );
1608
+ }
1609
+ return /* @__PURE__ */ jsx(
1610
+ EChartWrapper,
1611
+ {
1612
+ option,
1613
+ loading,
1614
+ height,
1615
+ width,
1616
+ className,
1617
+ onEvents: handleEvents
1618
+ }
1619
+ );
1620
+ }
1621
+ function PieChart2({
1622
+ data,
1623
+ labelColumn,
1624
+ valueColumn,
1625
+ variant = "pie",
1626
+ donutRatio = 0.5,
1627
+ showLabels = true,
1628
+ labelPosition = "outside",
1629
+ showPercentage = false,
1630
+ showLegend = true,
1631
+ colors,
1632
+ startAngle = 90,
1633
+ sortSlices = "none",
1634
+ loading = false,
1635
+ error,
1636
+ height = 300,
1637
+ width = "100%",
1638
+ className,
1639
+ onDataPointClick,
1640
+ crossFilter,
1641
+ selectedValue,
1642
+ labelFormat
1643
+ }) {
1644
+ const { theme } = useTheme();
1645
+ const containerRef = useRef(null);
1646
+ const [containerWidth, setContainerWidth] = useState(0);
1647
+ useEffect(() => {
1648
+ const measureWidth = () => {
1649
+ if (containerRef.current) {
1650
+ setContainerWidth(containerRef.current.offsetWidth);
1651
+ }
1652
+ };
1653
+ measureWidth();
1654
+ window.addEventListener("resize", measureWidth);
1655
+ return () => window.removeEventListener("resize", measureWidth);
1656
+ }, []);
1657
+ const isNarrow = containerWidth < 300;
1658
+ const legendOrient = isNarrow ? "horizontal" : "vertical";
1659
+ const legendPosition = isNarrow ? { bottom: 0, left: "center" } : { left: "55%", top: "middle" };
1660
+ const chartData = useMemo(
1661
+ () => toChartData(data, labelColumn, [valueColumn]),
1662
+ [data, labelColumn, valueColumn]
1663
+ );
1664
+ const dateFormatter = useMemo(
1665
+ () => labelFormat ? createDateFormatter(labelFormat) : null,
1666
+ [labelFormat]
1667
+ );
1668
+ const formattedCategories = useMemo(() => {
1669
+ if (!dateFormatter) return chartData.categories;
1670
+ return chartData.categories.map((cat) => dateFormatter(cat));
1671
+ }, [chartData.categories, dateFormatter]);
1672
+ const pieData = useMemo(() => {
1673
+ if (chartData.series.length === 0) {
1674
+ return [];
1675
+ }
1676
+ const series = chartData.series[0];
1677
+ if (!series) {
1678
+ return [];
1679
+ }
1680
+ const items = formattedCategories.map((name, index) => ({
1681
+ name,
1682
+ value: series.data[index] ?? 0
1683
+ }));
1684
+ if (sortSlices === "asc") {
1685
+ items.sort((a, b) => (a.value || 0) - (b.value || 0));
1686
+ } else if (sortSlices === "desc") {
1687
+ items.sort((a, b) => (b.value || 0) - (a.value || 0));
1688
+ }
1689
+ return items;
1690
+ }, [formattedCategories, chartData.series, sortSlices]);
1691
+ const seriesColors = useMemo(
1692
+ () => colors || getChartColors(theme, pieData.length),
1693
+ [colors, theme, pieData.length]
1694
+ );
1695
+ const isEmpty = isChartDataEmpty(data);
1696
+ const option = useMemo(() => {
1697
+ if (isEmpty) {
1698
+ return {
1699
+ graphic: createEmptyStateGraphic()
1700
+ };
1701
+ }
1702
+ const total = pieData.reduce((sum, item) => sum + (item.value || 0), 0);
1703
+ const innerRadius = variant === "donut" ? `${donutRatio * 50}%` : "0%";
1704
+ let outerRadius;
1705
+ let center;
1706
+ if (showLegend) {
1707
+ if (isNarrow) {
1708
+ outerRadius = "55%";
1709
+ center = ["50%", "35%"];
1710
+ } else {
1711
+ outerRadius = "50%";
1712
+ center = ["25%", "50%"];
1713
+ }
1714
+ } else {
1715
+ outerRadius = labelPosition === "outside" ? "60%" : "75%";
1716
+ center = ["50%", "50%"];
1717
+ }
1718
+ const effectiveLabelPosition = showLegend && labelPosition === "outside" ? "inside" : labelPosition;
1719
+ const effectiveShowLabels = showLegend && labelPosition === "outside" ? false : showLabels;
1720
+ const label = effectiveShowLabels ? {
1721
+ show: true,
1722
+ position: effectiveLabelPosition,
1723
+ formatter: (params) => {
1724
+ if (showPercentage) {
1725
+ return effectiveLabelPosition === "inside" ? `${params.percent.toFixed(0)}%` : `${params.name}: ${params.percent.toFixed(1)}%`;
1726
+ }
1727
+ return effectiveLabelPosition === "inside" ? formatAxisLabel(params.value, "compact") : `${params.name}: ${formatAxisLabel(params.value, "compact")}`;
1728
+ },
1729
+ color: effectiveLabelPosition === "inside" ? "#ffffff" : theme.colors.text,
1730
+ fontSize: effectiveLabelPosition === "inside" ? 11 : 12
1731
+ } : { show: false };
1732
+ const labelLine = effectiveShowLabels && effectiveLabelPosition === "outside" ? {
1733
+ show: true,
1734
+ length: 10,
1735
+ length2: 10,
1736
+ lineStyle: {
1737
+ color: theme.colors.border
1738
+ }
1739
+ } : { show: false };
1740
+ const legend = showLegend ? {
1741
+ show: true,
1742
+ orient: legendOrient,
1743
+ ...legendPosition,
1744
+ selectedMode: "multiple",
1745
+ data: pieData.map((item) => item.name),
1746
+ formatter: (name) => {
1747
+ const item = pieData.find((d) => d.name === name);
1748
+ if (!item || total === 0) return name;
1749
+ const percent = (item.value || 0) / total * 100;
1750
+ return `${name} (${percent.toFixed(1)}%)`;
1751
+ },
1752
+ textStyle: {
1753
+ fontSize: isNarrow ? 11 : 12
1754
+ },
1755
+ itemGap: isNarrow ? 6 : 8,
1756
+ itemWidth: isNarrow ? 12 : 14,
1757
+ itemHeight: isNarrow ? 12 : 14
1758
+ } : void 0;
1759
+ return {
1760
+ tooltip: {
1761
+ trigger: "item",
1762
+ formatter: (params) => {
1763
+ const formatted = formatAxisLabel(params.value, "number");
1764
+ return `${params.marker} ${params.name}<br/>Value: ${formatted}<br/>Percent: ${params.percent.toFixed(1)}%`;
1765
+ }
1766
+ },
1767
+ legend,
1768
+ series: [
1769
+ {
1770
+ type: "pie",
1771
+ radius: [innerRadius, outerRadius],
1772
+ center,
1773
+ startAngle,
1774
+ data: pieData.map((item, index) => {
1775
+ const baseColor = seriesColors[index] ?? theme.colors.primary;
1776
+ const isSelected = selectedValue != null && item.name === selectedValue;
1777
+ const isOther = selectedValue != null && item.name !== selectedValue;
1778
+ return {
1779
+ ...item,
1780
+ itemStyle: {
1781
+ color: baseColor,
1782
+ opacity: isOther ? 0.3 : 1,
1783
+ // Highlight selected slice
1784
+ ...isSelected && {
1785
+ borderColor: theme.colors.primary,
1786
+ borderWidth: 3,
1787
+ shadowBlur: 15,
1788
+ shadowColor: "rgba(0, 0, 0, 0.3)"
1789
+ }
1790
+ }
1791
+ };
1792
+ }),
1793
+ label,
1794
+ labelLine,
1795
+ emphasis: {
1796
+ itemStyle: {
1797
+ shadowBlur: 10,
1798
+ shadowOffsetX: 0,
1799
+ shadowColor: "rgba(0, 0, 0, 0.2)"
1800
+ },
1801
+ // Disable emphasis label to avoid conflict with legend and tooltip
1802
+ label: {
1803
+ show: false
1804
+ }
1805
+ },
1806
+ // Enable cursor pointer when cross-filter is enabled
1807
+ cursor: crossFilter?.enabled ? "pointer" : "default",
1808
+ animationType: "scale",
1809
+ animationEasing: "elasticOut"
1810
+ }
1811
+ ]
1812
+ };
1813
+ }, [
1814
+ isEmpty,
1815
+ pieData,
1816
+ variant,
1817
+ donutRatio,
1818
+ showLabels,
1819
+ labelPosition,
1820
+ showPercentage,
1821
+ showLegend,
1822
+ startAngle,
1823
+ seriesColors,
1824
+ theme,
1825
+ selectedValue,
1826
+ crossFilter?.enabled,
1827
+ isNarrow,
1828
+ legendOrient,
1829
+ legendPosition
1830
+ ]);
1831
+ const handleClick = useCallback(
1832
+ (params) => {
1833
+ const p = params;
1834
+ const clickParams = {
1835
+ seriesName: p.seriesName ?? valueColumn,
1836
+ dataIndex: p.dataIndex ?? 0,
1837
+ value: p.value ?? 0,
1838
+ name: p.name ?? ""
1839
+ };
1840
+ onDataPointClick?.(clickParams);
1841
+ },
1842
+ [onDataPointClick, valueColumn]
1843
+ );
1844
+ const handleEvents = useMemo(() => {
1845
+ if (!onDataPointClick && !crossFilter?.enabled) return void 0;
1846
+ return {
1847
+ click: handleClick
1848
+ };
1849
+ }, [onDataPointClick, crossFilter?.enabled, handleClick]);
1850
+ if (error) {
1851
+ return /* @__PURE__ */ jsxs(
1852
+ "div",
1853
+ {
1854
+ ref: containerRef,
1855
+ style: {
1856
+ display: "flex",
1857
+ alignItems: "center",
1858
+ justifyContent: "center",
1859
+ height: typeof height === "number" ? `${height}px` : height,
1860
+ width: typeof width === "number" ? `${width}px` : width,
1861
+ color: theme.colors.error,
1862
+ fontSize: theme.fontSizes.sm
1863
+ },
1864
+ className,
1865
+ children: [
1866
+ "Error loading chart: ",
1867
+ error.message
1868
+ ]
1869
+ }
1870
+ );
1871
+ }
1872
+ if (containerWidth === 0) {
1873
+ return /* @__PURE__ */ jsx("div", { ref: containerRef, style: { height, width }, className });
1874
+ }
1875
+ return /* @__PURE__ */ jsx("div", { ref: containerRef, style: { height, width }, children: /* @__PURE__ */ jsx(
1876
+ EChartWrapper,
1877
+ {
1878
+ option,
1879
+ loading,
1880
+ height,
1881
+ width,
1882
+ className,
1883
+ onEvents: handleEvents
1884
+ }
1885
+ ) });
1886
+ }
1887
+ function extractScatterData(data, xColumn, yColumn, sizeColumn, colorColumn, labelColumn) {
1888
+ const points = [];
1889
+ const colorSet = /* @__PURE__ */ new Set();
1890
+ if (isQueryResult(data)) {
1891
+ const xIndex = data.columns.indexOf(xColumn);
1892
+ const yIndex = data.columns.indexOf(yColumn);
1893
+ const sizeIndex = sizeColumn ? data.columns.indexOf(sizeColumn) : -1;
1894
+ const colorIndex = colorColumn ? data.columns.indexOf(colorColumn) : -1;
1895
+ const labelIndex = labelColumn ? data.columns.indexOf(labelColumn) : -1;
1896
+ if (xIndex === -1 || yIndex === -1) {
1897
+ return { points: [], colorCategories: [] };
1898
+ }
1899
+ data.rows.forEach((row, index) => {
1900
+ const x = row[xIndex];
1901
+ const y = row[yIndex];
1902
+ if (x !== null && y !== null) {
1903
+ const size = sizeIndex >= 0 ? row[sizeIndex] : void 0;
1904
+ const color = colorIndex >= 0 ? String(row[colorIndex]) : void 0;
1905
+ const label = labelIndex >= 0 ? String(row[labelIndex]) : void 0;
1906
+ if (color) colorSet.add(color);
1907
+ points.push({
1908
+ x: typeof x === "number" ? x : Number(x),
1909
+ y: typeof y === "number" ? y : Number(y),
1910
+ size: size !== null && size !== void 0 ? Number(size) : void 0,
1911
+ color,
1912
+ label,
1913
+ index
1914
+ });
1915
+ }
1916
+ });
1917
+ } else {
1918
+ data.forEach((point, index) => {
1919
+ const x = point[xColumn];
1920
+ const y = point[yColumn];
1921
+ if (x !== null && x !== void 0 && y !== null && y !== void 0) {
1922
+ const size = sizeColumn ? point[sizeColumn] : void 0;
1923
+ const color = colorColumn ? String(point[colorColumn]) : void 0;
1924
+ const label = labelColumn ? String(point[labelColumn]) : void 0;
1925
+ if (color) colorSet.add(color);
1926
+ points.push({
1927
+ x: typeof x === "number" ? x : Number(x),
1928
+ y: typeof y === "number" ? y : Number(y),
1929
+ size: size !== null && size !== void 0 ? Number(size) : void 0,
1930
+ color,
1931
+ label,
1932
+ index
1933
+ });
1934
+ }
1935
+ });
1936
+ }
1937
+ return { points, colorCategories: Array.from(colorSet) };
1938
+ }
1939
+ function calculateTrendline(points) {
1940
+ if (points.length < 2) return null;
1941
+ const n = points.length;
1942
+ let sumX = 0;
1943
+ let sumY = 0;
1944
+ let sumXY = 0;
1945
+ let sumX2 = 0;
1946
+ let minX = Infinity;
1947
+ let maxX = -Infinity;
1948
+ points.forEach((p) => {
1949
+ sumX += p.x;
1950
+ sumY += p.y;
1951
+ sumXY += p.x * p.y;
1952
+ sumX2 += p.x * p.x;
1953
+ minX = Math.min(minX, p.x);
1954
+ maxX = Math.max(maxX, p.x);
1955
+ });
1956
+ const denominator = n * sumX2 - sumX * sumX;
1957
+ if (denominator === 0) return null;
1958
+ const slope = (n * sumXY - sumX * sumY) / denominator;
1959
+ const intercept = (sumY - slope * sumX) / n;
1960
+ return { slope, intercept, minX, maxX };
1961
+ }
1962
+ function ScatterChart2({
1963
+ data,
1964
+ xAxis,
1965
+ yAxis,
1966
+ sizeColumn,
1967
+ colorColumn,
1968
+ minSize = 10,
1969
+ maxSize = 50,
1970
+ showLabels = false,
1971
+ labelColumn,
1972
+ showTrendline = false,
1973
+ xAxisLabel,
1974
+ yAxisLabel,
1975
+ loading = false,
1976
+ error,
1977
+ height = 300,
1978
+ width = "100%",
1979
+ className,
1980
+ onDataPointClick
1981
+ }) {
1982
+ const { theme } = useTheme();
1983
+ const { points, colorCategories } = useMemo(
1984
+ () => extractScatterData(data, xAxis, yAxis, sizeColumn, colorColumn, labelColumn),
1985
+ [data, xAxis, yAxis, sizeColumn, colorColumn, labelColumn]
1986
+ );
1987
+ const categoryColors = useMemo(
1988
+ () => getChartColors(theme, Math.max(colorCategories.length, 1)),
1989
+ [theme, colorCategories.length]
1990
+ );
1991
+ const isEmpty = isChartDataEmpty(data) || points.length === 0;
1992
+ const option = useMemo(() => {
1993
+ if (isEmpty) {
1994
+ return {
1995
+ graphic: createEmptyStateGraphic()
1996
+ };
1997
+ }
1998
+ let sizeScale = () => minSize;
1999
+ if (sizeColumn) {
2000
+ const sizes = points.map((p) => p.size).filter((s) => s !== void 0);
2001
+ if (sizes.length > 0) {
2002
+ const minSizeVal = Math.min(...sizes);
2003
+ const maxSizeVal = Math.max(...sizes);
2004
+ const sizeRange = maxSizeVal - minSizeVal || 1;
2005
+ sizeScale = (val) => {
2006
+ const normalized = (val - minSizeVal) / sizeRange;
2007
+ return minSize + normalized * (maxSize - minSize);
2008
+ };
2009
+ }
2010
+ }
2011
+ const seriesMap = /* @__PURE__ */ new Map();
2012
+ if (colorColumn && colorCategories.length > 0) {
2013
+ colorCategories.forEach((cat) => {
2014
+ seriesMap.set(cat, []);
2015
+ });
2016
+ points.forEach((p) => {
2017
+ const cat = p.color || "default";
2018
+ const arr = seriesMap.get(cat);
2019
+ if (arr) {
2020
+ arr.push([
2021
+ p.x,
2022
+ p.y,
2023
+ p.size !== void 0 ? sizeScale(p.size) : minSize,
2024
+ p.label,
2025
+ String(p.index)
2026
+ ]);
2027
+ }
2028
+ });
2029
+ } else {
2030
+ seriesMap.set("data", points.map((p) => [
2031
+ p.x,
2032
+ p.y,
2033
+ p.size !== void 0 ? sizeScale(p.size) : minSize,
2034
+ p.label,
2035
+ String(p.index)
2036
+ ]));
2037
+ }
2038
+ const series = Array.from(seriesMap.entries()).map(
2039
+ ([name, seriesData], index) => ({
2040
+ type: "scatter",
2041
+ name,
2042
+ data: seriesData,
2043
+ symbolSize: (dataItem) => dataItem[2],
2044
+ itemStyle: {
2045
+ color: categoryColors[index] ?? theme.colors.primary
2046
+ },
2047
+ label: showLabels ? {
2048
+ show: true,
2049
+ position: "top",
2050
+ formatter: (params) => params.data[3] || "",
2051
+ fontSize: 10,
2052
+ color: theme.colors.textMuted
2053
+ } : void 0,
2054
+ emphasis: {
2055
+ focus: "series",
2056
+ itemStyle: {
2057
+ shadowBlur: 10,
2058
+ shadowOffsetX: 0,
2059
+ shadowColor: "rgba(0, 0, 0, 0.2)"
2060
+ }
2061
+ }
2062
+ })
2063
+ );
2064
+ if (showTrendline) {
2065
+ const trend = calculateTrendline(points);
2066
+ if (trend) {
2067
+ const y1 = trend.slope * trend.minX + trend.intercept;
2068
+ const y2 = trend.slope * trend.maxX + trend.intercept;
2069
+ series.push({
2070
+ type: "line",
2071
+ name: "Trendline",
2072
+ data: [
2073
+ [trend.minX, y1],
2074
+ [trend.maxX, y2]
2075
+ ],
2076
+ symbol: "none",
2077
+ lineStyle: {
2078
+ color: theme.colors.textMuted,
2079
+ type: "dashed",
2080
+ width: 2
2081
+ },
2082
+ silent: true
2083
+ });
2084
+ }
2085
+ }
2086
+ const legend = colorColumn && colorCategories.length > 1 ? {
2087
+ show: true,
2088
+ data: colorCategories,
2089
+ top: 10
2090
+ } : void 0;
2091
+ return {
2092
+ tooltip: {
2093
+ trigger: "item",
2094
+ formatter: (params) => {
2095
+ if (!params.data) return "";
2096
+ const [x, y, , label] = params.data;
2097
+ let html = `${params.marker}`;
2098
+ if (colorColumn) {
2099
+ html += ` ${params.seriesName}<br/>`;
2100
+ }
2101
+ if (label) {
2102
+ html += `${label}<br/>`;
2103
+ }
2104
+ html += `${xAxis}: ${formatAxisLabel(x, "number")}<br/>`;
2105
+ html += `${yAxis}: ${formatAxisLabel(y, "number")}`;
2106
+ return html;
2107
+ }
2108
+ },
2109
+ legend,
2110
+ grid: {
2111
+ left: 60,
2112
+ right: 20,
2113
+ top: legend ? 50 : 40,
2114
+ bottom: 40
2115
+ },
2116
+ xAxis: {
2117
+ type: "value",
2118
+ name: xAxisLabel || xAxis,
2119
+ nameLocation: "middle",
2120
+ nameGap: 35,
2121
+ axisLabel: {
2122
+ formatter: (value) => formatAxisLabel(value, "compact")
2123
+ },
2124
+ splitLine: {
2125
+ show: true,
2126
+ lineStyle: {
2127
+ type: "dashed"
2128
+ }
2129
+ }
2130
+ },
2131
+ yAxis: {
2132
+ type: "value",
2133
+ name: yAxisLabel || yAxis,
2134
+ nameLocation: "middle",
2135
+ nameGap: 50,
2136
+ axisLabel: {
2137
+ formatter: (value) => formatAxisLabel(value, "compact")
2138
+ },
2139
+ splitLine: {
2140
+ show: true,
2141
+ lineStyle: {
2142
+ type: "dashed"
2143
+ }
2144
+ }
2145
+ },
2146
+ series
2147
+ };
2148
+ }, [
2149
+ isEmpty,
2150
+ points,
2151
+ colorColumn,
2152
+ colorCategories,
2153
+ categoryColors,
2154
+ sizeColumn,
2155
+ minSize,
2156
+ maxSize,
2157
+ showLabels,
2158
+ showTrendline,
2159
+ xAxis,
2160
+ yAxis,
2161
+ xAxisLabel,
2162
+ yAxisLabel,
2163
+ theme
2164
+ ]);
2165
+ const handleEvents = useMemo(() => {
2166
+ if (!onDataPointClick) return void 0;
2167
+ return {
2168
+ click: (params) => {
2169
+ const p = params;
2170
+ if (!p.data) return;
2171
+ const [x, y, , , indexStr] = p.data;
2172
+ const clickParams = {
2173
+ seriesName: p.seriesName ?? "",
2174
+ dataIndex: indexStr ? parseInt(indexStr, 10) : p.dataIndex ?? 0,
2175
+ value: y,
2176
+ name: String(x)
2177
+ };
2178
+ onDataPointClick(clickParams);
2179
+ }
2180
+ };
2181
+ }, [onDataPointClick]);
2182
+ if (error) {
2183
+ return /* @__PURE__ */ jsxs(
2184
+ "div",
2185
+ {
2186
+ style: {
2187
+ display: "flex",
2188
+ alignItems: "center",
2189
+ justifyContent: "center",
2190
+ height: typeof height === "number" ? `${height}px` : height,
2191
+ width: typeof width === "number" ? `${width}px` : width,
2192
+ color: theme.colors.error,
2193
+ fontSize: theme.fontSizes.sm
2194
+ },
2195
+ className,
2196
+ children: [
2197
+ "Error loading chart: ",
2198
+ error.message
2199
+ ]
2200
+ }
2201
+ );
2202
+ }
2203
+ return /* @__PURE__ */ jsx(
2204
+ EChartWrapper,
2205
+ {
2206
+ option,
2207
+ loading,
2208
+ height,
2209
+ width,
2210
+ className,
2211
+ onEvents: handleEvents
2212
+ }
2213
+ );
2214
+ }
2215
+
2216
+ // src/charts/autoSuggest.ts
2217
+ function categorizeColumn(dataType) {
2218
+ const normalizedType = dataType.toLowerCase();
2219
+ if (normalizedType.includes("date") || normalizedType.includes("time") || normalizedType.includes("timestamp")) {
2220
+ return "date";
2221
+ }
2222
+ if (normalizedType.includes("int") || normalizedType.includes("numeric") || normalizedType.includes("decimal") || normalizedType.includes("float") || normalizedType.includes("double") || normalizedType.includes("real") || normalizedType.includes("money")) {
2223
+ return "numeric";
2224
+ }
2225
+ if (normalizedType.includes("char") || normalizedType.includes("text") || normalizedType.includes("varchar") || normalizedType.includes("bool") || normalizedType.includes("uuid")) {
2226
+ return "categorical";
2227
+ }
2228
+ return "unknown";
2229
+ }
2230
+ function countUniqueValues(result, columnIndex) {
2231
+ const uniqueValues = /* @__PURE__ */ new Set();
2232
+ result.rows.forEach((row) => {
2233
+ uniqueValues.add(row[columnIndex]);
2234
+ });
2235
+ return uniqueValues.size;
2236
+ }
2237
+ function analyzeColumns(result, selections) {
2238
+ return selections.map((sel) => {
2239
+ const columnIndex = result.columns.indexOf(sel.alias || sel.column);
2240
+ const typeIndex = columnIndex !== -1 ? columnIndex : 0;
2241
+ const dataType = result.column_types[typeIndex] || "unknown";
2242
+ return {
2243
+ name: sel.alias || sel.column,
2244
+ category: categorizeColumn(dataType),
2245
+ uniqueCount: columnIndex !== -1 ? countUniqueValues(result, columnIndex) : 0,
2246
+ hasAggregation: sel.aggregation !== "none"
2247
+ };
2248
+ });
2249
+ }
2250
+ function suggestChartType(result, columns) {
2251
+ const suggestions = [];
2252
+ if (!result || result.rows.length === 0 || columns.length === 0) {
2253
+ return [];
2254
+ }
2255
+ const analyzedColumns = analyzeColumns(result, columns);
2256
+ const dateColumns = analyzedColumns.filter((c) => c.category === "date");
2257
+ const numericColumns = analyzedColumns.filter((c) => c.category === "numeric");
2258
+ const categoricalColumns = analyzedColumns.filter((c) => c.category === "categorical");
2259
+ const aggregatedColumns = analyzedColumns.filter((c) => c.hasAggregation);
2260
+ if (result.rows.length === 1 && numericColumns.length === 1) {
2261
+ const numericCol = numericColumns[0];
2262
+ if (numericCol) {
2263
+ const colIndex = result.columns.indexOf(numericCol.name);
2264
+ const value = colIndex !== -1 ? result.rows[0]?.[colIndex] : 0;
2265
+ suggestions.push({
2266
+ type: "metric",
2267
+ confidence: 0.95,
2268
+ reason: "Single numeric value is best displayed as a metric card",
2269
+ config: {
2270
+ title: numericCol.name,
2271
+ value: typeof value === "number" ? value : 0,
2272
+ format: "number"
2273
+ }
2274
+ });
2275
+ }
2276
+ }
2277
+ if (dateColumns.length >= 1 && numericColumns.length >= 1) {
2278
+ const dateCol = dateColumns[0];
2279
+ if (dateCol) {
2280
+ const xAxis = dateCol.name;
2281
+ const yAxis = numericColumns.map((c) => c.name);
2282
+ suggestions.push({
2283
+ type: "line",
2284
+ confidence: 0.9,
2285
+ reason: "Time series data is best visualized as a line chart",
2286
+ config: {
2287
+ xAxis,
2288
+ yAxis: yAxis.length === 1 ? yAxis[0] : yAxis,
2289
+ smooth: true,
2290
+ showPoints: result.rows.length <= 20
2291
+ }
2292
+ });
2293
+ if (numericColumns.length > 1) {
2294
+ suggestions.push({
2295
+ type: "area",
2296
+ confidence: 0.75,
2297
+ reason: "Multiple time series can be compared with stacked area chart",
2298
+ config: {
2299
+ xAxis,
2300
+ yAxis,
2301
+ stacked: true,
2302
+ smooth: true
2303
+ }
2304
+ });
2305
+ }
2306
+ }
2307
+ }
2308
+ if (categoricalColumns.length >= 1 && numericColumns.length >= 1) {
2309
+ const categoryCol = categoricalColumns[0];
2310
+ if (categoryCol) {
2311
+ const xAxis = categoryCol.name;
2312
+ const yAxis = numericColumns.map((c) => c.name);
2313
+ const uniqueCount = categoryCol.uniqueCount;
2314
+ if (uniqueCount <= 20) {
2315
+ suggestions.push({
2316
+ type: "bar",
2317
+ confidence: 0.85,
2318
+ reason: "Categorical data with numeric values is best shown as a bar chart",
2319
+ config: {
2320
+ xAxis,
2321
+ yAxis: yAxis.length === 1 ? yAxis[0] : yAxis,
2322
+ orientation: uniqueCount > 10 ? "horizontal" : "vertical",
2323
+ stacked: numericColumns.length > 1
2324
+ }
2325
+ });
2326
+ }
2327
+ if (uniqueCount <= 7 && numericColumns.length === 1) {
2328
+ const numericCol = numericColumns[0];
2329
+ if (numericCol) {
2330
+ suggestions.push({
2331
+ type: "pie",
2332
+ confidence: 0.8,
2333
+ reason: "Small number of categories is well suited for a pie chart",
2334
+ config: {
2335
+ labelColumn: xAxis,
2336
+ valueColumn: numericCol.name,
2337
+ variant: uniqueCount <= 5 ? "donut" : "pie",
2338
+ showPercentage: true
2339
+ }
2340
+ });
2341
+ }
2342
+ }
2343
+ }
2344
+ }
2345
+ if (numericColumns.length >= 2 && dateColumns.length === 0) {
2346
+ const hasEnoughPoints = result.rows.length >= 5;
2347
+ const numericCol0 = numericColumns[0];
2348
+ const numericCol1 = numericColumns[1];
2349
+ const numericCol2 = numericColumns[2];
2350
+ if (hasEnoughPoints && numericCol0 && numericCol1) {
2351
+ suggestions.push({
2352
+ type: "scatter",
2353
+ confidence: 0.7,
2354
+ reason: "Two numeric columns can show correlation in a scatter plot",
2355
+ config: {
2356
+ xAxis: numericCol0.name,
2357
+ yAxis: numericCol1.name,
2358
+ sizeColumn: numericCol2?.name,
2359
+ showTrendline: true
2360
+ }
2361
+ });
2362
+ }
2363
+ }
2364
+ if (numericColumns.length > 1 && categoricalColumns.length >= 1 && dateColumns.length === 0 && aggregatedColumns.length > 0) {
2365
+ const categoryCol = categoricalColumns[0];
2366
+ if (categoryCol) {
2367
+ const xAxis = categoryCol.name;
2368
+ const yAxis = numericColumns.map((c) => c.name);
2369
+ const hasBarSuggestion = suggestions.some(
2370
+ (s) => s.type === "bar" && s.confidence >= 0.85
2371
+ );
2372
+ if (!hasBarSuggestion) {
2373
+ suggestions.push({
2374
+ type: "bar",
2375
+ confidence: 0.65,
2376
+ reason: "Multiple numeric metrics can be compared with grouped bars",
2377
+ config: {
2378
+ xAxis,
2379
+ yAxis,
2380
+ orientation: "vertical",
2381
+ stacked: false,
2382
+ showLegend: true
2383
+ }
2384
+ });
2385
+ }
2386
+ }
2387
+ }
2388
+ if (suggestions.length === 0 && analyzedColumns.length >= 2) {
2389
+ const firstCol = analyzedColumns[0];
2390
+ const secondCol = analyzedColumns[1];
2391
+ if (firstCol && secondCol) {
2392
+ suggestions.push({
2393
+ type: "bar",
2394
+ confidence: 0.5,
2395
+ reason: "Default visualization for the selected data",
2396
+ config: {
2397
+ xAxis: firstCol.name,
2398
+ yAxis: secondCol.name
2399
+ }
2400
+ });
2401
+ }
2402
+ }
2403
+ suggestions.sort((a, b) => b.confidence - a.confidence);
2404
+ return suggestions;
2405
+ }
2406
+
2407
+ export { AreaChart, BarChart2 as BarChart, EChartWrapper, LineChart3 as LineChart, MetricCard, PieChart2 as PieChart, ScatterChart2 as ScatterChart, Sparkline, TrendIndicator, adjustColorOpacity, applyThemeToOption, createChartTheme, createEmptyStateGraphic, createGradientColor, createMarkLines, dataPointsToChartData, formatAxisLabel, formatCompact, formatMetricValue, getChartColors, isChartDataEmpty, isQueryResult, queryResultToChartData, suggestChartType, toChartData };
2408
+ //# sourceMappingURL=chunk-2H5WTH4K.js.map
2409
+ //# sourceMappingURL=chunk-2H5WTH4K.js.map