csv-charts-ai 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.
- package/README.md +94 -0
- package/dist/index.d.ts +107 -0
- package/dist/index.js +949 -0
- package/dist/index.js.map +1 -0
- package/package.json +44 -0
package/README.md
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# csv-charts-ai
|
|
2
|
+
|
|
3
|
+
Reusable chart components for CSV/tabular data visualization, powered by [Recharts](https://recharts.org/).
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install csv-charts-ai
|
|
9
|
+
# or
|
|
10
|
+
pnpm add csv-charts-ai
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
### Peer Dependencies
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install react recharts lucide-react
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
```tsx
|
|
22
|
+
import { ChartDisplay, type TabularData, type ChartConfig } from "csv-charts-ai";
|
|
23
|
+
|
|
24
|
+
const data: TabularData = {
|
|
25
|
+
headers: ["Category", "Sales"],
|
|
26
|
+
rows: [
|
|
27
|
+
["Electronics", "1200"],
|
|
28
|
+
["Clothing", "800"],
|
|
29
|
+
["Food", "950"],
|
|
30
|
+
],
|
|
31
|
+
columns: [
|
|
32
|
+
{ name: "Category", type: "string", index: 0 },
|
|
33
|
+
{ name: "Sales", type: "number", index: 1 },
|
|
34
|
+
],
|
|
35
|
+
rowCount: 3,
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const charts: ChartConfig[] = [
|
|
39
|
+
{
|
|
40
|
+
id: "chart-1",
|
|
41
|
+
type: "bar",
|
|
42
|
+
title: "Sales by Category",
|
|
43
|
+
description: "Comparison of sales across categories",
|
|
44
|
+
xAxis: "Category",
|
|
45
|
+
yAxis: "Sales",
|
|
46
|
+
aggregation: "sum",
|
|
47
|
+
},
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
function App() {
|
|
51
|
+
return <ChartDisplay data={data} charts={charts} />;
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Components
|
|
56
|
+
|
|
57
|
+
### `ChartDisplay`
|
|
58
|
+
|
|
59
|
+
Main component that renders multiple charts from configuration.
|
|
60
|
+
|
|
61
|
+
| Prop | Type | Description |
|
|
62
|
+
|------|------|-------------|
|
|
63
|
+
| `data` | `TabularData` | The tabular data to visualize |
|
|
64
|
+
| `charts` | `ChartConfig[]` | Array of chart configurations |
|
|
65
|
+
| `onRegenerate` | `(chart: ChartConfig) => Promise<void>` | Optional callback for chart regeneration |
|
|
66
|
+
| `cardWrapper` | `React.ComponentType` | Optional wrapper component for each chart card |
|
|
67
|
+
|
|
68
|
+
### `SingleChart`
|
|
69
|
+
|
|
70
|
+
Renders an individual chart with toolbar controls (sort, zoom, trendline, export).
|
|
71
|
+
|
|
72
|
+
### `ChartToolbar`
|
|
73
|
+
|
|
74
|
+
Toolbar component with sort, filter, zoom, trendline, and export controls.
|
|
75
|
+
|
|
76
|
+
### `processChartData`
|
|
77
|
+
|
|
78
|
+
Utility function that processes `TabularData` into chart-ready data points with aggregation support.
|
|
79
|
+
|
|
80
|
+
## Chart Types
|
|
81
|
+
|
|
82
|
+
- **bar** — Category comparisons
|
|
83
|
+
- **line** — Trends over time
|
|
84
|
+
- **area** — Cumulative trends
|
|
85
|
+
- **scatter** — Correlations
|
|
86
|
+
- **pie** — Proportional distributions
|
|
87
|
+
|
|
88
|
+
## Aggregation Types
|
|
89
|
+
|
|
90
|
+
`sum` | `avg` | `count` | `min` | `max` | `none`
|
|
91
|
+
|
|
92
|
+
## License
|
|
93
|
+
|
|
94
|
+
MIT
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
|
|
3
|
+
type ChartType = "bar" | "line" | "pie" | "scatter" | "area";
|
|
4
|
+
type AggregationType = "sum" | "avg" | "count" | "min" | "max" | "none";
|
|
5
|
+
interface TabularData {
|
|
6
|
+
headers: string[];
|
|
7
|
+
rows: string[][];
|
|
8
|
+
columns: {
|
|
9
|
+
name: string;
|
|
10
|
+
type: "string" | "number" | "date" | "boolean";
|
|
11
|
+
index: number;
|
|
12
|
+
}[];
|
|
13
|
+
rowCount: number;
|
|
14
|
+
}
|
|
15
|
+
interface ChartConfig {
|
|
16
|
+
id: string;
|
|
17
|
+
type: ChartType;
|
|
18
|
+
title: string;
|
|
19
|
+
description: string;
|
|
20
|
+
xAxis: string;
|
|
21
|
+
yAxis: string;
|
|
22
|
+
groupBy?: string;
|
|
23
|
+
aggregation: AggregationType;
|
|
24
|
+
dataConfig?: {
|
|
25
|
+
xColumn: string;
|
|
26
|
+
yColumn: string;
|
|
27
|
+
groupColumn?: string;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
type SortOrder = "none" | "asc" | "desc";
|
|
31
|
+
type ChartDataPoint = Record<string, string | number>;
|
|
32
|
+
interface ChartTheme {
|
|
33
|
+
colors: string[];
|
|
34
|
+
background: string;
|
|
35
|
+
cardBackground: string;
|
|
36
|
+
border: string;
|
|
37
|
+
text: string;
|
|
38
|
+
textMuted: string;
|
|
39
|
+
textDimmed: string;
|
|
40
|
+
gridStroke: string;
|
|
41
|
+
tooltipBackground: string;
|
|
42
|
+
tooltipBorder: string;
|
|
43
|
+
accentPrimary: string;
|
|
44
|
+
accentSecondary: string;
|
|
45
|
+
accentSuccess: string;
|
|
46
|
+
accentDanger: string;
|
|
47
|
+
}
|
|
48
|
+
declare const defaultDarkTheme: ChartTheme;
|
|
49
|
+
declare const defaultLightTheme: ChartTheme;
|
|
50
|
+
|
|
51
|
+
interface ChartDisplayProps {
|
|
52
|
+
data: TabularData;
|
|
53
|
+
charts: ChartConfig[];
|
|
54
|
+
onRegenerate?: (chart: ChartConfig) => Promise<void>;
|
|
55
|
+
/** Optional wrapper component for each chart card */
|
|
56
|
+
cardWrapper?: React.ComponentType<{
|
|
57
|
+
children: React.ReactNode;
|
|
58
|
+
title?: string;
|
|
59
|
+
className?: string;
|
|
60
|
+
}>;
|
|
61
|
+
/** Optional theme override */
|
|
62
|
+
theme?: ChartTheme;
|
|
63
|
+
}
|
|
64
|
+
declare function ChartDisplay({ data, charts, onRegenerate, cardWrapper: CardWrapper, theme, }: ChartDisplayProps): react_jsx_runtime.JSX.Element | null;
|
|
65
|
+
|
|
66
|
+
interface SingleChartProps {
|
|
67
|
+
data: TabularData;
|
|
68
|
+
chart: ChartConfig;
|
|
69
|
+
onRegenerate?: (chart: ChartConfig) => Promise<void>;
|
|
70
|
+
}
|
|
71
|
+
declare function SingleChart({ data, chart, onRegenerate }: SingleChartProps): react_jsx_runtime.JSX.Element;
|
|
72
|
+
|
|
73
|
+
interface ChartToolbarProps {
|
|
74
|
+
chartType: ChartType;
|
|
75
|
+
sortOrder: SortOrder;
|
|
76
|
+
limitResults: number;
|
|
77
|
+
showBrush: boolean;
|
|
78
|
+
showTrendline: boolean;
|
|
79
|
+
isRegenerating: boolean;
|
|
80
|
+
hasRegenerate: boolean;
|
|
81
|
+
onToggleSort: () => void;
|
|
82
|
+
onLimitChange: (limit: number) => void;
|
|
83
|
+
onToggleBrush: () => void;
|
|
84
|
+
onToggleTrendline: () => void;
|
|
85
|
+
onExportCSV: () => void;
|
|
86
|
+
onExportPNG: () => void;
|
|
87
|
+
onRegenerate: () => void;
|
|
88
|
+
}
|
|
89
|
+
declare function ChartToolbar({ chartType, sortOrder, limitResults, showBrush, showTrendline, isRegenerating, hasRegenerate, onToggleSort, onLimitChange, onToggleBrush, onToggleTrendline, onExportCSV, onExportPNG, onRegenerate, }: ChartToolbarProps): react_jsx_runtime.JSX.Element;
|
|
90
|
+
|
|
91
|
+
interface ProcessedChartResult {
|
|
92
|
+
data: ChartDataPoint[];
|
|
93
|
+
seriesKeys: string[];
|
|
94
|
+
yKey: string;
|
|
95
|
+
}
|
|
96
|
+
declare const processChartData: (data: TabularData, chart: ChartConfig, sortOrder?: SortOrder, limit?: number) => ChartDataPoint[];
|
|
97
|
+
declare const processChartDataMultiSeries: (data: TabularData, chart: ChartConfig, sortOrder?: SortOrder, limit?: number) => ProcessedChartResult;
|
|
98
|
+
|
|
99
|
+
declare function ChartThemeProvider({ theme, children, }: {
|
|
100
|
+
theme: ChartTheme;
|
|
101
|
+
children: React.ReactNode;
|
|
102
|
+
}): react_jsx_runtime.JSX.Element;
|
|
103
|
+
declare function useChartTheme(): ChartTheme;
|
|
104
|
+
|
|
105
|
+
declare const COLORS: string[];
|
|
106
|
+
|
|
107
|
+
export { type AggregationType, COLORS, type ChartConfig, type ChartDataPoint, ChartDisplay, type ChartDisplayProps, type ChartTheme, ChartThemeProvider, ChartToolbar, type ChartType, type ProcessedChartResult, SingleChart, type SingleChartProps, type SortOrder, type TabularData, defaultDarkTheme, defaultLightTheme, processChartData, processChartDataMultiSeries, useChartTheme };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,949 @@
|
|
|
1
|
+
// src/types.ts
|
|
2
|
+
var defaultDarkTheme = {
|
|
3
|
+
colors: [
|
|
4
|
+
"#8b5cf6",
|
|
5
|
+
"#06b6d4",
|
|
6
|
+
"#f43f5e",
|
|
7
|
+
"#eab308",
|
|
8
|
+
"#10b981",
|
|
9
|
+
"#3b82f6",
|
|
10
|
+
"#d946ef",
|
|
11
|
+
"#f97316"
|
|
12
|
+
],
|
|
13
|
+
background: "rgba(15, 23, 42, 0.5)",
|
|
14
|
+
cardBackground: "rgba(255, 255, 255, 0.05)",
|
|
15
|
+
border: "rgba(255, 255, 255, 0.1)",
|
|
16
|
+
text: "#ffffff",
|
|
17
|
+
textMuted: "#9ca3af",
|
|
18
|
+
textDimmed: "#6b7280",
|
|
19
|
+
gridStroke: "#374151",
|
|
20
|
+
tooltipBackground: "#1f2937",
|
|
21
|
+
tooltipBorder: "1px solid #374151",
|
|
22
|
+
accentPrimary: "#8b5cf6",
|
|
23
|
+
accentSecondary: "#06b6d4",
|
|
24
|
+
accentSuccess: "#10b981",
|
|
25
|
+
accentDanger: "#ef4444"
|
|
26
|
+
};
|
|
27
|
+
var defaultLightTheme = {
|
|
28
|
+
colors: [
|
|
29
|
+
"#7c3aed",
|
|
30
|
+
"#0891b2",
|
|
31
|
+
"#e11d48",
|
|
32
|
+
"#ca8a04",
|
|
33
|
+
"#059669",
|
|
34
|
+
"#2563eb",
|
|
35
|
+
"#c026d3",
|
|
36
|
+
"#ea580c"
|
|
37
|
+
],
|
|
38
|
+
background: "#ffffff",
|
|
39
|
+
cardBackground: "#f8fafc",
|
|
40
|
+
border: "#e2e8f0",
|
|
41
|
+
text: "#0f172a",
|
|
42
|
+
textMuted: "#64748b",
|
|
43
|
+
textDimmed: "#94a3b8",
|
|
44
|
+
gridStroke: "#e2e8f0",
|
|
45
|
+
tooltipBackground: "#ffffff",
|
|
46
|
+
tooltipBorder: "1px solid #e2e8f0",
|
|
47
|
+
accentPrimary: "#7c3aed",
|
|
48
|
+
accentSecondary: "#0891b2",
|
|
49
|
+
accentSuccess: "#059669",
|
|
50
|
+
accentDanger: "#dc2626"
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// src/ThemeContext.tsx
|
|
54
|
+
import { createContext, useContext } from "react";
|
|
55
|
+
import { jsx } from "react/jsx-runtime";
|
|
56
|
+
var ChartThemeContext = createContext(defaultDarkTheme);
|
|
57
|
+
function ChartThemeProvider({
|
|
58
|
+
theme,
|
|
59
|
+
children
|
|
60
|
+
}) {
|
|
61
|
+
return /* @__PURE__ */ jsx(ChartThemeContext.Provider, { value: theme, children });
|
|
62
|
+
}
|
|
63
|
+
function useChartTheme() {
|
|
64
|
+
return useContext(ChartThemeContext);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// src/SingleChart.tsx
|
|
68
|
+
import { useState, useMemo, useCallback, useRef } from "react";
|
|
69
|
+
import {
|
|
70
|
+
BarChart,
|
|
71
|
+
Bar,
|
|
72
|
+
LineChart,
|
|
73
|
+
Line,
|
|
74
|
+
PieChart,
|
|
75
|
+
Pie,
|
|
76
|
+
ScatterChart,
|
|
77
|
+
Scatter,
|
|
78
|
+
AreaChart,
|
|
79
|
+
Area,
|
|
80
|
+
XAxis,
|
|
81
|
+
YAxis,
|
|
82
|
+
CartesianGrid,
|
|
83
|
+
Tooltip,
|
|
84
|
+
Legend,
|
|
85
|
+
ResponsiveContainer,
|
|
86
|
+
Cell,
|
|
87
|
+
Brush,
|
|
88
|
+
ReferenceLine
|
|
89
|
+
} from "recharts";
|
|
90
|
+
import { RefreshCw as RefreshCw2 } from "lucide-react";
|
|
91
|
+
|
|
92
|
+
// src/processChartData.ts
|
|
93
|
+
var processChartData = (data, chart, sortOrder = "none", limit = 20) => {
|
|
94
|
+
const result = processChartDataMultiSeries(data, chart, sortOrder, limit);
|
|
95
|
+
return result.data;
|
|
96
|
+
};
|
|
97
|
+
var processChartDataMultiSeries = (data, chart, sortOrder = "none", limit = 20) => {
|
|
98
|
+
let result = [];
|
|
99
|
+
const xColDef = data.columns.find(
|
|
100
|
+
(c) => c.name.toLowerCase() === chart.xAxis.toLowerCase()
|
|
101
|
+
);
|
|
102
|
+
const yColDef = data.columns.find(
|
|
103
|
+
(c) => c.name.toLowerCase() === chart.yAxis.toLowerCase()
|
|
104
|
+
);
|
|
105
|
+
const groupColDef = chart.groupBy ? data.columns.find(
|
|
106
|
+
(c) => c.name.toLowerCase() === chart.groupBy.toLowerCase()
|
|
107
|
+
) : void 0;
|
|
108
|
+
if (!xColDef) return { data: [], seriesKeys: [], yKey: chart.yAxis };
|
|
109
|
+
const xCol = xColDef.name;
|
|
110
|
+
const xIdx = xColDef.index;
|
|
111
|
+
const isCountMode = chart.aggregation === "count";
|
|
112
|
+
const yCol = yColDef?.name ?? "count";
|
|
113
|
+
const yIdx = yColDef?.index ?? -1;
|
|
114
|
+
const supportsGroupBy = chart.type !== "pie" && chart.type !== "scatter";
|
|
115
|
+
if (supportsGroupBy && groupColDef && chart.aggregation && chart.aggregation !== "none") {
|
|
116
|
+
const groupIdx = groupColDef.index;
|
|
117
|
+
const allGroups = /* @__PURE__ */ new Set();
|
|
118
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
119
|
+
data.rows.forEach((row) => {
|
|
120
|
+
const xVal = String(row[xIdx] ?? "").trim();
|
|
121
|
+
const groupVal = String(row[groupIdx] ?? "").trim();
|
|
122
|
+
if (!xVal || !groupVal) return;
|
|
123
|
+
allGroups.add(groupVal);
|
|
124
|
+
if (!grouped.has(xVal)) grouped.set(xVal, /* @__PURE__ */ new Map());
|
|
125
|
+
const xGroup = grouped.get(xVal);
|
|
126
|
+
if (isCountMode) {
|
|
127
|
+
const current = xGroup.get(groupVal) ?? {
|
|
128
|
+
sum: 0,
|
|
129
|
+
count: 0,
|
|
130
|
+
min: 0,
|
|
131
|
+
max: 0
|
|
132
|
+
};
|
|
133
|
+
xGroup.set(groupVal, { ...current, count: current.count + 1 });
|
|
134
|
+
} else if (yIdx >= 0) {
|
|
135
|
+
const yVal = parseFloat(String(row[yIdx] ?? "0"));
|
|
136
|
+
if (!isNaN(yVal)) {
|
|
137
|
+
const current = xGroup.get(groupVal) ?? {
|
|
138
|
+
sum: 0,
|
|
139
|
+
count: 0,
|
|
140
|
+
min: Infinity,
|
|
141
|
+
max: -Infinity
|
|
142
|
+
};
|
|
143
|
+
xGroup.set(groupVal, {
|
|
144
|
+
sum: current.sum + yVal,
|
|
145
|
+
count: current.count + 1,
|
|
146
|
+
min: Math.min(current.min, yVal),
|
|
147
|
+
max: Math.max(current.max, yVal)
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
const seriesKeys = Array.from(allGroups).slice(0, 8);
|
|
153
|
+
grouped.forEach((groupMap, xKey) => {
|
|
154
|
+
const point = { [xCol]: xKey };
|
|
155
|
+
seriesKeys.forEach((groupKey) => {
|
|
156
|
+
const stats = groupMap.get(groupKey);
|
|
157
|
+
if (stats) {
|
|
158
|
+
let value;
|
|
159
|
+
switch (chart.aggregation) {
|
|
160
|
+
case "sum":
|
|
161
|
+
value = stats.sum;
|
|
162
|
+
break;
|
|
163
|
+
case "avg":
|
|
164
|
+
value = stats.count > 0 ? stats.sum / stats.count : 0;
|
|
165
|
+
break;
|
|
166
|
+
case "count":
|
|
167
|
+
value = stats.count;
|
|
168
|
+
break;
|
|
169
|
+
case "min":
|
|
170
|
+
value = stats.min === Infinity ? 0 : stats.min;
|
|
171
|
+
break;
|
|
172
|
+
case "max":
|
|
173
|
+
value = stats.max === -Infinity ? 0 : stats.max;
|
|
174
|
+
break;
|
|
175
|
+
default:
|
|
176
|
+
value = stats.sum;
|
|
177
|
+
}
|
|
178
|
+
point[groupKey] = Math.round(value * 100) / 100;
|
|
179
|
+
} else {
|
|
180
|
+
point[groupKey] = 0;
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
result.push(point);
|
|
184
|
+
});
|
|
185
|
+
if (sortOrder !== "none" && seriesKeys[0]) {
|
|
186
|
+
const firstKey = seriesKeys[0];
|
|
187
|
+
result.sort((a, b) => {
|
|
188
|
+
const aVal = a[firstKey] ?? 0;
|
|
189
|
+
const bVal = b[firstKey] ?? 0;
|
|
190
|
+
return sortOrder === "desc" ? bVal - aVal : aVal - bVal;
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
return {
|
|
194
|
+
data: result.slice(0, limit),
|
|
195
|
+
seriesKeys,
|
|
196
|
+
yKey: yCol
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
if (chart.aggregation && chart.aggregation !== "none") {
|
|
200
|
+
const groups = /* @__PURE__ */ new Map();
|
|
201
|
+
data.rows.forEach((row) => {
|
|
202
|
+
const xVal = String(row[xIdx] ?? "").trim();
|
|
203
|
+
if (!xVal) return;
|
|
204
|
+
if (isCountMode) {
|
|
205
|
+
const current = groups.get(xVal) ?? {
|
|
206
|
+
sum: 0,
|
|
207
|
+
count: 0,
|
|
208
|
+
min: 0,
|
|
209
|
+
max: 0
|
|
210
|
+
};
|
|
211
|
+
groups.set(xVal, {
|
|
212
|
+
...current,
|
|
213
|
+
count: current.count + 1
|
|
214
|
+
});
|
|
215
|
+
} else if (yIdx >= 0) {
|
|
216
|
+
const yVal = parseFloat(String(row[yIdx] ?? "0"));
|
|
217
|
+
if (!isNaN(yVal)) {
|
|
218
|
+
const current = groups.get(xVal) ?? {
|
|
219
|
+
sum: 0,
|
|
220
|
+
count: 0,
|
|
221
|
+
min: Infinity,
|
|
222
|
+
max: -Infinity
|
|
223
|
+
};
|
|
224
|
+
groups.set(xVal, {
|
|
225
|
+
sum: current.sum + yVal,
|
|
226
|
+
count: current.count + 1,
|
|
227
|
+
min: Math.min(current.min, yVal),
|
|
228
|
+
max: Math.max(current.max, yVal)
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
groups.forEach((stats, key) => {
|
|
234
|
+
let value;
|
|
235
|
+
switch (chart.aggregation) {
|
|
236
|
+
case "sum":
|
|
237
|
+
value = stats.sum;
|
|
238
|
+
break;
|
|
239
|
+
case "avg":
|
|
240
|
+
value = stats.count > 0 ? stats.sum / stats.count : 0;
|
|
241
|
+
break;
|
|
242
|
+
case "count":
|
|
243
|
+
value = stats.count;
|
|
244
|
+
break;
|
|
245
|
+
case "min":
|
|
246
|
+
value = stats.min === Infinity ? 0 : stats.min;
|
|
247
|
+
break;
|
|
248
|
+
case "max":
|
|
249
|
+
value = stats.max === -Infinity ? 0 : stats.max;
|
|
250
|
+
break;
|
|
251
|
+
default:
|
|
252
|
+
value = stats.sum;
|
|
253
|
+
}
|
|
254
|
+
result.push({ [xCol]: key, [yCol]: Math.round(value * 100) / 100 });
|
|
255
|
+
});
|
|
256
|
+
} else if (yIdx >= 0) {
|
|
257
|
+
result = data.rows.slice(0, Math.min(limit * 2, data.rows.length)).map((row) => ({
|
|
258
|
+
[xCol]: row[xIdx] ?? "",
|
|
259
|
+
[yCol]: parseFloat(String(row[yIdx] ?? "0"))
|
|
260
|
+
})).filter((item) => !isNaN(item[yCol]));
|
|
261
|
+
}
|
|
262
|
+
if (sortOrder !== "none") {
|
|
263
|
+
result.sort((a, b) => {
|
|
264
|
+
const aVal = a[yCol];
|
|
265
|
+
const bVal = b[yCol];
|
|
266
|
+
return sortOrder === "desc" ? bVal - aVal : aVal - bVal;
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
return {
|
|
270
|
+
data: result.slice(0, limit),
|
|
271
|
+
seriesKeys: [],
|
|
272
|
+
yKey: yCol
|
|
273
|
+
};
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
// src/ChartToolbar.tsx
|
|
277
|
+
import {
|
|
278
|
+
RefreshCw,
|
|
279
|
+
Download,
|
|
280
|
+
SortAsc,
|
|
281
|
+
SortDesc,
|
|
282
|
+
RotateCcw,
|
|
283
|
+
TrendingUp,
|
|
284
|
+
Filter,
|
|
285
|
+
Image as Image2
|
|
286
|
+
} from "lucide-react";
|
|
287
|
+
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
288
|
+
function ChartToolbar({
|
|
289
|
+
chartType,
|
|
290
|
+
sortOrder,
|
|
291
|
+
limitResults,
|
|
292
|
+
showBrush,
|
|
293
|
+
showTrendline,
|
|
294
|
+
isRegenerating,
|
|
295
|
+
hasRegenerate,
|
|
296
|
+
onToggleSort,
|
|
297
|
+
onLimitChange,
|
|
298
|
+
onToggleBrush,
|
|
299
|
+
onToggleTrendline,
|
|
300
|
+
onExportCSV,
|
|
301
|
+
onExportPNG,
|
|
302
|
+
onRegenerate
|
|
303
|
+
}) {
|
|
304
|
+
const supportsBrush = chartType === "line" || chartType === "area" || chartType === "bar";
|
|
305
|
+
const supportsTrendline = chartType === "bar" || chartType === "line" || chartType === "area";
|
|
306
|
+
return /* @__PURE__ */ jsxs("div", { className: "mb-4 flex flex-wrap items-center gap-2 rounded-xl border border-white/10 bg-white/5 p-3", children: [
|
|
307
|
+
/* @__PURE__ */ jsxs(
|
|
308
|
+
"button",
|
|
309
|
+
{
|
|
310
|
+
onClick: onToggleSort,
|
|
311
|
+
className: `flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-sm transition-colors ${sortOrder !== "none" ? "border border-violet-500/30 bg-violet-500/20 text-violet-400" : "bg-white/5 text-gray-400 hover:bg-white/10 hover:text-white"}`,
|
|
312
|
+
title: "Sort by value",
|
|
313
|
+
children: [
|
|
314
|
+
sortOrder === "asc" ? /* @__PURE__ */ jsx2(SortAsc, { className: "h-4 w-4" }) : sortOrder === "desc" ? /* @__PURE__ */ jsx2(SortDesc, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx2(SortDesc, { className: "h-4 w-4 opacity-50" }),
|
|
315
|
+
"Sort"
|
|
316
|
+
]
|
|
317
|
+
}
|
|
318
|
+
),
|
|
319
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
320
|
+
/* @__PURE__ */ jsx2(Filter, { className: "h-4 w-4 text-gray-400" }),
|
|
321
|
+
/* @__PURE__ */ jsxs(
|
|
322
|
+
"select",
|
|
323
|
+
{
|
|
324
|
+
value: limitResults,
|
|
325
|
+
onChange: (e) => onLimitChange(Number(e.target.value)),
|
|
326
|
+
className: "rounded-lg border border-white/10 bg-white/5 px-2 py-1.5 text-sm text-gray-300 focus:border-violet-500/50 focus:outline-none",
|
|
327
|
+
children: [
|
|
328
|
+
/* @__PURE__ */ jsx2("option", { value: 10, children: "Top 10" }),
|
|
329
|
+
/* @__PURE__ */ jsx2("option", { value: 20, children: "Top 20" }),
|
|
330
|
+
/* @__PURE__ */ jsx2("option", { value: 50, children: "Top 50" }),
|
|
331
|
+
/* @__PURE__ */ jsx2("option", { value: 100, children: "Top 100" }),
|
|
332
|
+
/* @__PURE__ */ jsx2("option", { value: 999999, children: "All" })
|
|
333
|
+
]
|
|
334
|
+
}
|
|
335
|
+
)
|
|
336
|
+
] }),
|
|
337
|
+
supportsBrush && /* @__PURE__ */ jsxs(
|
|
338
|
+
"button",
|
|
339
|
+
{
|
|
340
|
+
onClick: onToggleBrush,
|
|
341
|
+
className: `flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-sm transition-colors ${showBrush ? "border border-cyan-500/30 bg-cyan-500/20 text-cyan-400" : "bg-white/5 text-gray-400 hover:bg-white/10 hover:text-white"}`,
|
|
342
|
+
title: "Enable zoom/brush",
|
|
343
|
+
children: [
|
|
344
|
+
/* @__PURE__ */ jsx2(RotateCcw, { className: "h-4 w-4" }),
|
|
345
|
+
"Zoom"
|
|
346
|
+
]
|
|
347
|
+
}
|
|
348
|
+
),
|
|
349
|
+
supportsTrendline && /* @__PURE__ */ jsxs(
|
|
350
|
+
"button",
|
|
351
|
+
{
|
|
352
|
+
onClick: onToggleTrendline,
|
|
353
|
+
className: `flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-sm transition-colors ${showTrendline ? "border border-emerald-500/30 bg-emerald-500/20 text-emerald-400" : "bg-white/5 text-gray-400 hover:bg-white/10 hover:text-white"}`,
|
|
354
|
+
title: "Show average line",
|
|
355
|
+
children: [
|
|
356
|
+
/* @__PURE__ */ jsx2(TrendingUp, { className: "h-4 w-4" }),
|
|
357
|
+
"Average"
|
|
358
|
+
]
|
|
359
|
+
}
|
|
360
|
+
),
|
|
361
|
+
/* @__PURE__ */ jsx2("div", { className: "flex-1" }),
|
|
362
|
+
/* @__PURE__ */ jsxs(
|
|
363
|
+
"button",
|
|
364
|
+
{
|
|
365
|
+
onClick: onExportPNG,
|
|
366
|
+
className: "flex items-center gap-1.5 rounded-lg bg-white/5 px-3 py-1.5 text-sm text-gray-400 transition-colors hover:bg-white/10 hover:text-white",
|
|
367
|
+
title: "Export chart as PNG image",
|
|
368
|
+
children: [
|
|
369
|
+
/* @__PURE__ */ jsx2(Image2, { className: "h-4 w-4" }),
|
|
370
|
+
"PNG"
|
|
371
|
+
]
|
|
372
|
+
}
|
|
373
|
+
),
|
|
374
|
+
/* @__PURE__ */ jsxs(
|
|
375
|
+
"button",
|
|
376
|
+
{
|
|
377
|
+
onClick: onExportCSV,
|
|
378
|
+
className: "flex items-center gap-1.5 rounded-lg bg-white/5 px-3 py-1.5 text-sm text-gray-400 transition-colors hover:bg-white/10 hover:text-white",
|
|
379
|
+
title: "Export chart data as CSV",
|
|
380
|
+
children: [
|
|
381
|
+
/* @__PURE__ */ jsx2(Download, { className: "h-4 w-4" }),
|
|
382
|
+
"CSV"
|
|
383
|
+
]
|
|
384
|
+
}
|
|
385
|
+
),
|
|
386
|
+
hasRegenerate && /* @__PURE__ */ jsxs(
|
|
387
|
+
"button",
|
|
388
|
+
{
|
|
389
|
+
onClick: onRegenerate,
|
|
390
|
+
disabled: isRegenerating,
|
|
391
|
+
className: "flex items-center gap-1.5 rounded-lg bg-violet-500/20 px-3 py-1.5 text-sm text-violet-400 transition-colors hover:bg-violet-500/30 disabled:opacity-50",
|
|
392
|
+
children: [
|
|
393
|
+
/* @__PURE__ */ jsx2(
|
|
394
|
+
RefreshCw,
|
|
395
|
+
{
|
|
396
|
+
className: `h-4 w-4 ${isRegenerating ? "animate-spin" : ""}`
|
|
397
|
+
}
|
|
398
|
+
),
|
|
399
|
+
isRegenerating ? "..." : "Regenerate"
|
|
400
|
+
]
|
|
401
|
+
}
|
|
402
|
+
)
|
|
403
|
+
] });
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// src/SingleChart.tsx
|
|
407
|
+
import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
408
|
+
function SingleChart({ data, chart, onRegenerate }) {
|
|
409
|
+
const theme = useChartTheme();
|
|
410
|
+
const chartContainerRef = useRef(null);
|
|
411
|
+
const [isRegenerating, setIsRegenerating] = useState(false);
|
|
412
|
+
const [sortOrder, setSortOrder] = useState("none");
|
|
413
|
+
const [showBrush, setShowBrush] = useState(false);
|
|
414
|
+
const [showTrendline, setShowTrendline] = useState(false);
|
|
415
|
+
const [limitResults, setLimitResults] = useState(20);
|
|
416
|
+
const processed = useMemo(
|
|
417
|
+
() => processChartDataMultiSeries(data, chart, sortOrder, limitResults),
|
|
418
|
+
[data, chart, sortOrder, limitResults]
|
|
419
|
+
);
|
|
420
|
+
const { data: processedData, seriesKeys } = processed;
|
|
421
|
+
const isMultiSeries = seriesKeys.length > 0;
|
|
422
|
+
const xColName = data.columns.find(
|
|
423
|
+
(col) => col.name === chart.xAxis || col.name.toLowerCase() === chart.xAxis.toLowerCase()
|
|
424
|
+
)?.name ?? chart.xAxis;
|
|
425
|
+
const yColName = data.columns.find(
|
|
426
|
+
(col) => col.name === chart.yAxis || col.name.toLowerCase() === chart.yAxis.toLowerCase()
|
|
427
|
+
)?.name ?? chart.yAxis;
|
|
428
|
+
const average = useMemo(() => {
|
|
429
|
+
if (processedData.length === 0 || isMultiSeries) return 0;
|
|
430
|
+
const sum = processedData.reduce((acc, item) => {
|
|
431
|
+
const val = item[yColName];
|
|
432
|
+
return acc + (typeof val === "number" ? val : 0);
|
|
433
|
+
}, 0);
|
|
434
|
+
return sum / processedData.length;
|
|
435
|
+
}, [processedData, yColName, isMultiSeries]);
|
|
436
|
+
const handleRegenerate = async () => {
|
|
437
|
+
if (!onRegenerate) return;
|
|
438
|
+
setIsRegenerating(true);
|
|
439
|
+
try {
|
|
440
|
+
await onRegenerate(chart);
|
|
441
|
+
} finally {
|
|
442
|
+
setIsRegenerating(false);
|
|
443
|
+
}
|
|
444
|
+
};
|
|
445
|
+
const handleExportCSV = useCallback(() => {
|
|
446
|
+
if (processedData.length === 0) return;
|
|
447
|
+
const headers = Object.keys(processedData[0] ?? {}).join(",");
|
|
448
|
+
const rows = processedData.map(
|
|
449
|
+
(row) => Object.values(row).map((v) => `"${String(v)}"`).join(",")
|
|
450
|
+
).join("\n");
|
|
451
|
+
const csv = `${headers}
|
|
452
|
+
${rows}`;
|
|
453
|
+
const blob = new Blob([csv], { type: "text/csv" });
|
|
454
|
+
const url = URL.createObjectURL(blob);
|
|
455
|
+
const a = document.createElement("a");
|
|
456
|
+
a.href = url;
|
|
457
|
+
a.download = `${chart.title.replace(/\s+/g, "_")}.csv`;
|
|
458
|
+
a.click();
|
|
459
|
+
URL.revokeObjectURL(url);
|
|
460
|
+
}, [processedData, chart.title]);
|
|
461
|
+
const handleExportPNG = useCallback(() => {
|
|
462
|
+
const container = chartContainerRef.current;
|
|
463
|
+
if (!container) return;
|
|
464
|
+
const svgElement = container.querySelector("svg");
|
|
465
|
+
if (!svgElement) return;
|
|
466
|
+
const svgClone = svgElement.cloneNode(true);
|
|
467
|
+
svgClone.setAttribute(
|
|
468
|
+
"xmlns",
|
|
469
|
+
"http://www.w3.org/2000/svg"
|
|
470
|
+
);
|
|
471
|
+
const rect = svgElement.getBoundingClientRect();
|
|
472
|
+
svgClone.setAttribute("width", String(rect.width));
|
|
473
|
+
svgClone.setAttribute("height", String(rect.height));
|
|
474
|
+
const svgString = new XMLSerializer().serializeToString(svgClone);
|
|
475
|
+
const svgBlob = new Blob([svgString], {
|
|
476
|
+
type: "image/svg+xml;charset=utf-8"
|
|
477
|
+
});
|
|
478
|
+
const url = URL.createObjectURL(svgBlob);
|
|
479
|
+
const img = new Image();
|
|
480
|
+
img.onload = () => {
|
|
481
|
+
const canvas = document.createElement("canvas");
|
|
482
|
+
const scale = 2;
|
|
483
|
+
canvas.width = rect.width * scale;
|
|
484
|
+
canvas.height = rect.height * scale;
|
|
485
|
+
const ctx = canvas.getContext("2d");
|
|
486
|
+
if (!ctx) return;
|
|
487
|
+
ctx.scale(scale, scale);
|
|
488
|
+
ctx.fillStyle = theme.tooltipBackground;
|
|
489
|
+
ctx.fillRect(0, 0, rect.width, rect.height);
|
|
490
|
+
ctx.drawImage(img, 0, 0, rect.width, rect.height);
|
|
491
|
+
canvas.toBlob((blob) => {
|
|
492
|
+
if (!blob) return;
|
|
493
|
+
const pngUrl = URL.createObjectURL(blob);
|
|
494
|
+
const a = document.createElement("a");
|
|
495
|
+
a.href = pngUrl;
|
|
496
|
+
a.download = `${chart.title.replace(/\s+/g, "_")}.png`;
|
|
497
|
+
a.click();
|
|
498
|
+
URL.revokeObjectURL(pngUrl);
|
|
499
|
+
}, "image/png");
|
|
500
|
+
URL.revokeObjectURL(url);
|
|
501
|
+
};
|
|
502
|
+
img.src = url;
|
|
503
|
+
}, [chart.title, theme.tooltipBackground]);
|
|
504
|
+
const toggleSort = () => {
|
|
505
|
+
setSortOrder((prev) => {
|
|
506
|
+
if (prev === "none") return "desc";
|
|
507
|
+
if (prev === "desc") return "asc";
|
|
508
|
+
return "none";
|
|
509
|
+
});
|
|
510
|
+
};
|
|
511
|
+
const renderChartContent = (type, chartData, xKey, yKey, enableBrush, enableTrendline, avgValue) => {
|
|
512
|
+
const commonProps = {
|
|
513
|
+
data: chartData,
|
|
514
|
+
margin: { top: 20, right: 30, left: 20, bottom: 50 }
|
|
515
|
+
};
|
|
516
|
+
const tooltipStyle = {
|
|
517
|
+
backgroundColor: theme.tooltipBackground,
|
|
518
|
+
border: theme.tooltipBorder,
|
|
519
|
+
borderRadius: "8px"
|
|
520
|
+
};
|
|
521
|
+
const brushComponent = enableBrush ? /* @__PURE__ */ jsx3(
|
|
522
|
+
Brush,
|
|
523
|
+
{
|
|
524
|
+
dataKey: xKey,
|
|
525
|
+
height: 30,
|
|
526
|
+
stroke: theme.accentPrimary,
|
|
527
|
+
fill: "#1e1b4b"
|
|
528
|
+
}
|
|
529
|
+
) : null;
|
|
530
|
+
const trendlineComponent = enableTrendline && avgValue && !isMultiSeries ? /* @__PURE__ */ jsx3(
|
|
531
|
+
ReferenceLine,
|
|
532
|
+
{
|
|
533
|
+
y: avgValue,
|
|
534
|
+
stroke: theme.accentSuccess,
|
|
535
|
+
strokeDasharray: "5 5",
|
|
536
|
+
label: {
|
|
537
|
+
value: `Avg: ${avgValue.toFixed(2)}`,
|
|
538
|
+
fill: theme.accentSuccess,
|
|
539
|
+
fontSize: 12
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
) : null;
|
|
543
|
+
const xAxisProps = {
|
|
544
|
+
dataKey: xKey,
|
|
545
|
+
stroke: theme.textMuted,
|
|
546
|
+
fontSize: 11,
|
|
547
|
+
angle: -45,
|
|
548
|
+
textAnchor: "end",
|
|
549
|
+
height: 80,
|
|
550
|
+
interval: 0,
|
|
551
|
+
tick: { fill: theme.textMuted }
|
|
552
|
+
};
|
|
553
|
+
const yAxisProps = {
|
|
554
|
+
stroke: theme.textMuted,
|
|
555
|
+
fontSize: 12,
|
|
556
|
+
tick: { fill: theme.textMuted }
|
|
557
|
+
};
|
|
558
|
+
if (isMultiSeries && type !== "pie" && type !== "scatter") {
|
|
559
|
+
switch (type) {
|
|
560
|
+
case "bar":
|
|
561
|
+
return /* @__PURE__ */ jsxs2(BarChart, { ...commonProps, children: [
|
|
562
|
+
/* @__PURE__ */ jsx3(CartesianGrid, { strokeDasharray: "3 3", stroke: theme.gridStroke }),
|
|
563
|
+
/* @__PURE__ */ jsx3(XAxis, { ...xAxisProps }),
|
|
564
|
+
/* @__PURE__ */ jsx3(YAxis, { ...yAxisProps }),
|
|
565
|
+
/* @__PURE__ */ jsx3(Tooltip, { contentStyle: tooltipStyle }),
|
|
566
|
+
/* @__PURE__ */ jsx3(Legend, {}),
|
|
567
|
+
seriesKeys.map((key, i) => /* @__PURE__ */ jsx3(
|
|
568
|
+
Bar,
|
|
569
|
+
{
|
|
570
|
+
dataKey: key,
|
|
571
|
+
fill: theme.colors[i % theme.colors.length],
|
|
572
|
+
stackId: "stack",
|
|
573
|
+
radius: i === seriesKeys.length - 1 ? [4, 4, 0, 0] : void 0
|
|
574
|
+
},
|
|
575
|
+
key
|
|
576
|
+
)),
|
|
577
|
+
brushComponent
|
|
578
|
+
] });
|
|
579
|
+
case "line":
|
|
580
|
+
return /* @__PURE__ */ jsxs2(LineChart, { ...commonProps, children: [
|
|
581
|
+
/* @__PURE__ */ jsx3(CartesianGrid, { strokeDasharray: "3 3", stroke: theme.gridStroke }),
|
|
582
|
+
/* @__PURE__ */ jsx3(XAxis, { ...xAxisProps }),
|
|
583
|
+
/* @__PURE__ */ jsx3(YAxis, { ...yAxisProps }),
|
|
584
|
+
/* @__PURE__ */ jsx3(Tooltip, { contentStyle: tooltipStyle }),
|
|
585
|
+
/* @__PURE__ */ jsx3(Legend, {}),
|
|
586
|
+
seriesKeys.map((key, i) => /* @__PURE__ */ jsx3(
|
|
587
|
+
Line,
|
|
588
|
+
{
|
|
589
|
+
type: "monotone",
|
|
590
|
+
dataKey: key,
|
|
591
|
+
stroke: theme.colors[i % theme.colors.length],
|
|
592
|
+
strokeWidth: 2,
|
|
593
|
+
dot: { fill: theme.colors[i % theme.colors.length] }
|
|
594
|
+
},
|
|
595
|
+
key
|
|
596
|
+
)),
|
|
597
|
+
brushComponent
|
|
598
|
+
] });
|
|
599
|
+
case "area":
|
|
600
|
+
return /* @__PURE__ */ jsxs2(AreaChart, { ...commonProps, children: [
|
|
601
|
+
/* @__PURE__ */ jsx3("defs", { children: seriesKeys.map((key, i) => /* @__PURE__ */ jsxs2(
|
|
602
|
+
"linearGradient",
|
|
603
|
+
{
|
|
604
|
+
id: `grad-${chart.id}-${i}`,
|
|
605
|
+
x1: "0",
|
|
606
|
+
y1: "0",
|
|
607
|
+
x2: "0",
|
|
608
|
+
y2: "1",
|
|
609
|
+
children: [
|
|
610
|
+
/* @__PURE__ */ jsx3(
|
|
611
|
+
"stop",
|
|
612
|
+
{
|
|
613
|
+
offset: "5%",
|
|
614
|
+
stopColor: theme.colors[i % theme.colors.length],
|
|
615
|
+
stopOpacity: 0.6
|
|
616
|
+
}
|
|
617
|
+
),
|
|
618
|
+
/* @__PURE__ */ jsx3(
|
|
619
|
+
"stop",
|
|
620
|
+
{
|
|
621
|
+
offset: "95%",
|
|
622
|
+
stopColor: theme.colors[i % theme.colors.length],
|
|
623
|
+
stopOpacity: 0
|
|
624
|
+
}
|
|
625
|
+
)
|
|
626
|
+
]
|
|
627
|
+
},
|
|
628
|
+
key
|
|
629
|
+
)) }),
|
|
630
|
+
/* @__PURE__ */ jsx3(CartesianGrid, { strokeDasharray: "3 3", stroke: theme.gridStroke }),
|
|
631
|
+
/* @__PURE__ */ jsx3(XAxis, { ...xAxisProps }),
|
|
632
|
+
/* @__PURE__ */ jsx3(YAxis, { ...yAxisProps }),
|
|
633
|
+
/* @__PURE__ */ jsx3(Tooltip, { contentStyle: tooltipStyle }),
|
|
634
|
+
/* @__PURE__ */ jsx3(Legend, {}),
|
|
635
|
+
seriesKeys.map((key, i) => /* @__PURE__ */ jsx3(
|
|
636
|
+
Area,
|
|
637
|
+
{
|
|
638
|
+
type: "monotone",
|
|
639
|
+
dataKey: key,
|
|
640
|
+
stroke: theme.colors[i % theme.colors.length],
|
|
641
|
+
fillOpacity: 1,
|
|
642
|
+
fill: `url(#grad-${chart.id}-${i})`,
|
|
643
|
+
stackId: "stack"
|
|
644
|
+
},
|
|
645
|
+
key
|
|
646
|
+
)),
|
|
647
|
+
brushComponent
|
|
648
|
+
] });
|
|
649
|
+
default:
|
|
650
|
+
return null;
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
switch (type) {
|
|
654
|
+
case "bar":
|
|
655
|
+
return /* @__PURE__ */ jsxs2(BarChart, { ...commonProps, children: [
|
|
656
|
+
/* @__PURE__ */ jsx3(CartesianGrid, { strokeDasharray: "3 3", stroke: theme.gridStroke }),
|
|
657
|
+
/* @__PURE__ */ jsx3(XAxis, { ...xAxisProps }),
|
|
658
|
+
/* @__PURE__ */ jsx3(YAxis, { ...yAxisProps }),
|
|
659
|
+
/* @__PURE__ */ jsx3(Tooltip, { contentStyle: tooltipStyle }),
|
|
660
|
+
/* @__PURE__ */ jsx3(Legend, {}),
|
|
661
|
+
trendlineComponent,
|
|
662
|
+
/* @__PURE__ */ jsx3(Bar, { dataKey: yKey, fill: theme.accentPrimary, radius: [4, 4, 0, 0], children: chartData.map((_, index) => /* @__PURE__ */ jsx3(
|
|
663
|
+
Cell,
|
|
664
|
+
{
|
|
665
|
+
fill: theme.colors[index % theme.colors.length]
|
|
666
|
+
},
|
|
667
|
+
`cell-${index}`
|
|
668
|
+
)) }),
|
|
669
|
+
brushComponent
|
|
670
|
+
] });
|
|
671
|
+
case "line":
|
|
672
|
+
return /* @__PURE__ */ jsxs2(LineChart, { ...commonProps, children: [
|
|
673
|
+
/* @__PURE__ */ jsx3(CartesianGrid, { strokeDasharray: "3 3", stroke: theme.gridStroke }),
|
|
674
|
+
/* @__PURE__ */ jsx3(XAxis, { ...xAxisProps }),
|
|
675
|
+
/* @__PURE__ */ jsx3(YAxis, { ...yAxisProps }),
|
|
676
|
+
/* @__PURE__ */ jsx3(Tooltip, { contentStyle: tooltipStyle }),
|
|
677
|
+
/* @__PURE__ */ jsx3(Legend, {}),
|
|
678
|
+
trendlineComponent,
|
|
679
|
+
/* @__PURE__ */ jsx3(
|
|
680
|
+
Line,
|
|
681
|
+
{
|
|
682
|
+
type: "monotone",
|
|
683
|
+
dataKey: yKey,
|
|
684
|
+
stroke: theme.accentPrimary,
|
|
685
|
+
strokeWidth: 3,
|
|
686
|
+
dot: { fill: theme.accentPrimary },
|
|
687
|
+
activeDot: { r: 8 }
|
|
688
|
+
}
|
|
689
|
+
),
|
|
690
|
+
brushComponent
|
|
691
|
+
] });
|
|
692
|
+
case "area": {
|
|
693
|
+
const gradientId = `colorY-${chart.id}`;
|
|
694
|
+
return /* @__PURE__ */ jsxs2(AreaChart, { ...commonProps, children: [
|
|
695
|
+
/* @__PURE__ */ jsx3("defs", { children: /* @__PURE__ */ jsxs2("linearGradient", { id: gradientId, x1: "0", y1: "0", x2: "0", y2: "1", children: [
|
|
696
|
+
/* @__PURE__ */ jsx3(
|
|
697
|
+
"stop",
|
|
698
|
+
{
|
|
699
|
+
offset: "5%",
|
|
700
|
+
stopColor: theme.accentPrimary,
|
|
701
|
+
stopOpacity: 0.8
|
|
702
|
+
}
|
|
703
|
+
),
|
|
704
|
+
/* @__PURE__ */ jsx3(
|
|
705
|
+
"stop",
|
|
706
|
+
{
|
|
707
|
+
offset: "95%",
|
|
708
|
+
stopColor: theme.accentPrimary,
|
|
709
|
+
stopOpacity: 0
|
|
710
|
+
}
|
|
711
|
+
)
|
|
712
|
+
] }) }),
|
|
713
|
+
/* @__PURE__ */ jsx3(CartesianGrid, { strokeDasharray: "3 3", stroke: theme.gridStroke }),
|
|
714
|
+
/* @__PURE__ */ jsx3(XAxis, { ...xAxisProps }),
|
|
715
|
+
/* @__PURE__ */ jsx3(YAxis, { ...yAxisProps }),
|
|
716
|
+
/* @__PURE__ */ jsx3(Tooltip, { contentStyle: tooltipStyle }),
|
|
717
|
+
/* @__PURE__ */ jsx3(Legend, {}),
|
|
718
|
+
trendlineComponent,
|
|
719
|
+
/* @__PURE__ */ jsx3(
|
|
720
|
+
Area,
|
|
721
|
+
{
|
|
722
|
+
type: "monotone",
|
|
723
|
+
dataKey: yKey,
|
|
724
|
+
stroke: theme.accentPrimary,
|
|
725
|
+
fillOpacity: 1,
|
|
726
|
+
fill: `url(#${gradientId})`
|
|
727
|
+
}
|
|
728
|
+
),
|
|
729
|
+
brushComponent
|
|
730
|
+
] });
|
|
731
|
+
}
|
|
732
|
+
case "scatter":
|
|
733
|
+
return /* @__PURE__ */ jsxs2(ScatterChart, { ...commonProps, children: [
|
|
734
|
+
/* @__PURE__ */ jsx3(CartesianGrid, { strokeDasharray: "3 3", stroke: theme.gridStroke }),
|
|
735
|
+
/* @__PURE__ */ jsx3(
|
|
736
|
+
XAxis,
|
|
737
|
+
{
|
|
738
|
+
type: "category",
|
|
739
|
+
...xAxisProps
|
|
740
|
+
}
|
|
741
|
+
),
|
|
742
|
+
/* @__PURE__ */ jsx3(
|
|
743
|
+
YAxis,
|
|
744
|
+
{
|
|
745
|
+
type: "number",
|
|
746
|
+
dataKey: yKey,
|
|
747
|
+
...yAxisProps
|
|
748
|
+
}
|
|
749
|
+
),
|
|
750
|
+
/* @__PURE__ */ jsx3(
|
|
751
|
+
Tooltip,
|
|
752
|
+
{
|
|
753
|
+
cursor: { strokeDasharray: "3 3" },
|
|
754
|
+
contentStyle: tooltipStyle
|
|
755
|
+
}
|
|
756
|
+
),
|
|
757
|
+
/* @__PURE__ */ jsx3(Legend, {}),
|
|
758
|
+
/* @__PURE__ */ jsx3(Scatter, { name: yKey, data: chartData, fill: theme.accentPrimary })
|
|
759
|
+
] });
|
|
760
|
+
case "pie":
|
|
761
|
+
return /* @__PURE__ */ jsxs2(PieChart, { children: [
|
|
762
|
+
/* @__PURE__ */ jsx3(
|
|
763
|
+
Pie,
|
|
764
|
+
{
|
|
765
|
+
data: chartData,
|
|
766
|
+
innerRadius: 60,
|
|
767
|
+
outerRadius: 100,
|
|
768
|
+
paddingAngle: 5,
|
|
769
|
+
dataKey: yKey,
|
|
770
|
+
nameKey: xKey,
|
|
771
|
+
label: ({ name, percent }) => `${name}: ${((percent ?? 0) * 100).toFixed(0)}%`,
|
|
772
|
+
labelLine: { stroke: theme.textMuted },
|
|
773
|
+
children: chartData.map((_, index) => /* @__PURE__ */ jsx3(
|
|
774
|
+
Cell,
|
|
775
|
+
{
|
|
776
|
+
fill: theme.colors[index % theme.colors.length]
|
|
777
|
+
},
|
|
778
|
+
`cell-${index}`
|
|
779
|
+
))
|
|
780
|
+
}
|
|
781
|
+
),
|
|
782
|
+
/* @__PURE__ */ jsx3(Tooltip, { contentStyle: tooltipStyle }),
|
|
783
|
+
/* @__PURE__ */ jsx3(Legend, {})
|
|
784
|
+
] });
|
|
785
|
+
default:
|
|
786
|
+
return null;
|
|
787
|
+
}
|
|
788
|
+
};
|
|
789
|
+
if (processedData.length === 0) {
|
|
790
|
+
return /* @__PURE__ */ jsx3("div", { className: "flex h-[300px] flex-col items-center justify-center gap-4 text-gray-400", children: /* @__PURE__ */ jsxs2("div", { className: "text-center", children: [
|
|
791
|
+
/* @__PURE__ */ jsx3("p", { className: "mb-1 font-medium text-red-400", children: "Unable to generate this chart" }),
|
|
792
|
+
/* @__PURE__ */ jsxs2("p", { className: "mb-4 text-sm text-gray-500", children: [
|
|
793
|
+
"Columns: ",
|
|
794
|
+
chart.xAxis,
|
|
795
|
+
", ",
|
|
796
|
+
chart.yAxis
|
|
797
|
+
] }),
|
|
798
|
+
onRegenerate && /* @__PURE__ */ jsxs2(
|
|
799
|
+
"button",
|
|
800
|
+
{
|
|
801
|
+
onClick: handleRegenerate,
|
|
802
|
+
disabled: isRegenerating,
|
|
803
|
+
className: "flex items-center gap-2 rounded-lg bg-violet-600 px-4 py-2 text-sm text-white transition-colors hover:bg-violet-500 disabled:opacity-50",
|
|
804
|
+
children: [
|
|
805
|
+
/* @__PURE__ */ jsx3(
|
|
806
|
+
RefreshCw2,
|
|
807
|
+
{
|
|
808
|
+
className: `h-4 w-4 ${isRegenerating ? "animate-spin" : ""}`
|
|
809
|
+
}
|
|
810
|
+
),
|
|
811
|
+
isRegenerating ? "Regenerating..." : "Regenerate with AI"
|
|
812
|
+
]
|
|
813
|
+
}
|
|
814
|
+
)
|
|
815
|
+
] }) });
|
|
816
|
+
}
|
|
817
|
+
return /* @__PURE__ */ jsxs2("div", { children: [
|
|
818
|
+
/* @__PURE__ */ jsx3(
|
|
819
|
+
ChartToolbar,
|
|
820
|
+
{
|
|
821
|
+
chartType: chart.type,
|
|
822
|
+
sortOrder,
|
|
823
|
+
limitResults,
|
|
824
|
+
showBrush,
|
|
825
|
+
showTrendline,
|
|
826
|
+
isRegenerating,
|
|
827
|
+
hasRegenerate: !!onRegenerate,
|
|
828
|
+
onToggleSort: toggleSort,
|
|
829
|
+
onLimitChange: setLimitResults,
|
|
830
|
+
onToggleBrush: () => setShowBrush(!showBrush),
|
|
831
|
+
onToggleTrendline: () => setShowTrendline(!showTrendline),
|
|
832
|
+
onExportCSV: handleExportCSV,
|
|
833
|
+
onExportPNG: handleExportPNG,
|
|
834
|
+
onRegenerate: handleRegenerate
|
|
835
|
+
}
|
|
836
|
+
),
|
|
837
|
+
/* @__PURE__ */ jsx3("div", { className: "h-[450px] w-full", ref: chartContainerRef, children: /* @__PURE__ */ jsx3(
|
|
838
|
+
ResponsiveContainer,
|
|
839
|
+
{
|
|
840
|
+
width: "100%",
|
|
841
|
+
height: "100%",
|
|
842
|
+
children: renderChartContent(
|
|
843
|
+
chart.type,
|
|
844
|
+
processedData,
|
|
845
|
+
xColName,
|
|
846
|
+
isMultiSeries ? "" : yColName,
|
|
847
|
+
showBrush,
|
|
848
|
+
showTrendline,
|
|
849
|
+
average
|
|
850
|
+
)
|
|
851
|
+
},
|
|
852
|
+
`chart-${chart.id}-${showBrush ? "brush" : "no-brush"}`
|
|
853
|
+
) }),
|
|
854
|
+
/* @__PURE__ */ jsxs2("div", { className: "mt-4 flex flex-wrap gap-2", children: [
|
|
855
|
+
/* @__PURE__ */ jsxs2("span", { className: "rounded bg-white/10 px-2 py-0.5 text-xs text-gray-400", children: [
|
|
856
|
+
"X: ",
|
|
857
|
+
chart.xAxis
|
|
858
|
+
] }),
|
|
859
|
+
/* @__PURE__ */ jsxs2("span", { className: "rounded bg-white/10 px-2 py-0.5 text-xs text-gray-400", children: [
|
|
860
|
+
"Y: ",
|
|
861
|
+
chart.yAxis
|
|
862
|
+
] }),
|
|
863
|
+
chart.groupBy && /* @__PURE__ */ jsxs2("span", { className: "rounded bg-cyan-500/20 px-2 py-0.5 text-xs text-cyan-300", children: [
|
|
864
|
+
"Group: ",
|
|
865
|
+
chart.groupBy
|
|
866
|
+
] }),
|
|
867
|
+
chart.aggregation && chart.aggregation !== "none" && /* @__PURE__ */ jsx3("span", { className: "rounded bg-violet-500/20 px-2 py-0.5 text-xs text-violet-300", children: chart.aggregation }),
|
|
868
|
+
/* @__PURE__ */ jsxs2("span", { className: "rounded bg-white/10 px-2 py-0.5 text-xs text-gray-400", children: [
|
|
869
|
+
processedData.length,
|
|
870
|
+
" items"
|
|
871
|
+
] }),
|
|
872
|
+
isMultiSeries && /* @__PURE__ */ jsxs2("span", { className: "rounded bg-emerald-500/20 px-2 py-0.5 text-xs text-emerald-300", children: [
|
|
873
|
+
seriesKeys.length,
|
|
874
|
+
" series"
|
|
875
|
+
] })
|
|
876
|
+
] })
|
|
877
|
+
] });
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
// src/ChartDisplay.tsx
|
|
881
|
+
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
882
|
+
function DefaultCard({
|
|
883
|
+
children,
|
|
884
|
+
className = ""
|
|
885
|
+
}) {
|
|
886
|
+
return /* @__PURE__ */ jsx4("div", { className, children });
|
|
887
|
+
}
|
|
888
|
+
function ChartDisplay({
|
|
889
|
+
data,
|
|
890
|
+
charts,
|
|
891
|
+
onRegenerate,
|
|
892
|
+
cardWrapper: CardWrapper = DefaultCard,
|
|
893
|
+
theme = defaultDarkTheme
|
|
894
|
+
}) {
|
|
895
|
+
if (charts.length === 0) return null;
|
|
896
|
+
return /* @__PURE__ */ jsx4(ChartThemeProvider, { theme, children: /* @__PURE__ */ jsx4("div", { className: "animate-fade-in space-y-6", children: charts.map((chart) => /* @__PURE__ */ jsx4(
|
|
897
|
+
CardWrapper,
|
|
898
|
+
{
|
|
899
|
+
title: chart.title,
|
|
900
|
+
className: "overflow-hidden rounded-2xl border border-white/10 bg-slate-900/50",
|
|
901
|
+
children: /* @__PURE__ */ jsxs3("div", { className: "p-6", children: [
|
|
902
|
+
/* @__PURE__ */ jsx4("h3", { className: "mb-2 bg-linear-to-r from-violet-400 to-fuchsia-400 bg-clip-text text-xl font-bold text-transparent", children: chart.title }),
|
|
903
|
+
/* @__PURE__ */ jsx4("p", { className: "mb-4 text-gray-400", children: chart.description }),
|
|
904
|
+
/* @__PURE__ */ jsx4(
|
|
905
|
+
SingleChart,
|
|
906
|
+
{
|
|
907
|
+
data,
|
|
908
|
+
chart,
|
|
909
|
+
onRegenerate
|
|
910
|
+
}
|
|
911
|
+
)
|
|
912
|
+
] })
|
|
913
|
+
},
|
|
914
|
+
chart.id
|
|
915
|
+
)) }) });
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
// src/constants.ts
|
|
919
|
+
var COLORS = [
|
|
920
|
+
"#8b5cf6",
|
|
921
|
+
// Violet 500
|
|
922
|
+
"#06b6d4",
|
|
923
|
+
// Cyan 500
|
|
924
|
+
"#f43f5e",
|
|
925
|
+
// Rose 500
|
|
926
|
+
"#eab308",
|
|
927
|
+
// Yellow 500
|
|
928
|
+
"#10b981",
|
|
929
|
+
// Emerald 500
|
|
930
|
+
"#3b82f6",
|
|
931
|
+
// Blue 500
|
|
932
|
+
"#d946ef",
|
|
933
|
+
// Fuchsia 500
|
|
934
|
+
"#f97316"
|
|
935
|
+
// Orange 500
|
|
936
|
+
];
|
|
937
|
+
export {
|
|
938
|
+
COLORS,
|
|
939
|
+
ChartDisplay,
|
|
940
|
+
ChartThemeProvider,
|
|
941
|
+
ChartToolbar,
|
|
942
|
+
SingleChart,
|
|
943
|
+
defaultDarkTheme,
|
|
944
|
+
defaultLightTheme,
|
|
945
|
+
processChartData,
|
|
946
|
+
processChartDataMultiSeries,
|
|
947
|
+
useChartTheme
|
|
948
|
+
};
|
|
949
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/types.ts","../src/ThemeContext.tsx","../src/SingleChart.tsx","../src/processChartData.ts","../src/ChartToolbar.tsx","../src/ChartDisplay.tsx","../src/constants.ts"],"sourcesContent":["export type ChartType = \"bar\" | \"line\" | \"pie\" | \"scatter\" | \"area\";\nexport type AggregationType =\n | \"sum\"\n | \"avg\"\n | \"count\"\n | \"min\"\n | \"max\"\n | \"none\";\n\nexport interface TabularData {\n headers: string[];\n rows: string[][];\n columns: {\n name: string;\n type: \"string\" | \"number\" | \"date\" | \"boolean\";\n index: number;\n }[];\n rowCount: number;\n}\n\nexport interface ChartConfig {\n id: string;\n type: ChartType;\n title: string;\n description: string;\n xAxis: string;\n yAxis: string;\n groupBy?: string;\n aggregation: AggregationType;\n dataConfig?: {\n xColumn: string;\n yColumn: string;\n groupColumn?: string;\n };\n}\n\nexport type SortOrder = \"none\" | \"asc\" | \"desc\";\nexport type ChartDataPoint = Record<string, string | number>;\n\n// Theme types\nexport interface ChartTheme {\n colors: string[];\n background: string;\n cardBackground: string;\n border: string;\n text: string;\n textMuted: string;\n textDimmed: string;\n gridStroke: string;\n tooltipBackground: string;\n tooltipBorder: string;\n accentPrimary: string;\n accentSecondary: string;\n accentSuccess: string;\n accentDanger: string;\n}\n\nexport const defaultDarkTheme: ChartTheme = {\n colors: [\n \"#8b5cf6\",\n \"#06b6d4\",\n \"#f43f5e\",\n \"#eab308\",\n \"#10b981\",\n \"#3b82f6\",\n \"#d946ef\",\n \"#f97316\",\n ],\n background: \"rgba(15, 23, 42, 0.5)\",\n cardBackground: \"rgba(255, 255, 255, 0.05)\",\n border: \"rgba(255, 255, 255, 0.1)\",\n text: \"#ffffff\",\n textMuted: \"#9ca3af\",\n textDimmed: \"#6b7280\",\n gridStroke: \"#374151\",\n tooltipBackground: \"#1f2937\",\n tooltipBorder: \"1px solid #374151\",\n accentPrimary: \"#8b5cf6\",\n accentSecondary: \"#06b6d4\",\n accentSuccess: \"#10b981\",\n accentDanger: \"#ef4444\",\n};\n\nexport const defaultLightTheme: ChartTheme = {\n colors: [\n \"#7c3aed\",\n \"#0891b2\",\n \"#e11d48\",\n \"#ca8a04\",\n \"#059669\",\n \"#2563eb\",\n \"#c026d3\",\n \"#ea580c\",\n ],\n background: \"#ffffff\",\n cardBackground: \"#f8fafc\",\n border: \"#e2e8f0\",\n text: \"#0f172a\",\n textMuted: \"#64748b\",\n textDimmed: \"#94a3b8\",\n gridStroke: \"#e2e8f0\",\n tooltipBackground: \"#ffffff\",\n tooltipBorder: \"1px solid #e2e8f0\",\n accentPrimary: \"#7c3aed\",\n accentSecondary: \"#0891b2\",\n accentSuccess: \"#059669\",\n accentDanger: \"#dc2626\",\n};\n","import { createContext, useContext } from \"react\";\nimport { type ChartTheme, defaultDarkTheme } from \"./types\";\n\nconst ChartThemeContext = createContext<ChartTheme>(defaultDarkTheme);\n\nexport function ChartThemeProvider({\n theme,\n children,\n}: {\n theme: ChartTheme;\n children: React.ReactNode;\n}) {\n return (\n <ChartThemeContext.Provider value={theme}>\n {children}\n </ChartThemeContext.Provider>\n );\n}\n\nexport function useChartTheme(): ChartTheme {\n return useContext(ChartThemeContext);\n}\n","import { useState, useMemo, useCallback, useRef } from \"react\";\nimport {\n BarChart,\n Bar,\n LineChart,\n Line,\n PieChart,\n Pie,\n ScatterChart,\n Scatter,\n AreaChart,\n Area,\n XAxis,\n YAxis,\n CartesianGrid,\n Tooltip,\n Legend,\n ResponsiveContainer,\n Cell,\n Brush,\n ReferenceLine,\n} from \"recharts\";\nimport { RefreshCw } from \"lucide-react\";\nimport type {\n TabularData,\n ChartConfig,\n ChartType,\n SortOrder,\n ChartDataPoint,\n} from \"./types\";\nimport { useChartTheme } from \"./ThemeContext\";\nimport { processChartDataMultiSeries } from \"./processChartData\";\nimport { ChartToolbar } from \"./ChartToolbar\";\n\nexport interface SingleChartProps {\n data: TabularData;\n chart: ChartConfig;\n onRegenerate?: (chart: ChartConfig) => Promise<void>;\n}\n\nexport function SingleChart({ data, chart, onRegenerate }: SingleChartProps) {\n const theme = useChartTheme();\n const chartContainerRef = useRef<HTMLDivElement>(null);\n const [isRegenerating, setIsRegenerating] = useState(false);\n const [sortOrder, setSortOrder] = useState<SortOrder>(\"none\");\n const [showBrush, setShowBrush] = useState(false);\n const [showTrendline, setShowTrendline] = useState(false);\n const [limitResults, setLimitResults] = useState<number>(20);\n\n const processed = useMemo(\n () => processChartDataMultiSeries(data, chart, sortOrder, limitResults),\n [data, chart, sortOrder, limitResults],\n );\n\n const { data: processedData, seriesKeys } = processed;\n const isMultiSeries = seriesKeys.length > 0;\n\n // Find actual column names from data\n const xColName =\n data.columns.find(\n (col) =>\n col.name === chart.xAxis ||\n col.name.toLowerCase() === chart.xAxis.toLowerCase(),\n )?.name ?? chart.xAxis;\n\n const yColName =\n data.columns.find(\n (col) =>\n col.name === chart.yAxis ||\n col.name.toLowerCase() === chart.yAxis.toLowerCase(),\n )?.name ?? chart.yAxis;\n\n // Calculate average for trend line\n const average = useMemo(() => {\n if (processedData.length === 0 || isMultiSeries) return 0;\n const sum = processedData.reduce((acc, item) => {\n const val = item[yColName];\n return acc + (typeof val === \"number\" ? val : 0);\n }, 0);\n return sum / processedData.length;\n }, [processedData, yColName, isMultiSeries]);\n\n const handleRegenerate = async () => {\n if (!onRegenerate) return;\n setIsRegenerating(true);\n try {\n await onRegenerate(chart);\n } finally {\n setIsRegenerating(false);\n }\n };\n\n const handleExportCSV = useCallback(() => {\n if (processedData.length === 0) return;\n\n const headers = Object.keys(processedData[0] ?? {}).join(\",\");\n const rows = processedData\n .map((row) =>\n Object.values(row)\n .map((v) => `\"${String(v)}\"`)\n .join(\",\"),\n )\n .join(\"\\n\");\n\n const csv = `${headers}\\n${rows}`;\n const blob = new Blob([csv], { type: \"text/csv\" });\n const url = URL.createObjectURL(blob);\n const a = document.createElement(\"a\");\n a.href = url;\n a.download = `${chart.title.replace(/\\s+/g, \"_\")}.csv`;\n a.click();\n URL.revokeObjectURL(url);\n }, [processedData, chart.title]);\n\n const handleExportPNG = useCallback(() => {\n const container = chartContainerRef.current;\n if (!container) return;\n\n const svgElement = container.querySelector(\"svg\");\n if (!svgElement) return;\n\n const svgClone = svgElement.cloneNode(true) as SVGSVGElement;\n svgClone.setAttribute(\n \"xmlns\",\n \"http://www.w3.org/2000/svg\",\n );\n // Ensure dimensions\n const rect = svgElement.getBoundingClientRect();\n svgClone.setAttribute(\"width\", String(rect.width));\n svgClone.setAttribute(\"height\", String(rect.height));\n\n const svgString = new XMLSerializer().serializeToString(svgClone);\n const svgBlob = new Blob([svgString], {\n type: \"image/svg+xml;charset=utf-8\",\n });\n const url = URL.createObjectURL(svgBlob);\n\n const img = new Image();\n img.onload = () => {\n const canvas = document.createElement(\"canvas\");\n const scale = 2; // retina\n canvas.width = rect.width * scale;\n canvas.height = rect.height * scale;\n const ctx = canvas.getContext(\"2d\");\n if (!ctx) return;\n ctx.scale(scale, scale);\n ctx.fillStyle = theme.tooltipBackground;\n ctx.fillRect(0, 0, rect.width, rect.height);\n ctx.drawImage(img, 0, 0, rect.width, rect.height);\n\n canvas.toBlob((blob) => {\n if (!blob) return;\n const pngUrl = URL.createObjectURL(blob);\n const a = document.createElement(\"a\");\n a.href = pngUrl;\n a.download = `${chart.title.replace(/\\s+/g, \"_\")}.png`;\n a.click();\n URL.revokeObjectURL(pngUrl);\n }, \"image/png\");\n\n URL.revokeObjectURL(url);\n };\n img.src = url;\n }, [chart.title, theme.tooltipBackground]);\n\n const toggleSort = () => {\n setSortOrder((prev) => {\n if (prev === \"none\") return \"desc\";\n if (prev === \"desc\") return \"asc\";\n return \"none\";\n });\n };\n\n // Render chart content helper\n const renderChartContent = (\n type: ChartType,\n chartData: ChartDataPoint[],\n xKey: string,\n yKey: string,\n enableBrush?: boolean,\n enableTrendline?: boolean,\n avgValue?: number,\n ) => {\n const commonProps = {\n data: chartData,\n margin: { top: 20, right: 30, left: 20, bottom: 50 },\n };\n\n const tooltipStyle = {\n backgroundColor: theme.tooltipBackground,\n border: theme.tooltipBorder,\n borderRadius: \"8px\",\n };\n\n const brushComponent = enableBrush ? (\n <Brush\n dataKey={xKey}\n height={30}\n stroke={theme.accentPrimary}\n fill=\"#1e1b4b\"\n />\n ) : null;\n\n const trendlineComponent =\n enableTrendline && avgValue && !isMultiSeries ? (\n <ReferenceLine\n y={avgValue}\n stroke={theme.accentSuccess}\n strokeDasharray=\"5 5\"\n label={{\n value: `Avg: ${avgValue.toFixed(2)}`,\n fill: theme.accentSuccess,\n fontSize: 12,\n }}\n />\n ) : null;\n\n const xAxisProps = {\n dataKey: xKey,\n stroke: theme.textMuted,\n fontSize: 11,\n angle: -45,\n textAnchor: \"end\" as const,\n height: 80,\n interval: 0 as const,\n tick: { fill: theme.textMuted },\n };\n\n const yAxisProps = {\n stroke: theme.textMuted,\n fontSize: 12,\n tick: { fill: theme.textMuted },\n };\n\n // Multi-series rendering\n if (isMultiSeries && type !== \"pie\" && type !== \"scatter\") {\n switch (type) {\n case \"bar\":\n return (\n <BarChart {...commonProps}>\n <CartesianGrid strokeDasharray=\"3 3\" stroke={theme.gridStroke} />\n <XAxis {...xAxisProps} />\n <YAxis {...yAxisProps} />\n <Tooltip contentStyle={tooltipStyle} />\n <Legend />\n {seriesKeys.map((key, i) => (\n <Bar\n key={key}\n dataKey={key}\n fill={theme.colors[i % theme.colors.length]}\n stackId=\"stack\"\n radius={\n i === seriesKeys.length - 1 ? [4, 4, 0, 0] : undefined\n }\n />\n ))}\n {brushComponent}\n </BarChart>\n );\n case \"line\":\n return (\n <LineChart {...commonProps}>\n <CartesianGrid strokeDasharray=\"3 3\" stroke={theme.gridStroke} />\n <XAxis {...xAxisProps} />\n <YAxis {...yAxisProps} />\n <Tooltip contentStyle={tooltipStyle} />\n <Legend />\n {seriesKeys.map((key, i) => (\n <Line\n key={key}\n type=\"monotone\"\n dataKey={key}\n stroke={theme.colors[i % theme.colors.length]}\n strokeWidth={2}\n dot={{ fill: theme.colors[i % theme.colors.length] }}\n />\n ))}\n {brushComponent}\n </LineChart>\n );\n case \"area\":\n return (\n <AreaChart {...commonProps}>\n <defs>\n {seriesKeys.map((key, i) => (\n <linearGradient\n key={key}\n id={`grad-${chart.id}-${i}`}\n x1=\"0\"\n y1=\"0\"\n x2=\"0\"\n y2=\"1\"\n >\n <stop\n offset=\"5%\"\n stopColor={theme.colors[i % theme.colors.length]}\n stopOpacity={0.6}\n />\n <stop\n offset=\"95%\"\n stopColor={theme.colors[i % theme.colors.length]}\n stopOpacity={0}\n />\n </linearGradient>\n ))}\n </defs>\n <CartesianGrid strokeDasharray=\"3 3\" stroke={theme.gridStroke} />\n <XAxis {...xAxisProps} />\n <YAxis {...yAxisProps} />\n <Tooltip contentStyle={tooltipStyle} />\n <Legend />\n {seriesKeys.map((key, i) => (\n <Area\n key={key}\n type=\"monotone\"\n dataKey={key}\n stroke={theme.colors[i % theme.colors.length]}\n fillOpacity={1}\n fill={`url(#grad-${chart.id}-${i})`}\n stackId=\"stack\"\n />\n ))}\n {brushComponent}\n </AreaChart>\n );\n default:\n return null;\n }\n }\n\n // Single-series rendering\n switch (type) {\n case \"bar\":\n return (\n <BarChart {...commonProps}>\n <CartesianGrid strokeDasharray=\"3 3\" stroke={theme.gridStroke} />\n <XAxis {...xAxisProps} />\n <YAxis {...yAxisProps} />\n <Tooltip contentStyle={tooltipStyle} />\n <Legend />\n {trendlineComponent}\n <Bar dataKey={yKey} fill={theme.accentPrimary} radius={[4, 4, 0, 0]}>\n {chartData.map((_, index) => (\n <Cell\n key={`cell-${index}`}\n fill={theme.colors[index % theme.colors.length]}\n />\n ))}\n </Bar>\n {brushComponent}\n </BarChart>\n );\n\n case \"line\":\n return (\n <LineChart {...commonProps}>\n <CartesianGrid strokeDasharray=\"3 3\" stroke={theme.gridStroke} />\n <XAxis {...xAxisProps} />\n <YAxis {...yAxisProps} />\n <Tooltip contentStyle={tooltipStyle} />\n <Legend />\n {trendlineComponent}\n <Line\n type=\"monotone\"\n dataKey={yKey}\n stroke={theme.accentPrimary}\n strokeWidth={3}\n dot={{ fill: theme.accentPrimary }}\n activeDot={{ r: 8 }}\n />\n {brushComponent}\n </LineChart>\n );\n\n case \"area\": {\n const gradientId = `colorY-${chart.id}`;\n return (\n <AreaChart {...commonProps}>\n <defs>\n <linearGradient id={gradientId} x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n <stop\n offset=\"5%\"\n stopColor={theme.accentPrimary}\n stopOpacity={0.8}\n />\n <stop\n offset=\"95%\"\n stopColor={theme.accentPrimary}\n stopOpacity={0}\n />\n </linearGradient>\n </defs>\n <CartesianGrid strokeDasharray=\"3 3\" stroke={theme.gridStroke} />\n <XAxis {...xAxisProps} />\n <YAxis {...yAxisProps} />\n <Tooltip contentStyle={tooltipStyle} />\n <Legend />\n {trendlineComponent}\n <Area\n type=\"monotone\"\n dataKey={yKey}\n stroke={theme.accentPrimary}\n fillOpacity={1}\n fill={`url(#${gradientId})`}\n />\n {brushComponent}\n </AreaChart>\n );\n }\n\n case \"scatter\":\n return (\n <ScatterChart {...commonProps}>\n <CartesianGrid strokeDasharray=\"3 3\" stroke={theme.gridStroke} />\n <XAxis\n type=\"category\"\n {...xAxisProps}\n />\n <YAxis\n type=\"number\"\n dataKey={yKey}\n {...yAxisProps}\n />\n <Tooltip\n cursor={{ strokeDasharray: \"3 3\" }}\n contentStyle={tooltipStyle}\n />\n <Legend />\n <Scatter name={yKey} data={chartData} fill={theme.accentPrimary} />\n </ScatterChart>\n );\n\n case \"pie\":\n return (\n <PieChart>\n <Pie\n data={chartData}\n innerRadius={60}\n outerRadius={100}\n paddingAngle={5}\n dataKey={yKey}\n nameKey={xKey}\n label={({ name, percent }) =>\n `${name}: ${((percent ?? 0) * 100).toFixed(0)}%`\n }\n labelLine={{ stroke: theme.textMuted }}\n >\n {chartData.map((_, index) => (\n <Cell\n key={`cell-${index}`}\n fill={theme.colors[index % theme.colors.length]}\n />\n ))}\n </Pie>\n <Tooltip contentStyle={tooltipStyle} />\n <Legend />\n </PieChart>\n );\n\n default:\n return null;\n }\n };\n\n if (processedData.length === 0) {\n return (\n <div className=\"flex h-[300px] flex-col items-center justify-center gap-4 text-gray-400\">\n <div className=\"text-center\">\n <p className=\"mb-1 font-medium text-red-400\">\n Unable to generate this chart\n </p>\n <p className=\"mb-4 text-sm text-gray-500\">\n Columns: {chart.xAxis}, {chart.yAxis}\n </p>\n {onRegenerate && (\n <button\n onClick={handleRegenerate}\n disabled={isRegenerating}\n className=\"flex items-center gap-2 rounded-lg bg-violet-600 px-4 py-2 text-sm text-white transition-colors hover:bg-violet-500 disabled:opacity-50\"\n >\n <RefreshCw\n className={`h-4 w-4 ${isRegenerating ? \"animate-spin\" : \"\"}`}\n />\n {isRegenerating ? \"Regenerating...\" : \"Regenerate with AI\"}\n </button>\n )}\n </div>\n </div>\n );\n }\n\n return (\n <div>\n <ChartToolbar\n chartType={chart.type}\n sortOrder={sortOrder}\n limitResults={limitResults}\n showBrush={showBrush}\n showTrendline={showTrendline}\n isRegenerating={isRegenerating}\n hasRegenerate={!!onRegenerate}\n onToggleSort={toggleSort}\n onLimitChange={setLimitResults}\n onToggleBrush={() => setShowBrush(!showBrush)}\n onToggleTrendline={() => setShowTrendline(!showTrendline)}\n onExportCSV={handleExportCSV}\n onExportPNG={handleExportPNG}\n onRegenerate={handleRegenerate}\n />\n\n {/* Chart */}\n <div className=\"h-[450px] w-full\" ref={chartContainerRef}>\n <ResponsiveContainer\n key={`chart-${chart.id}-${showBrush ? \"brush\" : \"no-brush\"}`}\n width=\"100%\"\n height=\"100%\"\n >\n {renderChartContent(\n chart.type,\n processedData,\n xColName,\n isMultiSeries ? \"\" : yColName,\n showBrush,\n showTrendline,\n average,\n )}\n </ResponsiveContainer>\n </div>\n\n {/* Metadata Tags */}\n <div className=\"mt-4 flex flex-wrap gap-2\">\n <span className=\"rounded bg-white/10 px-2 py-0.5 text-xs text-gray-400\">\n X: {chart.xAxis}\n </span>\n <span className=\"rounded bg-white/10 px-2 py-0.5 text-xs text-gray-400\">\n Y: {chart.yAxis}\n </span>\n {chart.groupBy && (\n <span className=\"rounded bg-cyan-500/20 px-2 py-0.5 text-xs text-cyan-300\">\n Group: {chart.groupBy}\n </span>\n )}\n {chart.aggregation && chart.aggregation !== \"none\" && (\n <span className=\"rounded bg-violet-500/20 px-2 py-0.5 text-xs text-violet-300\">\n {chart.aggregation}\n </span>\n )}\n <span className=\"rounded bg-white/10 px-2 py-0.5 text-xs text-gray-400\">\n {processedData.length} items\n </span>\n {isMultiSeries && (\n <span className=\"rounded bg-emerald-500/20 px-2 py-0.5 text-xs text-emerald-300\">\n {seriesKeys.length} series\n </span>\n )}\n </div>\n </div>\n );\n}\n","import type {\n TabularData,\n ChartConfig,\n SortOrder,\n ChartDataPoint,\n} from \"./types\";\n\nexport interface ProcessedChartResult {\n data: ChartDataPoint[];\n seriesKeys: string[];\n yKey: string;\n}\n\nexport const processChartData = (\n data: TabularData,\n chart: ChartConfig,\n sortOrder: SortOrder = \"none\",\n limit = 20,\n): ChartDataPoint[] => {\n const result = processChartDataMultiSeries(data, chart, sortOrder, limit);\n return result.data;\n};\n\nexport const processChartDataMultiSeries = (\n data: TabularData,\n chart: ChartConfig,\n sortOrder: SortOrder = \"none\",\n limit = 20,\n): ProcessedChartResult => {\n let result: ChartDataPoint[] = [];\n\n // Find actual columns (case-insensitive match)\n const xColDef = data.columns.find(\n (c) => c.name.toLowerCase() === chart.xAxis.toLowerCase(),\n );\n const yColDef = data.columns.find(\n (c) => c.name.toLowerCase() === chart.yAxis.toLowerCase(),\n );\n const groupColDef = chart.groupBy\n ? data.columns.find(\n (c) => c.name.toLowerCase() === chart.groupBy!.toLowerCase(),\n )\n : undefined;\n\n if (!xColDef) return { data: [], seriesKeys: [], yKey: chart.yAxis };\n\n const xCol = xColDef.name;\n const xIdx = xColDef.index;\n\n // For count aggregation, we don't need a valid Y column - we just count occurrences\n const isCountMode = chart.aggregation === \"count\";\n const yCol = yColDef?.name ?? \"count\";\n const yIdx = yColDef?.index ?? -1;\n\n // GroupBy mode: create multi-series data (not supported for pie/scatter)\n const supportsGroupBy =\n chart.type !== \"pie\" && chart.type !== \"scatter\";\n if (\n supportsGroupBy &&\n groupColDef &&\n chart.aggregation &&\n chart.aggregation !== \"none\"\n ) {\n const groupIdx = groupColDef.index;\n const allGroups = new Set<string>();\n\n // Nested grouping: xVal -> groupVal -> stats\n const grouped = new Map<\n string,\n Map<string, { sum: number; count: number; min: number; max: number }>\n >();\n\n data.rows.forEach((row) => {\n const xVal = String(row[xIdx] ?? \"\").trim();\n const groupVal = String(row[groupIdx] ?? \"\").trim();\n if (!xVal || !groupVal) return;\n\n allGroups.add(groupVal);\n\n if (!grouped.has(xVal)) grouped.set(xVal, new Map());\n const xGroup = grouped.get(xVal)!;\n\n if (isCountMode) {\n const current = xGroup.get(groupVal) ?? {\n sum: 0,\n count: 0,\n min: 0,\n max: 0,\n };\n xGroup.set(groupVal, { ...current, count: current.count + 1 });\n } else if (yIdx >= 0) {\n const yVal = parseFloat(String(row[yIdx] ?? \"0\"));\n if (!isNaN(yVal)) {\n const current = xGroup.get(groupVal) ?? {\n sum: 0,\n count: 0,\n min: Infinity,\n max: -Infinity,\n };\n xGroup.set(groupVal, {\n sum: current.sum + yVal,\n count: current.count + 1,\n min: Math.min(current.min, yVal),\n max: Math.max(current.max, yVal),\n });\n }\n }\n });\n\n const seriesKeys = Array.from(allGroups).slice(0, 8); // max 8 series\n\n grouped.forEach((groupMap, xKey) => {\n const point: ChartDataPoint = { [xCol]: xKey };\n seriesKeys.forEach((groupKey) => {\n const stats = groupMap.get(groupKey);\n if (stats) {\n let value: number;\n switch (chart.aggregation) {\n case \"sum\":\n value = stats.sum;\n break;\n case \"avg\":\n value = stats.count > 0 ? stats.sum / stats.count : 0;\n break;\n case \"count\":\n value = stats.count;\n break;\n case \"min\":\n value = stats.min === Infinity ? 0 : stats.min;\n break;\n case \"max\":\n value = stats.max === -Infinity ? 0 : stats.max;\n break;\n default:\n value = stats.sum;\n }\n point[groupKey] = Math.round(value * 100) / 100;\n } else {\n point[groupKey] = 0;\n }\n });\n result.push(point);\n });\n\n // Sort by first series value or alphabetically\n if (sortOrder !== \"none\" && seriesKeys[0]) {\n const firstKey = seriesKeys[0];\n result.sort((a, b) => {\n const aVal = (a[firstKey] as number) ?? 0;\n const bVal = (b[firstKey] as number) ?? 0;\n return sortOrder === \"desc\" ? bVal - aVal : aVal - bVal;\n });\n }\n\n return {\n data: result.slice(0, limit),\n seriesKeys,\n yKey: yCol,\n };\n }\n\n // Standard single-series mode (existing logic)\n if (chart.aggregation && chart.aggregation !== \"none\") {\n const groups = new Map<\n string,\n { sum: number; count: number; min: number; max: number }\n >();\n\n data.rows.forEach((row) => {\n const xVal = String(row[xIdx] ?? \"\").trim();\n if (!xVal) return;\n\n if (isCountMode) {\n const current = groups.get(xVal) ?? {\n sum: 0,\n count: 0,\n min: 0,\n max: 0,\n };\n groups.set(xVal, {\n ...current,\n count: current.count + 1,\n });\n } else if (yIdx >= 0) {\n const yVal = parseFloat(String(row[yIdx] ?? \"0\"));\n if (!isNaN(yVal)) {\n const current = groups.get(xVal) ?? {\n sum: 0,\n count: 0,\n min: Infinity,\n max: -Infinity,\n };\n groups.set(xVal, {\n sum: current.sum + yVal,\n count: current.count + 1,\n min: Math.min(current.min, yVal),\n max: Math.max(current.max, yVal),\n });\n }\n }\n });\n\n groups.forEach((stats, key) => {\n let value: number;\n switch (chart.aggregation) {\n case \"sum\":\n value = stats.sum;\n break;\n case \"avg\":\n value = stats.count > 0 ? stats.sum / stats.count : 0;\n break;\n case \"count\":\n value = stats.count;\n break;\n case \"min\":\n value = stats.min === Infinity ? 0 : stats.min;\n break;\n case \"max\":\n value = stats.max === -Infinity ? 0 : stats.max;\n break;\n default:\n value = stats.sum;\n }\n result.push({ [xCol]: key, [yCol]: Math.round(value * 100) / 100 });\n });\n } else if (yIdx >= 0) {\n result = data.rows\n .slice(0, Math.min(limit * 2, data.rows.length))\n .map((row) => ({\n [xCol]: row[xIdx] ?? \"\",\n [yCol]: parseFloat(String(row[yIdx] ?? \"0\")),\n }))\n .filter((item) => !isNaN(item[yCol] as number));\n }\n\n // Apply sorting\n if (sortOrder !== \"none\") {\n result.sort((a, b) => {\n const aVal = a[yCol] as number;\n const bVal = b[yCol] as number;\n return sortOrder === \"desc\" ? bVal - aVal : aVal - bVal;\n });\n }\n\n return {\n data: result.slice(0, limit),\n seriesKeys: [],\n yKey: yCol,\n };\n};\n","import {\n RefreshCw,\n Download,\n SortAsc,\n SortDesc,\n RotateCcw,\n TrendingUp,\n Filter,\n Image,\n} from \"lucide-react\";\nimport type { ChartType, SortOrder } from \"./types\";\n\ninterface ChartToolbarProps {\n chartType: ChartType;\n sortOrder: SortOrder;\n limitResults: number;\n showBrush: boolean;\n showTrendline: boolean;\n isRegenerating: boolean;\n hasRegenerate: boolean;\n onToggleSort: () => void;\n onLimitChange: (limit: number) => void;\n onToggleBrush: () => void;\n onToggleTrendline: () => void;\n onExportCSV: () => void;\n onExportPNG: () => void;\n onRegenerate: () => void;\n}\n\nexport function ChartToolbar({\n chartType,\n sortOrder,\n limitResults,\n showBrush,\n showTrendline,\n isRegenerating,\n hasRegenerate,\n onToggleSort,\n onLimitChange,\n onToggleBrush,\n onToggleTrendline,\n onExportCSV,\n onExportPNG,\n onRegenerate,\n}: ChartToolbarProps) {\n const supportsBrush =\n chartType === \"line\" || chartType === \"area\" || chartType === \"bar\";\n const supportsTrendline =\n chartType === \"bar\" || chartType === \"line\" || chartType === \"area\";\n\n return (\n <div className=\"mb-4 flex flex-wrap items-center gap-2 rounded-xl border border-white/10 bg-white/5 p-3\">\n {/* Sort Control */}\n <button\n onClick={onToggleSort}\n className={`flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-sm transition-colors ${\n sortOrder !== \"none\"\n ? \"border border-violet-500/30 bg-violet-500/20 text-violet-400\"\n : \"bg-white/5 text-gray-400 hover:bg-white/10 hover:text-white\"\n }`}\n title=\"Sort by value\"\n >\n {sortOrder === \"asc\" ? (\n <SortAsc className=\"h-4 w-4\" />\n ) : sortOrder === \"desc\" ? (\n <SortDesc className=\"h-4 w-4\" />\n ) : (\n <SortDesc className=\"h-4 w-4 opacity-50\" />\n )}\n Sort\n </button>\n\n {/* Limit Results */}\n <div className=\"flex items-center gap-2\">\n <Filter className=\"h-4 w-4 text-gray-400\" />\n <select\n value={limitResults}\n onChange={(e) => onLimitChange(Number(e.target.value))}\n className=\"rounded-lg border border-white/10 bg-white/5 px-2 py-1.5 text-sm text-gray-300 focus:border-violet-500/50 focus:outline-none\"\n >\n <option value={10}>Top 10</option>\n <option value={20}>Top 20</option>\n <option value={50}>Top 50</option>\n <option value={100}>Top 100</option>\n <option value={999999}>All</option>\n </select>\n </div>\n\n {/* Brush/Zoom Toggle */}\n {supportsBrush && (\n <button\n onClick={onToggleBrush}\n className={`flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-sm transition-colors ${\n showBrush\n ? \"border border-cyan-500/30 bg-cyan-500/20 text-cyan-400\"\n : \"bg-white/5 text-gray-400 hover:bg-white/10 hover:text-white\"\n }`}\n title=\"Enable zoom/brush\"\n >\n <RotateCcw className=\"h-4 w-4\" />\n Zoom\n </button>\n )}\n\n {/* Trend Line Toggle */}\n {supportsTrendline && (\n <button\n onClick={onToggleTrendline}\n className={`flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-sm transition-colors ${\n showTrendline\n ? \"border border-emerald-500/30 bg-emerald-500/20 text-emerald-400\"\n : \"bg-white/5 text-gray-400 hover:bg-white/10 hover:text-white\"\n }`}\n title=\"Show average line\"\n >\n <TrendingUp className=\"h-4 w-4\" />\n Average\n </button>\n )}\n\n <div className=\"flex-1\" />\n\n {/* Export PNG Button */}\n <button\n onClick={onExportPNG}\n className=\"flex items-center gap-1.5 rounded-lg bg-white/5 px-3 py-1.5 text-sm text-gray-400 transition-colors hover:bg-white/10 hover:text-white\"\n title=\"Export chart as PNG image\"\n >\n <Image className=\"h-4 w-4\" />\n PNG\n </button>\n\n {/* Export CSV Button */}\n <button\n onClick={onExportCSV}\n className=\"flex items-center gap-1.5 rounded-lg bg-white/5 px-3 py-1.5 text-sm text-gray-400 transition-colors hover:bg-white/10 hover:text-white\"\n title=\"Export chart data as CSV\"\n >\n <Download className=\"h-4 w-4\" />\n CSV\n </button>\n\n {/* Regenerate Button */}\n {hasRegenerate && (\n <button\n onClick={onRegenerate}\n disabled={isRegenerating}\n className=\"flex items-center gap-1.5 rounded-lg bg-violet-500/20 px-3 py-1.5 text-sm text-violet-400 transition-colors hover:bg-violet-500/30 disabled:opacity-50\"\n >\n <RefreshCw\n className={`h-4 w-4 ${isRegenerating ? \"animate-spin\" : \"\"}`}\n />\n {isRegenerating ? \"...\" : \"Regenerate\"}\n </button>\n )}\n </div>\n );\n}\n","import type { TabularData, ChartConfig, ChartTheme } from \"./types\";\nimport { defaultDarkTheme } from \"./types\";\nimport { ChartThemeProvider } from \"./ThemeContext\";\nimport { SingleChart } from \"./SingleChart\";\n\nexport interface ChartDisplayProps {\n data: TabularData;\n charts: ChartConfig[];\n onRegenerate?: (chart: ChartConfig) => Promise<void>;\n /** Optional wrapper component for each chart card */\n cardWrapper?: React.ComponentType<{\n children: React.ReactNode;\n title?: string;\n className?: string;\n }>;\n /** Optional theme override */\n theme?: ChartTheme;\n}\n\nfunction DefaultCard({\n children,\n className = \"\",\n}: {\n children: React.ReactNode;\n title?: string;\n className?: string;\n}) {\n return <div className={className}>{children}</div>;\n}\n\nexport function ChartDisplay({\n data,\n charts,\n onRegenerate,\n cardWrapper: CardWrapper = DefaultCard,\n theme = defaultDarkTheme,\n}: ChartDisplayProps) {\n if (charts.length === 0) return null;\n\n return (\n <ChartThemeProvider theme={theme}>\n <div className=\"animate-fade-in space-y-6\">\n {charts.map((chart) => (\n <CardWrapper\n key={chart.id}\n title={chart.title}\n className=\"overflow-hidden rounded-2xl border border-white/10 bg-slate-900/50\"\n >\n <div className=\"p-6\">\n <h3 className=\"mb-2 bg-linear-to-r from-violet-400 to-fuchsia-400 bg-clip-text text-xl font-bold text-transparent\">\n {chart.title}\n </h3>\n <p className=\"mb-4 text-gray-400\">{chart.description}</p>\n <SingleChart\n data={data}\n chart={chart}\n onRegenerate={onRegenerate}\n />\n </div>\n </CardWrapper>\n ))}\n </div>\n </ChartThemeProvider>\n );\n}\n","// Re-export from defaultDarkTheme for backwards compatibility\nexport { defaultDarkTheme as COLORS_THEME } from \"./types\";\n\nexport const COLORS = [\n \"#8b5cf6\", // Violet 500\n \"#06b6d4\", // Cyan 500\n \"#f43f5e\", // Rose 500\n \"#eab308\", // Yellow 500\n \"#10b981\", // Emerald 500\n \"#3b82f6\", // Blue 500\n \"#d946ef\", // Fuchsia 500\n \"#f97316\", // Orange 500\n];\n"],"mappings":";AAyDO,IAAM,mBAA+B;AAAA,EAC1C,QAAQ;AAAA,IACN;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAAA,EACA,YAAY;AAAA,EACZ,gBAAgB;AAAA,EAChB,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,WAAW;AAAA,EACX,YAAY;AAAA,EACZ,YAAY;AAAA,EACZ,mBAAmB;AAAA,EACnB,eAAe;AAAA,EACf,eAAe;AAAA,EACf,iBAAiB;AAAA,EACjB,eAAe;AAAA,EACf,cAAc;AAChB;AAEO,IAAM,oBAAgC;AAAA,EAC3C,QAAQ;AAAA,IACN;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAAA,EACA,YAAY;AAAA,EACZ,gBAAgB;AAAA,EAChB,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,WAAW;AAAA,EACX,YAAY;AAAA,EACZ,YAAY;AAAA,EACZ,mBAAmB;AAAA,EACnB,eAAe;AAAA,EACf,eAAe;AAAA,EACf,iBAAiB;AAAA,EACjB,eAAe;AAAA,EACf,cAAc;AAChB;;;AC3GA,SAAS,eAAe,kBAAkB;AAatC;AAVJ,IAAM,oBAAoB,cAA0B,gBAAgB;AAE7D,SAAS,mBAAmB;AAAA,EACjC;AAAA,EACA;AACF,GAGG;AACD,SACE,oBAAC,kBAAkB,UAAlB,EAA2B,OAAO,OAChC,UACH;AAEJ;AAEO,SAAS,gBAA4B;AAC1C,SAAO,WAAW,iBAAiB;AACrC;;;ACrBA,SAAS,UAAU,SAAS,aAAa,cAAc;AACvD;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,aAAAA,kBAAiB;;;ACTnB,IAAM,mBAAmB,CAC9B,MACA,OACA,YAAuB,QACvB,QAAQ,OACa;AACrB,QAAM,SAAS,4BAA4B,MAAM,OAAO,WAAW,KAAK;AACxE,SAAO,OAAO;AAChB;AAEO,IAAM,8BAA8B,CACzC,MACA,OACA,YAAuB,QACvB,QAAQ,OACiB;AACzB,MAAI,SAA2B,CAAC;AAGhC,QAAM,UAAU,KAAK,QAAQ;AAAA,IAC3B,CAAC,MAAM,EAAE,KAAK,YAAY,MAAM,MAAM,MAAM,YAAY;AAAA,EAC1D;AACA,QAAM,UAAU,KAAK,QAAQ;AAAA,IAC3B,CAAC,MAAM,EAAE,KAAK,YAAY,MAAM,MAAM,MAAM,YAAY;AAAA,EAC1D;AACA,QAAM,cAAc,MAAM,UACtB,KAAK,QAAQ;AAAA,IACX,CAAC,MAAM,EAAE,KAAK,YAAY,MAAM,MAAM,QAAS,YAAY;AAAA,EAC7D,IACA;AAEJ,MAAI,CAAC,QAAS,QAAO,EAAE,MAAM,CAAC,GAAG,YAAY,CAAC,GAAG,MAAM,MAAM,MAAM;AAEnE,QAAM,OAAO,QAAQ;AACrB,QAAM,OAAO,QAAQ;AAGrB,QAAM,cAAc,MAAM,gBAAgB;AAC1C,QAAM,OAAO,SAAS,QAAQ;AAC9B,QAAM,OAAO,SAAS,SAAS;AAG/B,QAAM,kBACJ,MAAM,SAAS,SAAS,MAAM,SAAS;AACzC,MACE,mBACA,eACA,MAAM,eACN,MAAM,gBAAgB,QACtB;AACA,UAAM,WAAW,YAAY;AAC7B,UAAM,YAAY,oBAAI,IAAY;AAGlC,UAAM,UAAU,oBAAI,IAGlB;AAEF,SAAK,KAAK,QAAQ,CAAC,QAAQ;AACzB,YAAM,OAAO,OAAO,IAAI,IAAI,KAAK,EAAE,EAAE,KAAK;AAC1C,YAAM,WAAW,OAAO,IAAI,QAAQ,KAAK,EAAE,EAAE,KAAK;AAClD,UAAI,CAAC,QAAQ,CAAC,SAAU;AAExB,gBAAU,IAAI,QAAQ;AAEtB,UAAI,CAAC,QAAQ,IAAI,IAAI,EAAG,SAAQ,IAAI,MAAM,oBAAI,IAAI,CAAC;AACnD,YAAM,SAAS,QAAQ,IAAI,IAAI;AAE/B,UAAI,aAAa;AACf,cAAM,UAAU,OAAO,IAAI,QAAQ,KAAK;AAAA,UACtC,KAAK;AAAA,UACL,OAAO;AAAA,UACP,KAAK;AAAA,UACL,KAAK;AAAA,QACP;AACA,eAAO,IAAI,UAAU,EAAE,GAAG,SAAS,OAAO,QAAQ,QAAQ,EAAE,CAAC;AAAA,MAC/D,WAAW,QAAQ,GAAG;AACpB,cAAM,OAAO,WAAW,OAAO,IAAI,IAAI,KAAK,GAAG,CAAC;AAChD,YAAI,CAAC,MAAM,IAAI,GAAG;AAChB,gBAAM,UAAU,OAAO,IAAI,QAAQ,KAAK;AAAA,YACtC,KAAK;AAAA,YACL,OAAO;AAAA,YACP,KAAK;AAAA,YACL,KAAK;AAAA,UACP;AACA,iBAAO,IAAI,UAAU;AAAA,YACnB,KAAK,QAAQ,MAAM;AAAA,YACnB,OAAO,QAAQ,QAAQ;AAAA,YACvB,KAAK,KAAK,IAAI,QAAQ,KAAK,IAAI;AAAA,YAC/B,KAAK,KAAK,IAAI,QAAQ,KAAK,IAAI;AAAA,UACjC,CAAC;AAAA,QACH;AAAA,MACF;AAAA,IACF,CAAC;AAED,UAAM,aAAa,MAAM,KAAK,SAAS,EAAE,MAAM,GAAG,CAAC;AAEnD,YAAQ,QAAQ,CAAC,UAAU,SAAS;AAClC,YAAM,QAAwB,EAAE,CAAC,IAAI,GAAG,KAAK;AAC7C,iBAAW,QAAQ,CAAC,aAAa;AAC/B,cAAM,QAAQ,SAAS,IAAI,QAAQ;AACnC,YAAI,OAAO;AACT,cAAI;AACJ,kBAAQ,MAAM,aAAa;AAAA,YACzB,KAAK;AACH,sBAAQ,MAAM;AACd;AAAA,YACF,KAAK;AACH,sBAAQ,MAAM,QAAQ,IAAI,MAAM,MAAM,MAAM,QAAQ;AACpD;AAAA,YACF,KAAK;AACH,sBAAQ,MAAM;AACd;AAAA,YACF,KAAK;AACH,sBAAQ,MAAM,QAAQ,WAAW,IAAI,MAAM;AAC3C;AAAA,YACF,KAAK;AACH,sBAAQ,MAAM,QAAQ,YAAY,IAAI,MAAM;AAC5C;AAAA,YACF;AACE,sBAAQ,MAAM;AAAA,UAClB;AACA,gBAAM,QAAQ,IAAI,KAAK,MAAM,QAAQ,GAAG,IAAI;AAAA,QAC9C,OAAO;AACL,gBAAM,QAAQ,IAAI;AAAA,QACpB;AAAA,MACF,CAAC;AACD,aAAO,KAAK,KAAK;AAAA,IACnB,CAAC;AAGD,QAAI,cAAc,UAAU,WAAW,CAAC,GAAG;AACzC,YAAM,WAAW,WAAW,CAAC;AAC7B,aAAO,KAAK,CAAC,GAAG,MAAM;AACpB,cAAM,OAAQ,EAAE,QAAQ,KAAgB;AACxC,cAAM,OAAQ,EAAE,QAAQ,KAAgB;AACxC,eAAO,cAAc,SAAS,OAAO,OAAO,OAAO;AAAA,MACrD,CAAC;AAAA,IACH;AAEA,WAAO;AAAA,MACL,MAAM,OAAO,MAAM,GAAG,KAAK;AAAA,MAC3B;AAAA,MACA,MAAM;AAAA,IACR;AAAA,EACF;AAGA,MAAI,MAAM,eAAe,MAAM,gBAAgB,QAAQ;AACrD,UAAM,SAAS,oBAAI,IAGjB;AAEF,SAAK,KAAK,QAAQ,CAAC,QAAQ;AACzB,YAAM,OAAO,OAAO,IAAI,IAAI,KAAK,EAAE,EAAE,KAAK;AAC1C,UAAI,CAAC,KAAM;AAEX,UAAI,aAAa;AACf,cAAM,UAAU,OAAO,IAAI,IAAI,KAAK;AAAA,UAClC,KAAK;AAAA,UACL,OAAO;AAAA,UACP,KAAK;AAAA,UACL,KAAK;AAAA,QACP;AACA,eAAO,IAAI,MAAM;AAAA,UACf,GAAG;AAAA,UACH,OAAO,QAAQ,QAAQ;AAAA,QACzB,CAAC;AAAA,MACH,WAAW,QAAQ,GAAG;AACpB,cAAM,OAAO,WAAW,OAAO,IAAI,IAAI,KAAK,GAAG,CAAC;AAChD,YAAI,CAAC,MAAM,IAAI,GAAG;AAChB,gBAAM,UAAU,OAAO,IAAI,IAAI,KAAK;AAAA,YAClC,KAAK;AAAA,YACL,OAAO;AAAA,YACP,KAAK;AAAA,YACL,KAAK;AAAA,UACP;AACA,iBAAO,IAAI,MAAM;AAAA,YACf,KAAK,QAAQ,MAAM;AAAA,YACnB,OAAO,QAAQ,QAAQ;AAAA,YACvB,KAAK,KAAK,IAAI,QAAQ,KAAK,IAAI;AAAA,YAC/B,KAAK,KAAK,IAAI,QAAQ,KAAK,IAAI;AAAA,UACjC,CAAC;AAAA,QACH;AAAA,MACF;AAAA,IACF,CAAC;AAED,WAAO,QAAQ,CAAC,OAAO,QAAQ;AAC7B,UAAI;AACJ,cAAQ,MAAM,aAAa;AAAA,QACzB,KAAK;AACH,kBAAQ,MAAM;AACd;AAAA,QACF,KAAK;AACH,kBAAQ,MAAM,QAAQ,IAAI,MAAM,MAAM,MAAM,QAAQ;AACpD;AAAA,QACF,KAAK;AACH,kBAAQ,MAAM;AACd;AAAA,QACF,KAAK;AACH,kBAAQ,MAAM,QAAQ,WAAW,IAAI,MAAM;AAC3C;AAAA,QACF,KAAK;AACH,kBAAQ,MAAM,QAAQ,YAAY,IAAI,MAAM;AAC5C;AAAA,QACF;AACE,kBAAQ,MAAM;AAAA,MAClB;AACA,aAAO,KAAK,EAAE,CAAC,IAAI,GAAG,KAAK,CAAC,IAAI,GAAG,KAAK,MAAM,QAAQ,GAAG,IAAI,IAAI,CAAC;AAAA,IACpE,CAAC;AAAA,EACH,WAAW,QAAQ,GAAG;AACpB,aAAS,KAAK,KACX,MAAM,GAAG,KAAK,IAAI,QAAQ,GAAG,KAAK,KAAK,MAAM,CAAC,EAC9C,IAAI,CAAC,SAAS;AAAA,MACb,CAAC,IAAI,GAAG,IAAI,IAAI,KAAK;AAAA,MACrB,CAAC,IAAI,GAAG,WAAW,OAAO,IAAI,IAAI,KAAK,GAAG,CAAC;AAAA,IAC7C,EAAE,EACD,OAAO,CAAC,SAAS,CAAC,MAAM,KAAK,IAAI,CAAW,CAAC;AAAA,EAClD;AAGA,MAAI,cAAc,QAAQ;AACxB,WAAO,KAAK,CAAC,GAAG,MAAM;AACpB,YAAM,OAAO,EAAE,IAAI;AACnB,YAAM,OAAO,EAAE,IAAI;AACnB,aAAO,cAAc,SAAS,OAAO,OAAO,OAAO;AAAA,IACrD,CAAC;AAAA,EACH;AAEA,SAAO;AAAA,IACL,MAAM,OAAO,MAAM,GAAG,KAAK;AAAA,IAC3B,YAAY,CAAC;AAAA,IACb,MAAM;AAAA,EACR;AACF;;;ACzPA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,SAAAC;AAAA,OACK;AA4CD,SAUI,OAAAC,MAVJ;AAxBC,SAAS,aAAa;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAsB;AACpB,QAAM,gBACJ,cAAc,UAAU,cAAc,UAAU,cAAc;AAChE,QAAM,oBACJ,cAAc,SAAS,cAAc,UAAU,cAAc;AAE/D,SACE,qBAAC,SAAI,WAAU,2FAEb;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,SAAS;AAAA,QACT,WAAW,8EACT,cAAc,SACV,iEACA,6DACN;AAAA,QACA,OAAM;AAAA,QAEL;AAAA,wBAAc,QACb,gBAAAA,KAAC,WAAQ,WAAU,WAAU,IAC3B,cAAc,SAChB,gBAAAA,KAAC,YAAS,WAAU,WAAU,IAE9B,gBAAAA,KAAC,YAAS,WAAU,sBAAqB;AAAA,UACzC;AAAA;AAAA;AAAA,IAEJ;AAAA,IAGA,qBAAC,SAAI,WAAU,2BACb;AAAA,sBAAAA,KAAC,UAAO,WAAU,yBAAwB;AAAA,MAC1C;AAAA,QAAC;AAAA;AAAA,UACC,OAAO;AAAA,UACP,UAAU,CAAC,MAAM,cAAc,OAAO,EAAE,OAAO,KAAK,CAAC;AAAA,UACrD,WAAU;AAAA,UAEV;AAAA,4BAAAA,KAAC,YAAO,OAAO,IAAI,oBAAM;AAAA,YACzB,gBAAAA,KAAC,YAAO,OAAO,IAAI,oBAAM;AAAA,YACzB,gBAAAA,KAAC,YAAO,OAAO,IAAI,oBAAM;AAAA,YACzB,gBAAAA,KAAC,YAAO,OAAO,KAAK,qBAAO;AAAA,YAC3B,gBAAAA,KAAC,YAAO,OAAO,QAAQ,iBAAG;AAAA;AAAA;AAAA,MAC5B;AAAA,OACF;AAAA,IAGC,iBACC;AAAA,MAAC;AAAA;AAAA,QACC,SAAS;AAAA,QACT,WAAW,8EACT,YACI,2DACA,6DACN;AAAA,QACA,OAAM;AAAA,QAEN;AAAA,0BAAAA,KAAC,aAAU,WAAU,WAAU;AAAA,UAAE;AAAA;AAAA;AAAA,IAEnC;AAAA,IAID,qBACC;AAAA,MAAC;AAAA;AAAA,QACC,SAAS;AAAA,QACT,WAAW,8EACT,gBACI,oEACA,6DACN;AAAA,QACA,OAAM;AAAA,QAEN;AAAA,0BAAAA,KAAC,cAAW,WAAU,WAAU;AAAA,UAAE;AAAA;AAAA;AAAA,IAEpC;AAAA,IAGF,gBAAAA,KAAC,SAAI,WAAU,UAAS;AAAA,IAGxB;AAAA,MAAC;AAAA;AAAA,QACC,SAAS;AAAA,QACT,WAAU;AAAA,QACV,OAAM;AAAA,QAEN;AAAA,0BAAAA,KAACD,QAAA,EAAM,WAAU,WAAU;AAAA,UAAE;AAAA;AAAA;AAAA,IAE/B;AAAA,IAGA;AAAA,MAAC;AAAA;AAAA,QACC,SAAS;AAAA,QACT,WAAU;AAAA,QACV,OAAM;AAAA,QAEN;AAAA,0BAAAC,KAAC,YAAS,WAAU,WAAU;AAAA,UAAE;AAAA;AAAA;AAAA,IAElC;AAAA,IAGC,iBACC;AAAA,MAAC;AAAA;AAAA,QACC,SAAS;AAAA,QACT,UAAU;AAAA,QACV,WAAU;AAAA,QAEV;AAAA,0BAAAA;AAAA,YAAC;AAAA;AAAA,cACC,WAAW,WAAW,iBAAiB,iBAAiB,EAAE;AAAA;AAAA,UAC5D;AAAA,UACC,iBAAiB,QAAQ;AAAA;AAAA;AAAA,IAC5B;AAAA,KAEJ;AAEJ;;;AFsCM,gBAAAC,MA4CM,QAAAC,aA5CN;AA3JC,SAAS,YAAY,EAAE,MAAM,OAAO,aAAa,GAAqB;AAC3E,QAAM,QAAQ,cAAc;AAC5B,QAAM,oBAAoB,OAAuB,IAAI;AACrD,QAAM,CAAC,gBAAgB,iBAAiB,IAAI,SAAS,KAAK;AAC1D,QAAM,CAAC,WAAW,YAAY,IAAI,SAAoB,MAAM;AAC5D,QAAM,CAAC,WAAW,YAAY,IAAI,SAAS,KAAK;AAChD,QAAM,CAAC,eAAe,gBAAgB,IAAI,SAAS,KAAK;AACxD,QAAM,CAAC,cAAc,eAAe,IAAI,SAAiB,EAAE;AAE3D,QAAM,YAAY;AAAA,IAChB,MAAM,4BAA4B,MAAM,OAAO,WAAW,YAAY;AAAA,IACtE,CAAC,MAAM,OAAO,WAAW,YAAY;AAAA,EACvC;AAEA,QAAM,EAAE,MAAM,eAAe,WAAW,IAAI;AAC5C,QAAM,gBAAgB,WAAW,SAAS;AAG1C,QAAM,WACJ,KAAK,QAAQ;AAAA,IACX,CAAC,QACC,IAAI,SAAS,MAAM,SACnB,IAAI,KAAK,YAAY,MAAM,MAAM,MAAM,YAAY;AAAA,EACvD,GAAG,QAAQ,MAAM;AAEnB,QAAM,WACJ,KAAK,QAAQ;AAAA,IACX,CAAC,QACC,IAAI,SAAS,MAAM,SACnB,IAAI,KAAK,YAAY,MAAM,MAAM,MAAM,YAAY;AAAA,EACvD,GAAG,QAAQ,MAAM;AAGnB,QAAM,UAAU,QAAQ,MAAM;AAC5B,QAAI,cAAc,WAAW,KAAK,cAAe,QAAO;AACxD,UAAM,MAAM,cAAc,OAAO,CAAC,KAAK,SAAS;AAC9C,YAAM,MAAM,KAAK,QAAQ;AACzB,aAAO,OAAO,OAAO,QAAQ,WAAW,MAAM;AAAA,IAChD,GAAG,CAAC;AACJ,WAAO,MAAM,cAAc;AAAA,EAC7B,GAAG,CAAC,eAAe,UAAU,aAAa,CAAC;AAE3C,QAAM,mBAAmB,YAAY;AACnC,QAAI,CAAC,aAAc;AACnB,sBAAkB,IAAI;AACtB,QAAI;AACF,YAAM,aAAa,KAAK;AAAA,IAC1B,UAAE;AACA,wBAAkB,KAAK;AAAA,IACzB;AAAA,EACF;AAEA,QAAM,kBAAkB,YAAY,MAAM;AACxC,QAAI,cAAc,WAAW,EAAG;AAEhC,UAAM,UAAU,OAAO,KAAK,cAAc,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,GAAG;AAC5D,UAAM,OAAO,cACV;AAAA,MAAI,CAAC,QACJ,OAAO,OAAO,GAAG,EACd,IAAI,CAAC,MAAM,IAAI,OAAO,CAAC,CAAC,GAAG,EAC3B,KAAK,GAAG;AAAA,IACb,EACC,KAAK,IAAI;AAEZ,UAAM,MAAM,GAAG,OAAO;AAAA,EAAK,IAAI;AAC/B,UAAM,OAAO,IAAI,KAAK,CAAC,GAAG,GAAG,EAAE,MAAM,WAAW,CAAC;AACjD,UAAM,MAAM,IAAI,gBAAgB,IAAI;AACpC,UAAM,IAAI,SAAS,cAAc,GAAG;AACpC,MAAE,OAAO;AACT,MAAE,WAAW,GAAG,MAAM,MAAM,QAAQ,QAAQ,GAAG,CAAC;AAChD,MAAE,MAAM;AACR,QAAI,gBAAgB,GAAG;AAAA,EACzB,GAAG,CAAC,eAAe,MAAM,KAAK,CAAC;AAE/B,QAAM,kBAAkB,YAAY,MAAM;AACxC,UAAM,YAAY,kBAAkB;AACpC,QAAI,CAAC,UAAW;AAEhB,UAAM,aAAa,UAAU,cAAc,KAAK;AAChD,QAAI,CAAC,WAAY;AAEjB,UAAM,WAAW,WAAW,UAAU,IAAI;AAC1C,aAAS;AAAA,MACP;AAAA,MACA;AAAA,IACF;AAEA,UAAM,OAAO,WAAW,sBAAsB;AAC9C,aAAS,aAAa,SAAS,OAAO,KAAK,KAAK,CAAC;AACjD,aAAS,aAAa,UAAU,OAAO,KAAK,MAAM,CAAC;AAEnD,UAAM,YAAY,IAAI,cAAc,EAAE,kBAAkB,QAAQ;AAChE,UAAM,UAAU,IAAI,KAAK,CAAC,SAAS,GAAG;AAAA,MACpC,MAAM;AAAA,IACR,CAAC;AACD,UAAM,MAAM,IAAI,gBAAgB,OAAO;AAEvC,UAAM,MAAM,IAAI,MAAM;AACtB,QAAI,SAAS,MAAM;AACjB,YAAM,SAAS,SAAS,cAAc,QAAQ;AAC9C,YAAM,QAAQ;AACd,aAAO,QAAQ,KAAK,QAAQ;AAC5B,aAAO,SAAS,KAAK,SAAS;AAC9B,YAAM,MAAM,OAAO,WAAW,IAAI;AAClC,UAAI,CAAC,IAAK;AACV,UAAI,MAAM,OAAO,KAAK;AACtB,UAAI,YAAY,MAAM;AACtB,UAAI,SAAS,GAAG,GAAG,KAAK,OAAO,KAAK,MAAM;AAC1C,UAAI,UAAU,KAAK,GAAG,GAAG,KAAK,OAAO,KAAK,MAAM;AAEhD,aAAO,OAAO,CAAC,SAAS;AACtB,YAAI,CAAC,KAAM;AACX,cAAM,SAAS,IAAI,gBAAgB,IAAI;AACvC,cAAM,IAAI,SAAS,cAAc,GAAG;AACpC,UAAE,OAAO;AACT,UAAE,WAAW,GAAG,MAAM,MAAM,QAAQ,QAAQ,GAAG,CAAC;AAChD,UAAE,MAAM;AACR,YAAI,gBAAgB,MAAM;AAAA,MAC5B,GAAG,WAAW;AAEd,UAAI,gBAAgB,GAAG;AAAA,IACzB;AACA,QAAI,MAAM;AAAA,EACZ,GAAG,CAAC,MAAM,OAAO,MAAM,iBAAiB,CAAC;AAEzC,QAAM,aAAa,MAAM;AACvB,iBAAa,CAAC,SAAS;AACrB,UAAI,SAAS,OAAQ,QAAO;AAC5B,UAAI,SAAS,OAAQ,QAAO;AAC5B,aAAO;AAAA,IACT,CAAC;AAAA,EACH;AAGA,QAAM,qBAAqB,CACzB,MACA,WACA,MACA,MACA,aACA,iBACA,aACG;AACH,UAAM,cAAc;AAAA,MAClB,MAAM;AAAA,MACN,QAAQ,EAAE,KAAK,IAAI,OAAO,IAAI,MAAM,IAAI,QAAQ,GAAG;AAAA,IACrD;AAEA,UAAM,eAAe;AAAA,MACnB,iBAAiB,MAAM;AAAA,MACvB,QAAQ,MAAM;AAAA,MACd,cAAc;AAAA,IAChB;AAEA,UAAM,iBAAiB,cACrB,gBAAAD;AAAA,MAAC;AAAA;AAAA,QACC,SAAS;AAAA,QACT,QAAQ;AAAA,QACR,QAAQ,MAAM;AAAA,QACd,MAAK;AAAA;AAAA,IACP,IACE;AAEJ,UAAM,qBACJ,mBAAmB,YAAY,CAAC,gBAC9B,gBAAAA;AAAA,MAAC;AAAA;AAAA,QACC,GAAG;AAAA,QACH,QAAQ,MAAM;AAAA,QACd,iBAAgB;AAAA,QAChB,OAAO;AAAA,UACL,OAAO,QAAQ,SAAS,QAAQ,CAAC,CAAC;AAAA,UAClC,MAAM,MAAM;AAAA,UACZ,UAAU;AAAA,QACZ;AAAA;AAAA,IACF,IACE;AAEN,UAAM,aAAa;AAAA,MACjB,SAAS;AAAA,MACT,QAAQ,MAAM;AAAA,MACd,UAAU;AAAA,MACV,OAAO;AAAA,MACP,YAAY;AAAA,MACZ,QAAQ;AAAA,MACR,UAAU;AAAA,MACV,MAAM,EAAE,MAAM,MAAM,UAAU;AAAA,IAChC;AAEA,UAAM,aAAa;AAAA,MACjB,QAAQ,MAAM;AAAA,MACd,UAAU;AAAA,MACV,MAAM,EAAE,MAAM,MAAM,UAAU;AAAA,IAChC;AAGA,QAAI,iBAAiB,SAAS,SAAS,SAAS,WAAW;AACzD,cAAQ,MAAM;AAAA,QACZ,KAAK;AACH,iBACE,gBAAAC,MAAC,YAAU,GAAG,aACZ;AAAA,4BAAAD,KAAC,iBAAc,iBAAgB,OAAM,QAAQ,MAAM,YAAY;AAAA,YAC/D,gBAAAA,KAAC,SAAO,GAAG,YAAY;AAAA,YACvB,gBAAAA,KAAC,SAAO,GAAG,YAAY;AAAA,YACvB,gBAAAA,KAAC,WAAQ,cAAc,cAAc;AAAA,YACrC,gBAAAA,KAAC,UAAO;AAAA,YACP,WAAW,IAAI,CAAC,KAAK,MACpB,gBAAAA;AAAA,cAAC;AAAA;AAAA,gBAEC,SAAS;AAAA,gBACT,MAAM,MAAM,OAAO,IAAI,MAAM,OAAO,MAAM;AAAA,gBAC1C,SAAQ;AAAA,gBACR,QACE,MAAM,WAAW,SAAS,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC,IAAI;AAAA;AAAA,cAL1C;AAAA,YAOP,CACD;AAAA,YACA;AAAA,aACH;AAAA,QAEJ,KAAK;AACH,iBACE,gBAAAC,MAAC,aAAW,GAAG,aACb;AAAA,4BAAAD,KAAC,iBAAc,iBAAgB,OAAM,QAAQ,MAAM,YAAY;AAAA,YAC/D,gBAAAA,KAAC,SAAO,GAAG,YAAY;AAAA,YACvB,gBAAAA,KAAC,SAAO,GAAG,YAAY;AAAA,YACvB,gBAAAA,KAAC,WAAQ,cAAc,cAAc;AAAA,YACrC,gBAAAA,KAAC,UAAO;AAAA,YACP,WAAW,IAAI,CAAC,KAAK,MACpB,gBAAAA;AAAA,cAAC;AAAA;AAAA,gBAEC,MAAK;AAAA,gBACL,SAAS;AAAA,gBACT,QAAQ,MAAM,OAAO,IAAI,MAAM,OAAO,MAAM;AAAA,gBAC5C,aAAa;AAAA,gBACb,KAAK,EAAE,MAAM,MAAM,OAAO,IAAI,MAAM,OAAO,MAAM,EAAE;AAAA;AAAA,cAL9C;AAAA,YAMP,CACD;AAAA,YACA;AAAA,aACH;AAAA,QAEJ,KAAK;AACH,iBACE,gBAAAC,MAAC,aAAW,GAAG,aACb;AAAA,4BAAAD,KAAC,UACE,qBAAW,IAAI,CAAC,KAAK,MACpB,gBAAAC;AAAA,cAAC;AAAA;AAAA,gBAEC,IAAI,QAAQ,MAAM,EAAE,IAAI,CAAC;AAAA,gBACzB,IAAG;AAAA,gBACH,IAAG;AAAA,gBACH,IAAG;AAAA,gBACH,IAAG;AAAA,gBAEH;AAAA,kCAAAD;AAAA,oBAAC;AAAA;AAAA,sBACC,QAAO;AAAA,sBACP,WAAW,MAAM,OAAO,IAAI,MAAM,OAAO,MAAM;AAAA,sBAC/C,aAAa;AAAA;AAAA,kBACf;AAAA,kBACA,gBAAAA;AAAA,oBAAC;AAAA;AAAA,sBACC,QAAO;AAAA,sBACP,WAAW,MAAM,OAAO,IAAI,MAAM,OAAO,MAAM;AAAA,sBAC/C,aAAa;AAAA;AAAA,kBACf;AAAA;AAAA;AAAA,cAhBK;AAAA,YAiBP,CACD,GACH;AAAA,YACA,gBAAAA,KAAC,iBAAc,iBAAgB,OAAM,QAAQ,MAAM,YAAY;AAAA,YAC/D,gBAAAA,KAAC,SAAO,GAAG,YAAY;AAAA,YACvB,gBAAAA,KAAC,SAAO,GAAG,YAAY;AAAA,YACvB,gBAAAA,KAAC,WAAQ,cAAc,cAAc;AAAA,YACrC,gBAAAA,KAAC,UAAO;AAAA,YACP,WAAW,IAAI,CAAC,KAAK,MACpB,gBAAAA;AAAA,cAAC;AAAA;AAAA,gBAEC,MAAK;AAAA,gBACL,SAAS;AAAA,gBACT,QAAQ,MAAM,OAAO,IAAI,MAAM,OAAO,MAAM;AAAA,gBAC5C,aAAa;AAAA,gBACb,MAAM,aAAa,MAAM,EAAE,IAAI,CAAC;AAAA,gBAChC,SAAQ;AAAA;AAAA,cANH;AAAA,YAOP,CACD;AAAA,YACA;AAAA,aACH;AAAA,QAEJ;AACE,iBAAO;AAAA,MACX;AAAA,IACF;AAGA,YAAQ,MAAM;AAAA,MACZ,KAAK;AACH,eACE,gBAAAC,MAAC,YAAU,GAAG,aACZ;AAAA,0BAAAD,KAAC,iBAAc,iBAAgB,OAAM,QAAQ,MAAM,YAAY;AAAA,UAC/D,gBAAAA,KAAC,SAAO,GAAG,YAAY;AAAA,UACvB,gBAAAA,KAAC,SAAO,GAAG,YAAY;AAAA,UACvB,gBAAAA,KAAC,WAAQ,cAAc,cAAc;AAAA,UACrC,gBAAAA,KAAC,UAAO;AAAA,UACP;AAAA,UACD,gBAAAA,KAAC,OAAI,SAAS,MAAM,MAAM,MAAM,eAAe,QAAQ,CAAC,GAAG,GAAG,GAAG,CAAC,GAC/D,oBAAU,IAAI,CAAC,GAAG,UACjB,gBAAAA;AAAA,YAAC;AAAA;AAAA,cAEC,MAAM,MAAM,OAAO,QAAQ,MAAM,OAAO,MAAM;AAAA;AAAA,YADzC,QAAQ,KAAK;AAAA,UAEpB,CACD,GACH;AAAA,UACC;AAAA,WACH;AAAA,MAGJ,KAAK;AACH,eACE,gBAAAC,MAAC,aAAW,GAAG,aACb;AAAA,0BAAAD,KAAC,iBAAc,iBAAgB,OAAM,QAAQ,MAAM,YAAY;AAAA,UAC/D,gBAAAA,KAAC,SAAO,GAAG,YAAY;AAAA,UACvB,gBAAAA,KAAC,SAAO,GAAG,YAAY;AAAA,UACvB,gBAAAA,KAAC,WAAQ,cAAc,cAAc;AAAA,UACrC,gBAAAA,KAAC,UAAO;AAAA,UACP;AAAA,UACD,gBAAAA;AAAA,YAAC;AAAA;AAAA,cACC,MAAK;AAAA,cACL,SAAS;AAAA,cACT,QAAQ,MAAM;AAAA,cACd,aAAa;AAAA,cACb,KAAK,EAAE,MAAM,MAAM,cAAc;AAAA,cACjC,WAAW,EAAE,GAAG,EAAE;AAAA;AAAA,UACpB;AAAA,UACC;AAAA,WACH;AAAA,MAGJ,KAAK,QAAQ;AACX,cAAM,aAAa,UAAU,MAAM,EAAE;AACrC,eACE,gBAAAC,MAAC,aAAW,GAAG,aACb;AAAA,0BAAAD,KAAC,UACC,0BAAAC,MAAC,oBAAe,IAAI,YAAY,IAAG,KAAI,IAAG,KAAI,IAAG,KAAI,IAAG,KACtD;AAAA,4BAAAD;AAAA,cAAC;AAAA;AAAA,gBACC,QAAO;AAAA,gBACP,WAAW,MAAM;AAAA,gBACjB,aAAa;AAAA;AAAA,YACf;AAAA,YACA,gBAAAA;AAAA,cAAC;AAAA;AAAA,gBACC,QAAO;AAAA,gBACP,WAAW,MAAM;AAAA,gBACjB,aAAa;AAAA;AAAA,YACf;AAAA,aACF,GACF;AAAA,UACA,gBAAAA,KAAC,iBAAc,iBAAgB,OAAM,QAAQ,MAAM,YAAY;AAAA,UAC/D,gBAAAA,KAAC,SAAO,GAAG,YAAY;AAAA,UACvB,gBAAAA,KAAC,SAAO,GAAG,YAAY;AAAA,UACvB,gBAAAA,KAAC,WAAQ,cAAc,cAAc;AAAA,UACrC,gBAAAA,KAAC,UAAO;AAAA,UACP;AAAA,UACD,gBAAAA;AAAA,YAAC;AAAA;AAAA,cACC,MAAK;AAAA,cACL,SAAS;AAAA,cACT,QAAQ,MAAM;AAAA,cACd,aAAa;AAAA,cACb,MAAM,QAAQ,UAAU;AAAA;AAAA,UAC1B;AAAA,UACC;AAAA,WACH;AAAA,MAEJ;AAAA,MAEA,KAAK;AACH,eACE,gBAAAC,MAAC,gBAAc,GAAG,aAChB;AAAA,0BAAAD,KAAC,iBAAc,iBAAgB,OAAM,QAAQ,MAAM,YAAY;AAAA,UAC/D,gBAAAA;AAAA,YAAC;AAAA;AAAA,cACC,MAAK;AAAA,cACJ,GAAG;AAAA;AAAA,UACN;AAAA,UACA,gBAAAA;AAAA,YAAC;AAAA;AAAA,cACC,MAAK;AAAA,cACL,SAAS;AAAA,cACR,GAAG;AAAA;AAAA,UACN;AAAA,UACA,gBAAAA;AAAA,YAAC;AAAA;AAAA,cACC,QAAQ,EAAE,iBAAiB,MAAM;AAAA,cACjC,cAAc;AAAA;AAAA,UAChB;AAAA,UACA,gBAAAA,KAAC,UAAO;AAAA,UACR,gBAAAA,KAAC,WAAQ,MAAM,MAAM,MAAM,WAAW,MAAM,MAAM,eAAe;AAAA,WACnE;AAAA,MAGJ,KAAK;AACH,eACE,gBAAAC,MAAC,YACC;AAAA,0BAAAD;AAAA,YAAC;AAAA;AAAA,cACC,MAAM;AAAA,cACN,aAAa;AAAA,cACb,aAAa;AAAA,cACb,cAAc;AAAA,cACd,SAAS;AAAA,cACT,SAAS;AAAA,cACT,OAAO,CAAC,EAAE,MAAM,QAAQ,MACtB,GAAG,IAAI,OAAO,WAAW,KAAK,KAAK,QAAQ,CAAC,CAAC;AAAA,cAE/C,WAAW,EAAE,QAAQ,MAAM,UAAU;AAAA,cAEpC,oBAAU,IAAI,CAAC,GAAG,UACjB,gBAAAA;AAAA,gBAAC;AAAA;AAAA,kBAEC,MAAM,MAAM,OAAO,QAAQ,MAAM,OAAO,MAAM;AAAA;AAAA,gBADzC,QAAQ,KAAK;AAAA,cAEpB,CACD;AAAA;AAAA,UACH;AAAA,UACA,gBAAAA,KAAC,WAAQ,cAAc,cAAc;AAAA,UACrC,gBAAAA,KAAC,UAAO;AAAA,WACV;AAAA,MAGJ;AACE,eAAO;AAAA,IACX;AAAA,EACF;AAEA,MAAI,cAAc,WAAW,GAAG;AAC9B,WACE,gBAAAA,KAAC,SAAI,WAAU,2EACb,0BAAAC,MAAC,SAAI,WAAU,eACb;AAAA,sBAAAD,KAAC,OAAE,WAAU,iCAAgC,2CAE7C;AAAA,MACA,gBAAAC,MAAC,OAAE,WAAU,8BAA6B;AAAA;AAAA,QAC9B,MAAM;AAAA,QAAM;AAAA,QAAG,MAAM;AAAA,SACjC;AAAA,MACC,gBACC,gBAAAA;AAAA,QAAC;AAAA;AAAA,UACC,SAAS;AAAA,UACT,UAAU;AAAA,UACV,WAAU;AAAA,UAEV;AAAA,4BAAAD;AAAA,cAACE;AAAA,cAAA;AAAA,gBACC,WAAW,WAAW,iBAAiB,iBAAiB,EAAE;AAAA;AAAA,YAC5D;AAAA,YACC,iBAAiB,oBAAoB;AAAA;AAAA;AAAA,MACxC;AAAA,OAEJ,GACF;AAAA,EAEJ;AAEA,SACE,gBAAAD,MAAC,SACC;AAAA,oBAAAD;AAAA,MAAC;AAAA;AAAA,QACC,WAAW,MAAM;AAAA,QACjB;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,eAAe,CAAC,CAAC;AAAA,QACjB,cAAc;AAAA,QACd,eAAe;AAAA,QACf,eAAe,MAAM,aAAa,CAAC,SAAS;AAAA,QAC5C,mBAAmB,MAAM,iBAAiB,CAAC,aAAa;AAAA,QACxD,aAAa;AAAA,QACb,aAAa;AAAA,QACb,cAAc;AAAA;AAAA,IAChB;AAAA,IAGA,gBAAAA,KAAC,SAAI,WAAU,oBAAmB,KAAK,mBACrC,0BAAAA;AAAA,MAAC;AAAA;AAAA,QAEC,OAAM;AAAA,QACN,QAAO;AAAA,QAEN;AAAA,UACC,MAAM;AAAA,UACN;AAAA,UACA;AAAA,UACA,gBAAgB,KAAK;AAAA,UACrB;AAAA,UACA;AAAA,UACA;AAAA,QACF;AAAA;AAAA,MAZK,SAAS,MAAM,EAAE,IAAI,YAAY,UAAU,UAAU;AAAA,IAa5D,GACF;AAAA,IAGA,gBAAAC,MAAC,SAAI,WAAU,6BACb;AAAA,sBAAAA,MAAC,UAAK,WAAU,yDAAwD;AAAA;AAAA,QAClE,MAAM;AAAA,SACZ;AAAA,MACA,gBAAAA,MAAC,UAAK,WAAU,yDAAwD;AAAA;AAAA,QAClE,MAAM;AAAA,SACZ;AAAA,MACC,MAAM,WACL,gBAAAA,MAAC,UAAK,WAAU,4DAA2D;AAAA;AAAA,QACjE,MAAM;AAAA,SAChB;AAAA,MAED,MAAM,eAAe,MAAM,gBAAgB,UAC1C,gBAAAD,KAAC,UAAK,WAAU,gEACb,gBAAM,aACT;AAAA,MAEF,gBAAAC,MAAC,UAAK,WAAU,yDACb;AAAA,sBAAc;AAAA,QAAO;AAAA,SACxB;AAAA,MACC,iBACC,gBAAAA,MAAC,UAAK,WAAU,kEACb;AAAA,mBAAW;AAAA,QAAO;AAAA,SACrB;AAAA,OAEJ;AAAA,KACF;AAEJ;;;AGnhBS,gBAAAE,MAqBG,QAAAC,aArBH;AART,SAAS,YAAY;AAAA,EACnB;AAAA,EACA,YAAY;AACd,GAIG;AACD,SAAO,gBAAAD,KAAC,SAAI,WAAuB,UAAS;AAC9C;AAEO,SAAS,aAAa;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AAAA,EACA,aAAa,cAAc;AAAA,EAC3B,QAAQ;AACV,GAAsB;AACpB,MAAI,OAAO,WAAW,EAAG,QAAO;AAEhC,SACE,gBAAAA,KAAC,sBAAmB,OAClB,0BAAAA,KAAC,SAAI,WAAU,6BACZ,iBAAO,IAAI,CAAC,UACX,gBAAAA;AAAA,IAAC;AAAA;AAAA,MAEC,OAAO,MAAM;AAAA,MACb,WAAU;AAAA,MAEV,0BAAAC,MAAC,SAAI,WAAU,OACb;AAAA,wBAAAD,KAAC,QAAG,WAAU,sGACX,gBAAM,OACT;AAAA,QACA,gBAAAA,KAAC,OAAE,WAAU,sBAAsB,gBAAM,aAAY;AAAA,QACrD,gBAAAA;AAAA,UAAC;AAAA;AAAA,YACC;AAAA,YACA;AAAA,YACA;AAAA;AAAA,QACF;AAAA,SACF;AAAA;AAAA,IAdK,MAAM;AAAA,EAeb,CACD,GACH,GACF;AAEJ;;;AC7DO,IAAM,SAAS;AAAA,EACpB;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AACF;","names":["RefreshCw","Image","jsx","jsx","jsxs","RefreshCw","jsx","jsxs"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "csv-charts-ai",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"types": "./dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"import": "./dist/index.js",
|
|
10
|
+
"types": "./dist/index.d.ts"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"dist"
|
|
15
|
+
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsup",
|
|
18
|
+
"dev": "tsup --watch",
|
|
19
|
+
"release": "semantic-release"
|
|
20
|
+
},
|
|
21
|
+
"peerDependencies": {
|
|
22
|
+
"lucide-react": ">=0.400.0",
|
|
23
|
+
"react": "^18.0.0 || ^19.0.0",
|
|
24
|
+
"recharts": "^3.0.0"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@semantic-release/changelog": "^6.0.3",
|
|
28
|
+
"@semantic-release/commit-analyzer": "^13.0.1",
|
|
29
|
+
"@semantic-release/git": "^10.0.1",
|
|
30
|
+
"@semantic-release/github": "^12.0.6",
|
|
31
|
+
"@semantic-release/npm": "^13.1.5",
|
|
32
|
+
"@semantic-release/release-notes-generator": "^14.1.0",
|
|
33
|
+
"@types/react": "^19.2.7",
|
|
34
|
+
"lucide-react": "^0.556.0",
|
|
35
|
+
"react": "^19.2.1",
|
|
36
|
+
"recharts": "^3.5.1",
|
|
37
|
+
"semantic-release": "^25.0.3",
|
|
38
|
+
"tsup": "^8.5.0",
|
|
39
|
+
"typescript": "^5.9.3"
|
|
40
|
+
},
|
|
41
|
+
"publishConfig": {
|
|
42
|
+
"access": "public"
|
|
43
|
+
}
|
|
44
|
+
}
|