最近項目中開始使用ECharts做圖表,在React+Antd中使用Echart還是遇到了很多坑,希望記錄下解決辦法可以幫助到大家。由於初次使用,很多代碼優化空間很大,希望大家能幫我提出,感恩~
最終效果
1、實現點擊折線圖上的點聯動兩個餅圖數據
2、處理ECharts餅圖數據爲0或者是空時餅圖消失問題
如何在react中使用ECharts
1、在render中添加div
<div id='echartLine' style={{ width: '100%', height: 300, margin: '24px 0' }} />
<div id='pieTotal' style={{ width: '100%', height: 300, margin: '24px 0' }} />
<div id='pieReject' style={{ width: '100%', height: 300, margin: '24px 0' }} />
2、 在對ECharts圖表進行init
// 餅圖初始option
const initPieOption = {
// tooltip: {
// show: false,
// },
dataset: {
dimensions: ['desc', 'type', 'value'],
source: [{ desc: '', type: '', value: 1 }], // 當無數據時,給餅圖個初始數據
},
grid: {
left: '3%',
right: '3%',
bottom: '0%',
containLabel: true,
},
series: [
{ type: 'pie',
radius: ['75%', '45%'],
stillShowZeroSum: false,
itemStyle: {
color: '#e7e7e7',
},
label: {
normal: {
show: true,
position: 'center',
formatter: function () {
var result = ''
result = '暫無數據'
return result
},
textStyle: {
fontSize: 16,
color: '#e2e2e2',
},
},
},
},
],
}
// 初始化圖表
initChart = () => {
// const { trendList } = this.state
// 折線圖
var lineChart = echarts.init(document.getElementById('echartLine'))
// 建議將ECharts圖表實例進行儲存,而不是每次數據變化都進行實例初始化
this.setState({ lineChart })
lineChart.setOption({
title: {
text: '趨勢',
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross',
},
},
legend: {
formatter: function (name) {
switch (name) {
case 'total':
return '總量 ' + name
case 'passCount':
return '通過量 ' + name
case 'rejectCount':
return '拒絕量 ' + name
}
},
},
dataset: {
// 這裏指定了維度名的順序,從而可以利用默認的維度到座標軸的映射。
// 如果不指定 dimensions,也可以通過指定 series.encode 完成映射,參見後文。
dimensions: ['date', 'total', 'passCount', 'rejectCount'],
source: [],
},
xAxis: { type: 'category' },
yAxis: {
type: 'value',
},
grid: {
left: '3%',
right: '6%',
bottom: '0%',
containLabel: true,
},
series: [
{ type: 'line' },
{ type: 'line' },
{ type: 'line' },
],
})
// 餅圖總量
var pieChartTotal = echarts.init(document.getElementById('pieTotal'))
this.setState({ pieChartTotal })
// 餅圖拒絕量
var pieChartReject = echarts.init(document.getElementById('pieReject'))
this.setState({ pieChartReject })
// 使圖表自適應div大小,防止圖表溢出
window.addEventListener('resize', function () {
lineChart.resize()
pieChartTotal.resize()
pieChartReject.resize()
})
}
3、當數據變化重新渲染圖表
// 獲取趨勢圖
fetchBaseTrend = async () => {
const res = await api.getBaseTrend()
this.setState({
trendList: res,
}, () => {
// 獲取到數據後再去調用渲染圖表函數
this.generateChart()
})
}
generateChart = () => {
const { trendList, lineChart, pieChartTotal, pieChartReject } = this.state
lineChart.setOption({
dataset: {
// 這裏指定了維度名的順序,從而可以利用默認的維度到座標軸的映射。
// 如果不指定 dimensions,也可以通過指定 series.encode 完成映射,參見後文。
dimensions: ['date', 'total', 'passCount', 'rejectCount'],
source: trendList,
},
})
// 由於我對餅圖無數據做了處理,所以每次setOption之前都要先執行clear(),防止之前setOption內的灰色背景屬性仍生效
pieChartTotal.clear()
pieChartReject.clear()
// 默認用數據加載第一條渲染餅圖,可能存在無數據情況,因此需要判斷
pieChartTotal.setOption(trendList[0] && !!trendList[0].totalRatio.length ? this.PieOption('total') : initPieOption)
pieChartReject.setOption(trendList[0] && !!trendList[0].rejectRatio.length ? this.PieOption('reject') : initPieOption)
}
4、如何點擊折線圖渲染餅圖
此部分代碼優化空間極大,但我改了很久還沒有找到更好的辦法,希望大家幫我提下意見,感恩~
generateChart = () => {
lineChart.on('click', function (event) {
pieChartTotal.clear()
pieChartTotal.setOption(event.data && !!event.data.totalRatio.length ? {
dataset: {
// 這裏指定了維度名的順序,從而可以利用默認的維度到座標軸的映射。
// 如果不指定 dimensions,也可以通過指定 series.encode 完成映射,參見後文。
dimensions: ['desc', 'type', 'value'],
source: event.data && event.data.totalRatio,
},
legend: {
orient: 'vertical',
left: 70,
},
tooltip: {
trigger: 'item',
formatter: function (params) {
var result = ''
result = params.name + ' : ' + params.data.value + ' ( ' + params.percent + '% )'
return result
},
},
series: [
{ type: 'pie',
radius: ['75%', '50%'],
label: {
normal: {
show: true,
position: 'center',
color: '#4c4a4a',
formatter: function (data) {
var result = ''
result = event.name + '\n' + '總量' + ' ' + event.data.total
return result
},
textStyle: {
fontSize: 16,
color: '#00c0ef',
},
},
},
},
],
} : initPieOption)
pieChartReject.clear()
pieChartReject.setOption(event.data.rejectRatio && !!event.data.rejectRatio.length ? {
dataset: {
// 這裏指定了維度名的順序,從而可以利用默認的維度到座標軸的映射。
// 如果不指定 dimensions,也可以通過指定 series.encode 完成映射,參見後文。
dimensions: ['desc', 'type', 'value'],
source: event.data && event.data.rejectRatio,
},
legend: {
orient: 'vertical',
right: 70,
},
series: [
{ type: 'pie',
radius: ['75%', '45%'],
label: {
normal: {
show: true,
position: 'center',
color: '#4c4a4a',
formatter: function (data) {
var result = ''
result = event.name + '\n' + '拒絕量' + ' ' + event.data.rejectCount
return result
},
textStyle: {
fontSize: 16,
color: '#00c0ef',
},
},
},
},
],
} : initPieOption)
})
}
4、trendList數據結構
[{
"date": "2020-03-23",
"total": 52,
"passCount": 51,
"rejectCount": 1,
"totalRatio": [{
"type": "text",
"desc": "文本",
"value": 27
}, {
"type": "picture",
"desc": "圖片",
"value": 25
}],
"rejectRatio": [{
"type": "picture",
"desc": "圖片",
"value": 1
}]
}, {
"date": "2020-03-24",
"total": 25,
"passCount": 18,
"rejectCount": 7,
"totalRatio": [{
"type": "picture",
"desc": "圖片",
"value": 15
}, {
"type": "text",
"desc": "文本",
"value": 10
}],
"rejectRatio": [{
"type": "picture",
"desc": "圖片",
"value": 7
}]
}]
ECharts常見問題
問題:Error: Component series.pie not exists. Load it first.
原因: 沒有引入pie的組件
解決:
import 'echarts/lib/chart/pie'
問題:ECharts3去掉了noDataLoadingOption,沒數據時在容器中顯示暫無數據,切換後無法顯示圖表
解決:
- 判斷數據是否有值,沒有值則進行處理
- 將實例的option先清空在setOption
- 將ECharts實例提取出來,不要每次都創建
- 具體代碼可看上面代碼塊,不清楚可私信問我
// 默認用數據加載第一條渲染餅圖,可能存在無數據情況,因此需要判斷
pieChartTotal.setOption(trendList[0] && !!trendList[0].totalRatio.length ? this.PieOption('total') : initPieOption)
問題:Label formatter 格式化內容,需要換行
原因: label是基於canvas的,不支持html,只支持換行\n
解決:
formatter: function (data) {
var result = ''
result = event.name + '\n' + '總量' + ' ' + event.data.total
return result
},
問題:圖表溢出div,無法自適應
解決:
window.addEventListener('resize', function () {
lineChart.resize()
})
問題:點擊事件多次綁定,導致回調多次觸發
解決:
myChart.off('click')// 防止累計觸發
myChart.on('click', function (event) {
const url = reportTypesMap[item.key]['url']
if (!url) return false
const href = window.location.href.split('#')[0]
window.open(`${href}#${url}?time=${event.name}`)
})