Skip to content

echart 开发3d 饼图

Updated: at 01:22 AM
import React, { useEffect, useRef } from "react";
import * as echarts from "echarts/core";
// import { SVGRenderer } from "echarts/renderers";
import "echarts-gl";
// echarts.use([SVGRenderer]);
type dataType = {
  name: string;
  value: number;
};
interface IOptionsData {
  data: Array<dataType>;
  width: React.CSSProperties['width'];
  height: React.CSSProperties['height'];
  left?: number;
  top?: number;
}
// 生成扇形的曲面参数方程
function getParametricEquation(
  startRatio: number,
  endRatio: number,
  isSelected: boolean,
  isHovered: boolean,
  k: number,
  h: number
) {
  let midRatio = (startRatio + endRatio) / 2;
  let startRadian = startRatio * Math.PI * 2;
  let endRadian = endRatio * Math.PI * 2;
  let midRadian = midRatio * Math.PI * 2;
  isSelected = false;
  k = typeof k !== "undefined" ? k : 1 / 3;
  let offsetX = isSelected ? Math.sin(midRadian) * 0.1 : 0;
  let offsetY = isSelected ? Math.cos(midRadian) * 0.1 : 0;
  let hoverRate = isHovered ? 1.05 : 1;
  return {
    u: {
      min: -Math.PI,
      max: Math.PI * 3,
      step: Math.PI / 32,
    },
    v: {
      min: 0,
      max: Math.PI * 2,
      step: Math.PI / 20,
    },
    x: function (u: number, v: number) {
      if (u < startRadian) {
        return (
          offsetX + Math.cos(startRadian) * (1 + Math.cos(v) * k) * hoverRate
        );
      }
      if (u > endRadian) {
        return (
          offsetX + Math.cos(endRadian) * (1 + Math.cos(v) * k) * hoverRate
        );
      }
      return offsetX + Math.cos(u) * (1 + Math.cos(v) * k) * hoverRate;
    },
    y: function (u: number, v: number) {
      if (u < startRadian) {
        return (
          offsetY + Math.sin(startRadian) * (1 + Math.cos(v) * k) * hoverRate
        );
      }
      if (u > endRadian) {
        return (
          offsetY + Math.sin(endRadian) * (1 + Math.cos(v) * k) * hoverRate
        );
      }
      return offsetY + Math.sin(u) * (1 + Math.cos(v) * k) * hoverRate;
    },
    z: function (u: number, v: number) {
      if (u < -Math.PI * 0.5) {
        return Math.sin(u);
      }
      if (u > Math.PI * 2.5) {
        return Math.sin(u) * h * 0.1;
      }
      return Math.sin(v) > 0 ? 1 * h * 0.1 : -1;
    },
  };
}
const Pie3d: React.FC<IOptionsData> = ({ data, width, height,left = -50, top = -60 }) => {
  // 生成模拟 3D 饼图的配置项
  function getPie3D(pieData: string | any[], internalDiameterRatio: number) {
    let series = [];
    let sumValue = 0;
    let startValue = 0;
    let endValue = 0;
    let legendData = [];
    let k =
      typeof internalDiameterRatio !== "undefined"
        ? (1 - internalDiameterRatio) / (1 + internalDiameterRatio)
        : 1 / 3;
    // 为每一个饼图数据,生成一个 series-surface 配置
    for (let i = 0; i < pieData.length; i++) {
      sumValue += pieData[i].value;
      let seriesItem: any = {
        name:
          typeof pieData[i].name === "undefined"
            ? `series${i}`
            : pieData[i].name,
        type: "surface",
        parametric: true,
        wireframe: {
          show: false,
        },
        pieData: pieData[i],
        pieStatus: {
          selected: false,
          hovered: false,
          k: 1 / 10,
        },
      };
      if (typeof pieData[i].itemStyle != "undefined") {
        let itemStyle:any = {};
        typeof pieData[i].itemStyle.color != "undefined"
          ? (itemStyle.color = pieData[i].itemStyle.color)
          : null;
        typeof pieData[i].itemStyle.opacity != "undefined"
          ? (itemStyle.opacity = pieData[i].itemStyle.opacity)
          : null;
        seriesItem.itemStyle = itemStyle;
      }
      series.push(seriesItem);
    }
    // 使用上一次遍历时,计算出的数据和 sumValue,调用 getParametricEquation 函数,
    // 向每个 series-surface 传入不同的参数方程 series-surface.parametricEquation,也就是实现每一个扇形。
    for (let i = 0; i < series.length; i++) {
      endValue = startValue + series[i].pieData.value;
      series[i].pieData.startRatio = startValue / sumValue;
      series[i].pieData.endRatio = endValue / sumValue;
      series[i].parametricEquation = getParametricEquation(
        series[i].pieData.startRatio,
        series[i].pieData.endRatio,
        false,
        false,
        k,
        series[i].pieData.value
      );
      startValue = endValue;
      legendData.push(series[i].name);
    }

    let option = {
      animation: true,
      xAxis3D: {
        min: -1,
        max: 1,
      },
      yAxis3D: {
        min: -1,
        max: 1,
      },
      zAxis3D: {
        min: -1,
        max: 1,
      },
      grid3D: {
        show: false,
        boxHeight: 1,
        left,
        top,
        width,
        height,
        viewControl: {
          distance: 280,
          alpha: 18,
          autoRotate: true,
          rotateSensitivity: [1, 0],
          zoomSensitivity: 0,
          panSensitivity: 0,
        },
      },
      series: series,
    };
    return option;
  }
  const chartRef = useRef<HTMLDivElement>(null);
  const chartInstance = useRef<echarts.EChartsType | null>(null);
  useEffect(() => {
    if (chartRef && chartRef.current) {
      const options: any = getPie3D(data, 0.8);
      chartInstance.current = echarts.init(chartRef.current, null, {
        devicePixelRatio: window.devicePixelRatio
      });
      chartInstance.current.setOption(options);
    }
    return () => {
      chartInstance && chartInstance.current?.dispose();
    };
  }, []);

  return <div ref={chartRef} style={{ height: "100%", width: "100%"}} />;
};
export default Pie3d;