使用 React 封装 antv/G2 饼图-分片
效果
组件代码
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);