2023快结束了,再次来打个卡,水个文,大家请见谅。
虽说是水字数,但还是希望这篇文章能够对有需要的童鞋有一些帮助。
产品提了一个新需求:
饼状图中间展示每一项的名称及对应的百分比,并且内容是随着鼠标再饼图上的滑动而动态展示
默认展示第一项数据的占比数据
本文介绍的就是后面这种方式实现,如果还有其他方式,欢迎大家在多多评论。
鼠标事件包括:
'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}
/>
到这里也就结束了,希望能够对有需要的朋友一些帮助,如果介绍的不对,或者代码中应该有更好的实现方式,请评论区留言指正,谢谢。