Echarts 饼状图百分比数据展示及鼠标事件

文章背景

2023快结束了,再次来打个卡,水个文,大家请见谅
虽说是水字数,但还是希望这篇文章能够对有需要的童鞋有一些帮助。

产品提了一个新需求

  • 饼状图中间展示每一项的名称及对应的百分比,并且内容是随着鼠标再饼图上的滑动而动态展示
  • 默认展示第一项数据的占比数据

实现目标

Echarts 饼状图百分比数据展示及鼠标事件_第1张图片
Echarts 饼状图百分比数据展示及鼠标事件_第2张图片
要实现方面的效果方式还是很多的,比如:

  • 可以提前先算出占比,通过title直接设置值,然后渲染
  • 通过echarts本身具备的能力进行计算,需要自己额外计算

本文介绍的就是后面这种方式实现,如果还有其他方式,欢迎大家在多多评论。

Echarts鼠标事件

鼠标事件包括:
'click''dblclick''mousedown''mousemove''mouseup''mouseover''mouseout''globalout''contextmenu'

具体使用请查看 官网,这里不作过多赘述。

如何实现

下面代码就是当前自己定义的一个通用组件,写的不好,请大家多多包涵指正。

定义饼图组件

import { useEffect, useRef } from 'react';
import * as echarts from 'echarts';

import './index.less';

import { digitalDivideTothousands, randomSort } from '@/utils/utils.public'

import EmptyModal from '@/components/emptyModal';
import { useDebounceFn } from 'ahooks';


interface seriesDataType {
  name: string
  value: string | number | null
}

interface propsType {
  id: string
  data: seriesDataType[],
  optionConfig?: any,
  notNeedDipose?: boolean//不需要每次都刷新
}

interface legendDataType {
  legendData: string[],
  legendDataObj: any
}


