diff options
Diffstat (limited to 'makima/frontend/src/components/charts')
| -rw-r--r-- | makima/frontend/src/components/charts/ChartRenderer.tsx | 181 |
1 files changed, 181 insertions, 0 deletions
diff --git a/makima/frontend/src/components/charts/ChartRenderer.tsx b/makima/frontend/src/components/charts/ChartRenderer.tsx new file mode 100644 index 0000000..276b170 --- /dev/null +++ b/makima/frontend/src/components/charts/ChartRenderer.tsx @@ -0,0 +1,181 @@ +import { useMemo } from "react"; +import { + LineChart, + Line, + BarChart, + Bar, + PieChart, + Pie, + AreaChart, + Area, + XAxis, + YAxis, + CartesianGrid, + Tooltip, + Legend, + ResponsiveContainer, + Cell, +} from "recharts"; +import type { ChartType } from "../../lib/api"; + +interface ChartRendererProps { + chartType: ChartType; + data: Record<string, unknown>[]; + title?: string; + config?: Record<string, unknown>; +} + +// Default color palette +const COLORS = [ + "#9bc3ff", + "#ff9b9b", + "#9bffb3", + "#ffeb9b", + "#d49bff", + "#9bfff0", + "#ff9beb", + "#b3ff9b", +]; + +export function ChartRenderer({ + chartType, + data, + title, + config, +}: ChartRendererProps) { + // Extract data keys (excluding 'name' which is used for labels) + const dataKeys = useMemo(() => { + if (data.length === 0) return []; + const keys = Object.keys(data[0]).filter((key) => key !== "name"); + return keys; + }, [data]); + + // Get colors from config or use defaults + const colors = (config?.colors as string[]) || COLORS; + + const renderChart = () => { + switch (chartType) { + case "line": + return ( + <LineChart data={data}> + <CartesianGrid strokeDasharray="3 3" stroke="#333" /> + <XAxis dataKey="name" stroke="#9bc3ff" fontSize={12} /> + <YAxis stroke="#9bc3ff" fontSize={12} /> + <Tooltip + contentStyle={{ + backgroundColor: "#111", + border: "1px solid #9bc3ff", + borderRadius: "0", + }} + /> + <Legend /> + {dataKeys.map((key, i) => ( + <Line + key={key} + type="monotone" + dataKey={key} + stroke={colors[i % colors.length]} + strokeWidth={2} + dot={{ fill: colors[i % colors.length] }} + /> + ))} + </LineChart> + ); + + case "bar": + return ( + <BarChart data={data}> + <CartesianGrid strokeDasharray="3 3" stroke="#333" /> + <XAxis dataKey="name" stroke="#9bc3ff" fontSize={12} /> + <YAxis stroke="#9bc3ff" fontSize={12} /> + <Tooltip + contentStyle={{ + backgroundColor: "#111", + border: "1px solid #9bc3ff", + borderRadius: "0", + }} + /> + <Legend /> + {dataKeys.map((key, i) => ( + <Bar key={key} dataKey={key} fill={colors[i % colors.length]} /> + ))} + </BarChart> + ); + + case "area": + return ( + <AreaChart data={data}> + <CartesianGrid strokeDasharray="3 3" stroke="#333" /> + <XAxis dataKey="name" stroke="#9bc3ff" fontSize={12} /> + <YAxis stroke="#9bc3ff" fontSize={12} /> + <Tooltip + contentStyle={{ + backgroundColor: "#111", + border: "1px solid #9bc3ff", + borderRadius: "0", + }} + /> + <Legend /> + {dataKeys.map((key, i) => ( + <Area + key={key} + type="monotone" + dataKey={key} + stroke={colors[i % colors.length]} + fill={colors[i % colors.length]} + fillOpacity={0.3} + /> + ))} + </AreaChart> + ); + + case "pie": + // For pie charts, use the first data key as value + const valueKey = dataKeys[0] || "value"; + return ( + <PieChart> + <Pie + data={data} + dataKey={valueKey} + nameKey="name" + cx="50%" + cy="50%" + outerRadius={80} + label={({ name, percent }) => + `${name}: ${((percent ?? 0) * 100).toFixed(0)}%` + } + labelLine={{ stroke: "#9bc3ff" }} + > + {data.map((_, i) => ( + <Cell key={i} fill={colors[i % colors.length]} /> + ))} + </Pie> + <Tooltip + contentStyle={{ + backgroundColor: "#111", + border: "1px solid #9bc3ff", + borderRadius: "0", + }} + /> + <Legend /> + </PieChart> + ); + + default: + return <div className="text-red-400">Unknown chart type: {chartType}</div>; + } + }; + + return ( + <div className="w-full"> + {title && ( + <h4 className="text-[#9bc3ff] font-mono text-sm mb-2">{title}</h4> + )} + <div className="h-64 w-full"> + <ResponsiveContainer width="100%" height="100%"> + {renderChart()} + </ResponsiveContainer> + </div> + </div> + ); +} |
