chart.tsx 3.5 KB
import { Card } from "antd";
import * as echarts from "echarts/core";
import {
  GridComponent,
  TooltipComponent,
  LegendComponent,
  TitleComponent,
} from "echarts/components";
import {
  BarChart,
  PieChart,
  LineChart,
  BarSeriesOption,
  PieSeriesOption,
  LineSeriesOption,
} from "echarts/charts";
import { CanvasRenderer } from "echarts/renderers";
import { useRef, useEffect, useState } from "react";
import { ChartComponentProps, ChartData } from "./getChartData";

// 注册必要的组件
echarts.use([
  GridComponent,
  TooltipComponent,
  LegendComponent,
  TitleComponent,
  BarChart,
  PieChart,
  LineChart,
  CanvasRenderer,
]);

type EChartsOption = echarts.ComposeOption<
  BarSeriesOption | PieSeriesOption | LineSeriesOption
>;

const tabList = [
  { key: "bar", label: "柱状图" },
  { key: "pie", label: "饼图" },
  { key: "line", label: "折线图" },
];

// 图表配置生成器
const getOption = (type: string, data: ChartData): EChartsOption => {
  const commonOption = {
    title: { text: `${tabList.find((t) => t.key === type)?.label}` },
    tooltip: { trigger: "item" },
  };

  // 提取数值数组
  const values = data.values.map((item) => item.value);

  switch (type) {
    case "bar":
      return {
        ...commonOption,
        xAxis: { type: "category", data: data.categories },
        yAxis: { type: "value" },
        series: [{ data: values, type: "bar" }],
      };
    case "pie":
      return {
        ...commonOption,
        series: [
          {
            type: "pie",
            data: data.categories.map((name, i) => ({
              name,
              value: values[i],
            })),
            radius: "50%",
          },
        ],
      };
    case "line":
      return {
        ...commonOption,
        xAxis: { type: "category", data: data.categories },
        yAxis: { type: "value" },
        series: [
          {
            data: values,
            type: "line",
            smooth: true,
            areaStyle: {},
          },
        ],
      };
    default:
      return {};
  }
};

export function ChartComponent({
  data = { categories: [], values: [] },
}: ChartComponentProps) {
  const [activeTabKey, setActiveTabKey] = useState("bar");
  const chartRef = useRef<HTMLDivElement>(null);
  const chartInstance = useRef<echarts.ECharts | null>(null);

  useEffect(() => {
    if (!chartRef.current) return;

    // 数据校验
    if (
      !data?.categories?.length ||
      !data?.values?.length ||
      data.values.some((item) => typeof item.value !== "number")
    ) {
      console.warn("Invalid chart data");
      return;
    }

    // 销毁旧实例
    if (chartInstance.current) {
      chartInstance.current.dispose();
    }

    // 初始化新图表
    chartInstance.current = echarts.init(chartRef.current);
    chartInstance.current.setOption(getOption(activeTabKey, data));

    // 窗口resize监听
    const resizeHandler = () => chartInstance.current?.resize();
    window.addEventListener("resize", resizeHandler);

    // 清理函数
    return () => {
      window.removeEventListener("resize", resizeHandler);
      chartInstance.current?.dispose();
    };
  }, [activeTabKey, data]);

  return (
    <Card
      style={{ width: "100%", minHeight: 400 }}
      tabList={tabList}
      activeTabKey={activeTabKey}
      onTabChange={setActiveTabKey}
      tabProps={{ size: "middle" }}
    >
      <div
        ref={chartRef}
        style={{
          width: "100%",
          height: 400,
          minHeight: 400,
        }}
      />
    </Card>
  );
}