定义组件
const CustomPieModal = ({ id, data, optionConfig, notNeedDipose }: propsType) => {

  const myChart = useRef<echarts.ECharts | null>(null);

  const getLegendDataFn = (): legendDataType => {
    const legendData: string[] = [];
    let legendDataObj: any = {};
    if (data && Array.isArray(data) && data.length > 0) {
      data.forEach((item: seriesDataType, index: number) => {
        if (item['name'] !== undefined && item['name'] !== null && Number(item['value']) >= 0) {
          const tempNum = Number.isInteger(Number(item['value'])) ? item['value'] : parseFloat(String(item['value'])).toFixed(2);
          legendData.push(item['name']);
          legendDataObj[item['name']] = tempNum;
        }
      });
    }
    return {
      legendData,
      legendDataObj
    };
  };

  const getPropsDataFn = () => data.filter(({ value }: seriesDataType) => Number(value) >= 0);

  /*
  	关注重点:
	防抖-防止鼠标多次划过界面或随意改变窗口大小而导致 echarts 重复渲染
  */
  const { run: updateInitOption } = useDebounceFn((initOption: any) => {

    if (myChart.current) {
      initOption && myChart.current.setOption(initOption, true);
		
	  // 屏幕监听,自适应宽度
      window.addEventListener('resize', () => myChart.current?.resize());

      //渲染结束再操作
      myChart.current?.dispatchAction({
        type: "highlight",
        seriesIndex: 0,
        dataIndex: 0,
      });


      myChart.current?.on("mouseover", ({ name, seriesIndex, dataIndex, ...a }) => {

        const data = getPropsDataFn();
        data.forEach(({ name, value }, index: number) => {
          myChart.current?.dispatchAction({
            type: "downplay",
            name,
            seriesIndex: 0,
            dataIndex: index,
          });
        })

        myChart.current?.dispatchAction({
          type: "highlight",
          name,
          seriesIndex: seriesIndex,
          dataIndex: dataIndex,
        });
      });

      let b = false;
      let currentSelected: any = null;
      const dataObj: any = {}

      myChart.current?.on("mouseout", ({ name, seriesIndex, dataIndex }) => {
        myChart.current?.dispatchAction({
          type: "downplay",
          name,
          seriesIndex: seriesIndex,
          dataIndex: dataIndex,
        });


        b = false;

        if (currentSelected && Object.keys(currentSelected)?.length > 0) {
          Object.keys(currentSelected).forEach((name2: string) => {
            if (currentSelected[name2] && !b) {
              b = true;
              myChart.current?.dispatchAction({
                type: "highlight",
                seriesIndex: 0,
                dataIndex: dataObj[name2],
              });
            }
          })
        } else {
          myChart.current?.dispatchAction({
            type: "highlight",
            name: getPropsDataFn()[0].name,
            seriesIndex: 0,
            dataIndex: 0,
          });
        }
      });

      myChart.current?.on("legendselectchanged", ({ selected }: any) => {
        currentSelected = selected;

        b = false;

        const data = getPropsDataFn();
        data.forEach(({ name, value }, index: number) => dataObj[name] = index)

        Object.keys(selected).forEach((name1: string) => {
          myChart.current?.dispatchAction({
            type: "downplay",
            name: name1,
            seriesIndex: 0,
            dataIndex: dataObj[name1],
          });
        })

        Object.keys(selected).forEach((name2: string) => {
          if (selected[name2] && !b) {
            b = true;
            myChart.current?.dispatchAction({
              type: "highlight",
              seriesIndex: 0,
              dataIndex: dataObj[name2],
            });
          }
        })

      })
    }
  },
    { wait: 100 },
  );


  const watchDataToChangeOptionsFn = () => {
    const initOption = {
      backgroundColor: "#FFFFFF",
      color: ['#50DEB1', '#3E95FB', '#F99057', '#8D4EDA', '#FD6E65', '#FF9A2E', '#33D1C9', '#A67FDD'],
      title: {
        show: optionConfig?.titleShow === false ? false : true,
        text: `单位:${optionConfig?.['titleUnit'] || 'kgCO2e'}`,
        textStyle: {
          color: 'rgba(78, 89, 105, 1)',
          letterSpacing: '0em',
          fontWeight: 400,
          fontSize: 13,
          fontFamily: 'PingFang SC',
        }, // 副标题样式
        top: 0,
        left: -3,
      },
      tooltip: {
        show: false,
      },
      legend: [
        {
          type: 'scroll',
          pageIconColor: '#29CCA0',
          pageIconInactiveColor: '#f5f5f5',
          orient: 'vertical',
          x: "left",
          left: optionConfig?.legendRight ? null : '50%',
          right: optionConfig?.legendRight ?? null,
          height: optionConfig?.legendHeight ?? '90%',
          top: 'center',
          align: 'left',
          itemGap: 20,
          itemWidth: 8,
          itemHeight: 8,
          icon: 'circle',
          data: getLegendDataFn()['legendData'],
          textStyle: {
            color: '#77899c',
            rich: {
              uname: { color: '#4E5969', fontSize: 12, width: 'auto', align: 'left', padding: [0, 5, 0, 5], },
              unum: { color: '#101513', fontSize: 14, align: 'left', lineHeight: 24, padding: [0, 5, 0, 5], }
            }
          },
          formatter(name: string) {
            const legendFormatDataObj = getLegendDataFn()['legendDataObj']
            return `{uname|${name}}\n{unum|${legendFormatDataObj ? legendFormatDataObj[name] : '-'}}`;
          }
        }
      ],
      series: [
        {
          type: "pie",
          clockwise: false, //饼图的扇区是否是顺时针排布
          minAngle: 2, //最小的扇区角度(0 ~ 360)
          radius: optionConfig?.seriesRadius ?? ['50%', '75%'],
          center: optionConfig?.seriesCenter ?? ["24%", "55%"],
          avoidLabelOverlap: false,
          legendHoverLink: false,
          itemStyle: {
            borderRadius: 4,
            borderColor: '#fff',
            borderWidth: 2
          },
          label: {
            show: false,
            position: "center",
            formatter: "{value|{d}%}\n{text|{b}}",
            rich: {
              value: {
                color: '#1D2129',
                fontSize: 22,
                lineHeight: 30,
                fontWeight: 400,
                fontFamily: 'YouSheBiaoTiHei'
              },
              text: {
                color: '#4E5969',
                fontSize: 14,
                lineHeight: 18,
                fontWeight: 400,
                align: "center",
              },
            },
            emphasis: {
              show: true,
              textStyle: {
                fontSize: 16,
              },
            },
          },
          animationEasing: 'elasticOut',
          animationType: 'scale',
          data: getPropsDataFn()
        },
      ],
      ...optionConfig
    };

    // 此处需要延迟加载,否则可能导致超出父容器
    let chartDom: any = document.getElementById(id);
    if (chartDom) {
      myChart.current = echarts.getInstanceByDom(chartDom) || null;
      if (!notNeedDipose && myChart.current) {
        myChart.current.dispose();
      }

      setTimeout(() => {
        if (!myChart.current) {
          myChart.current = echarts.init(chartDom);
        }
        updateInitOption(initOption)

      }, 0);

    };
  }


  useEffect(() => {
    if (data && Array.isArray(data) && data.length > 0) {
      watchDataToChangeOptionsFn()
    }
  }, [data]);

  return (
    <>
      {
        data && Array.isArray(data) && data.length > 0 ? (
          <div id={id} style={{ width: '100%', height: '100%' }}></div>
        ) : (
          <EmptyModal />
        )
      }

    </>
  )
};

export default CustomPieModal;

父组件使用

<CustomPieEcharts
  id={'echarts01'}
  data={[
  	{name: '数据1', value: 100},
  	{name: '数据2', value: 200},
  ]}
  notNeedDipose={true}
/>

最后

到这里也就结束了,希望能够对有需要的朋友一些帮助,如果介绍的不对,或者代码中应该有更好的实现方式,请评论区留言指正,谢谢。

你可能感兴趣的:(Echarts,echarts,前端,javascript)