效果
技术
React + TypeScript + Less + Echarts
代码块
import * as echarts from "echarts";
import React, { useEffect, useRef } from "react";
import "echarts-gl";
import "./index.less";const LeftEcharts = () => {const chartDom = useRef(null);useEffect(() => {const myChart = echarts.init(chartDom.current);// 数据源const optionsData: any = [{name: "IT运营管控团队",value: 1000,itemStyle: {color: "#dd4b3d",},},{name: "业务支撑团队",value: 600,itemStyle: {color: "#dd9c3c",},},{name: "计费结算团队",value: 900,itemStyle: {color: "#f6bb50",},},{name: "数据应用运营团队",value: 800,itemStyle: {color: "#5ec7f8",},},{name: "Paas组件运营团队",value: 400,itemStyle: {color: "#31dda1",},},{name: "云数安全团队",value: 300,itemStyle: {color: "#637aff",},},];// 生成扇形的曲面参数方程,用于 series-surface.parametricEquationfunction getParametricEquation(startRatio,endRatio,isSelected,isHovered,k,h) {// 计算let midRatio = (startRatio + endRatio) / 2;let startRadian = startRatio * Math.PI * 2;let endRadian = endRatio * Math.PI * 2;let midRadian = midRatio * Math.PI * 2;// 如果只有一个扇形,则不实现选中效果。// if (startRatio === 0 && endRatio === 1) {// isSelected = false;// }isSelected = false;// 通过扇形内径/外径的值,换算出辅助参数 k(默认值 1/3)k = typeof k !== "undefined" ? k : 1 / 3;// 计算选中效果分别在 x 轴、y 轴方向上的位移(未选中,则位移均为 0)let offsetX = isSelected ? Math.sin(midRadian) * 0.1 : 0;let offsetY = isSelected ? Math.cos(midRadian) * 0.1 : 0;// 计算高亮效果的放大比例(未高亮,则比例为 1)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, v) {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, v) {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, v) {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;},};}// 生成模拟 3D 饼图的配置项function getPie3D(pieData: any, internalDiameterRatio) {let series: any = [];let sumValue = 0;let startValue = 0;let endValue = 0;let legendData: any = [];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);}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);}return series;}const series: any = getPie3D(optionsData, 0.6);series.push({name: "pie2d",type: "pie",label: {opacity: 1,fontSize: 14,lineHeight: 20,textStyle: {fontSize: 14,color: "#fff",},show: false,position: "center",},labelLine: {length: 10,length2: 10,show: false,},startAngle: 2, //起始角度,支持范围[0, 360]。clockwise: false, //饼图的扇区是否是顺时针排布。上述这两项配置主要是为了对齐3d的样式radius: ["50%", "60%"],center: ["62%", "50%"],data: optionsData,itemStyle: {opacity: 0,},});// 准备待返回的配置项,把准备好的 legendData、series 传入。const option = {legend: {show: true, // 显示图例tooltip: {show: true, // 显示图例的提示信息},orient: "vertical", // 图例的排列方向data: ["IT运营管控团队", "业务支撑团队", "计费结算团队", "数据应用运营团队", "Paas组件运营团队", '云数安全团队'], // 图例的内容top: 20, // 图例距离顶部的距离itemGap: 10, // 图例项之间的间距itemHeight: 20, // 图例项的高度itemWidth: 24, // 图例项的宽度right: "5%", // 图例距离右边的距离textStyle: { // 图例的文本样式color: "#fff", // 文本颜色fontSize: 10, // 文本字体大小rich: {name: {width: 60, // 名称部分的宽度fontSize: 14, // 名称部分字体大小color: "#B0D8DF", // 名称部分颜色fontFamily: "Source Han Sans CN", // 名称部分字体},value: {width: 50, // 数值部分的宽度fontSize: 4, // 数值部分字体大小padding: [0, 5, 0, 5], // 数值部分的内边距color: "#fff", // 数值部分颜色fontFamily: "Source Han Sans CN", // 数值部分字体},A: {fontSize: 20, // A部分的字体大小color: "#B0D8DF", // A部分颜色fontFamily: "Source Han Sans CN", // A部分字体},rate: {width: 60, // 比率部分的宽度fontSize: 14, // 比率部分字体大小padding: [0, 5, 0, 10], // 比率部分的内边距color: "#579ed2", // 比率部分颜色fontFamily: "Source Han Sans CN", // 比率部分字体},},},formatter: function (name) { // 格式化图例项的显示内容let total = 0; // 总值let target; // 目标值for (let i = 0; i < optionsData.length; i++) {total += optionsData[i].value; // 计算总值if (optionsData[i].name === name) { // 查找目标值target = optionsData[i].value;}}let arr = ["{name|" + name + "}", // 名称"{value|" + "}", // 数值(未赋值需补充)"{rate|" + ((target / total) * 100).toFixed(1) + "%}", // 比率];return arr.join(""); // 返回格式化后的字符串},},animation: true, // 开启动画效果tooltip: {backgroundColor: "rgba(64, 180, 176, 0.6)", // 提示框的背景颜色borderColor: "rgba(64, 180, 176, 0.6)", // 提示框的边框颜色textStyle: {color: "#fff", // 提示文本颜色fontSize: 24, // 提示文本字体大小},formatter: (params) => { // 格式化提示框内容if (params.seriesName !== "mouseoutSeries" &¶ms.seriesName !== "pie2d" // 排除特定系列) {return `${params.seriesName}<br/><span style="display:inline-block;margin-right:5px;border-radius:10px;width:10px;height:10px;background-color:${params.color};"></span>${option.series[params.seriesIndex].pieData.value + "万人"}`; // 返回系列名称和数值}},},labelLine: {show: true, // 显示标签连接线lineStyle: {color: "#7BC0CB", // 标签连接线颜色},normal: {show: true, // 正常状态显示length: 10, // 连接线长度length2: 10, // 连接线第二段长度},},label: {show: true, // 显示标签position: "outside", // 标签位置formatter: "{b} \n{c}\n{d}%", // 标签格式textStyle: {color: "rgba(176, 216, 223, 1)", // 标签文本颜色fontSize: 24, // 标签字体大小},},xAxis3D: {min: -1, // x轴最小值max: 1, // x轴最大值},yAxis3D: {min: -1, // y轴最小值max: 1, // y轴最大值},zAxis3D: {min: -1, // z轴最小值max: 1, // z轴最大值},grid3D: {show: false, // 是否显示3D网格boxHeight: 1, // 3D盒子的高度left: -40, // 3D图形左边距top: -10, // 3D图形顶部边距width: "50%", // 3D图形宽度viewControl: {distance: 280, // 视距alpha: 20, // 视角的俯仰角beta: 15, // 视角的旋转角autoRotate: true, // 是否自动旋转rotateSensitivity: 1, // 旋转灵敏度zoomSensitivity: 0, // 缩放灵敏度panSensitivity: 0, // 平移灵敏度},},series: series, // 数据系列};myChart.setOption(option);}, []);return (<div className='left-echarts'><div className='left-top-nav'>团队概况</div><div style={{ width: "496px", height: "270px", position: "relative" }}><div ref={chartDom} style={{ width: "100%", height: "100%", zIndex: "5" }}></div><div className="bg"></div></div></div>);
};export default LeftEcharts;