使用 React hooks 封装 antv/G2 饼图-分片

1,102 阅读1分钟

使用 React 封装 antv/G2 饼图-分片

效果

image.png

image.png

image.png

组件代码

import type { FC } from 'react';
import { useEffect, useRef } from 'react';
import DataSet from '@antv/data-set';
import { Chart } from '@antv/g2';
import { nowSize } from '@/utils/utils';

const { DataView } = DataSet;

type DateType = {
  type: string;
  value: number;
};

type PiePercentProps = {
  total: number;
  data: DateType[];
  title: string;
  color: string[];
  onClick?: () => void;
};

const PiePercent: FC<PiePercentProps> = ({ total, data = [], title, color = ['#4FC0A6', '#333F5C'], onClick }) => {
  const chartRef = useRef<HTMLDivElement>(null);

  const init = () => {
    const chart = new Chart({
      container: chartRef.current!,
      autoFit: true,
      padding: [4],
    });
    chart.legend(false);
    chart.tooltip({
      showTitle: false,
      showMarkers: false,
      domStyles: {
        'g2-tooltip': {
          background: '#0c1427',
          color: '#fff',
          textAlign: 'left',
          fontSize: `${nowSize(16)}px`,
          lineHeight: `${nowSize(16)}px`,
          padding: `${nowSize(10)}px`,
          boxShadow: '0px 3px 7px 3px #0c1427',
          border: '1px solid #27425d',
        },
      },
      customContent: (_, row) => {
        return total ? `${row[0]?.data?.type}${row[0]?.data.value} 台` : '无数据';
      },
    });

    chart.on('element:click', () => {
      onClick?.();
    });

    const userDv = new DataView();
    userDv.source(data).transform({
      type: 'percent',
      field: 'value',
      dimension: 'type',
      as: 'percent',
    });

    const pieView = chart.createView();

    pieView.data(userDv.rows);
    pieView.scale('percent', {
      formatter: (val) => {
        return (val * 100).toFixed(2) + '%';
      },
    });
    pieView.coordinate('theta', {
      radius: 0.9,
      innerRadius: 0.75,
    });
    pieView.interval().adjust('stack').position('percent').color('type', color);
    pieView
      .annotation()
      .text({
        position: ['50%', '50%'],
        content: () => total,
        style: {
          fontSize: nowSize(26),
          fill: '#EFF6FF',
          textAlign: 'center',
        },
        offsetY: -8,
      })
      .text({
        position: ['50%', '50%'],
        content: () => title,
        style: {
          fontSize: nowSize(12),
          fill: '#9AAEDC',
          textAlign: 'center',
        },
        offsetY: 12,
      });

    // ==============
    const dv = new DataView();
    const dvdata = [];
    for (let i = 0; i < 8; i++) {
      dvdata.push({
        type: i + '',
        value: 1,
      });
    }
    dv.source(dvdata).transform({
      type: 'percent',
      field: 'value',
      dimension: 'type',
      as: 'percent',
    });

    const bgView = chart.createView();
    bgView.coordinate('theta', {
      radius: 0.95,
      innerRadius: 0.6,
    });

    bgView.data(dv.rows);
    bgView
      .interval()
      .adjust('stack')
      .position('percent')
      .color('type', ['rgba(255, 255, 255,0)'])
      .style({
        stroke: '#0f1825',
        lineWidth: 2,
        cursor: 'pointer',
      })
      .tooltip(false);

    const view2 = chart.createView();
    view2.data(dv.rows);
    view2.coordinate('theta', {
      innerRadius: 0.99,
      // radius: 1,
    });

    // =============
    view2
      .interval()
      .position('percent')
      .color('#213F7F')
      .size(1)
      .animate({
        appear: {
          animation: 'wave-in',
          duration: 1000,
          delay: 1000,
        },
      });
    chart.render();
  };

  useEffect(() => {
    if (data.length) {
      init();
    }
  }, [data]);

  return <div key={Math.random()} ref={chartRef} style={{ width: '50%', height: '50%' }} />;
};

export default PiePercent;

使用 PiePercent 组件

import { memo, useEffect, useState } from 'react';
import { observer } from 'mobx-react';
import PiePercent from '@/components/Charts/PiePercent';
import styles from './index.less';
const MemoPiePercent = memo(PiePercent);

type ChartsListType = {
  key: number;
  total: number;
  data: { type: string; value: number }[];
  title: string;
  color: string[];
  onClick: () => void;
};

const Com = () => {
    
  const [chartDataList] = useState<ChartsListType[]>([
       {
        key: 1,
        total: 16,
        data: [
          { type: '在线', value: 10 },
          {
            type: '离线',
            value: 6,
          },
        ],
        title: '总数',
        color: ['#4FC0A6', '#333F5C'],
        onClick: () => callback('xxx'),
      },
  ]);
 
 return <div className={styles.dountChart}>
        {chartDataList?.map((item) => (
          <MemoPiePercent
            key={item.key}
            total={item.total}
            data={item.data}
            title={item.title}
            color={item.color}
            onClick={item.onClick}
          />
        ))}
 </div>

}

export default observer(Com);