chart.tsx 4.7 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, ValueItem } 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 getValue = (item: number | ValueItem): number =>
    typeof item === "number" ? item : item.value;

  const getItemStyle = (item: number | ValueItem) =>
    typeof item === "object" ? item.itemStyle : undefined;

  switch (type) {
    case "bar":
      return {
        ...commonOption,
        xAxis: { type: "category", data: data.categories },
        yAxis: { type: "value" },
        series: [
          {
            data: data.values,
            type: "bar",
            itemStyle: {
              color: (params) => {
                const dataItem = params.data as ValueItem | number;
                if (typeof dataItem === "object" && dataItem.itemStyle?.color) {
                  return dataItem.itemStyle.color;
                }
                // 返回一个默认颜色,比如 ECharts 默认色系中的颜色
                return "#5470c6";
              },
            },
          },
        ],
      };
    case "pie":
      return {
        ...commonOption,
        series: [
          {
            type: "pie",
            data: data.values.map((item, i) => ({
              name: data.categories[i],
              value: getValue(item),
              itemStyle: getItemStyle(item),
            })),
            radius: "50%",
          },
        ],
      };
    case "line":
      return {
        ...commonOption,
        xAxis: { type: "category", data: data.categories },
        yAxis: { type: "value" },
        series: [
          {
            data: data.values.map(getValue),
            type: "line",
            smooth: true,
            areaStyle: {},
            itemStyle: {
              color: (params) => {
                const dataItem = params.data as ValueItem | number;
                if (typeof dataItem === "object" && dataItem.itemStyle?.color) {
                  return dataItem.itemStyle.color;
                }
                // 返回一个默认颜色,比如 ECharts 默认色系中的颜色
                return "#5470c6";
              },
            },
          },
        ],
      };
    default:
      return commonOption;
  }
};

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 !== "number" &&
          (typeof (item as ValueItem).value !== "number" ||
            ((item as ValueItem).itemStyle &&
              typeof (item as ValueItem).itemStyle?.color !== "string")),
      )
    ) {
      console.warn("Invalid chart data");
      return;
    }

    if (chartInstance.current) {
      chartInstance.current.dispose();
    }

    chartInstance.current = echarts.init(chartRef.current);
    chartInstance.current.setOption(getOption(activeTabKey, data));

    const resizeHandler = () => chartInstance.current?.resize();
    window.addEventListener("resize", resizeHandler);

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

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