summaryrefslogblamecommitdiff
path: root/makima/frontend/src/components/charts/ChartRenderer.tsx
blob: 276b170492f6b18086ea0a197b9ce2e373e8d3e8 (plain) (tree)




















































































































































































                                                                                   
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>
  );
}