You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

270 lines
7.1 KiB

<template>
<div :class="className" :style="{height:height,width:width}" />
</template>
<script>
import * as echarts from 'echarts'
require('echarts/theme/macarons') // echarts theme
import resize from '@/mixins/resize'
export default {
mixins: [resize],
props: {
className: {
type: String,
default: ''
},
height: {
type: String,
default: '100px'
},
width: {
type: String,
default: '100px'
},
percent: {
type: [Number, String],
default: 100
},
chartData: {
type: Array,
default: () => []
},
title: {
type: String,
default: '标题'
}
},
data() {
return {
flag: true,
chart: null
}
},
computed: {
options() {
return {
legend: {
show: true,
bottom: 0,
left: 'center',
orient: 'horizontal',
itemWidth: 8,
itemHeight: 5
},
color: ['#f2a93f', '#446df6', '#56b7f9'],
series: [
{
type: 'pie',
radius: ['50%', '70%'],
center: ['50%', '36%'],
selectedMode: 'single',
padAngle: 0,
selectedOffset: 0,
hoverOffset: 0,
itemStyle: {
borderRadius: 0,
borderWidth: 0,
borderColor: 'transparent'
},
label: {
show: false
},
avoidLabelOverlap: true, // 是否启用防止标签重叠策略
emphasis: { // 高亮,即鼠标经过时的样式
scale: true, // 表示不放大item
label: {
show: true,
position: 'center',
formatter: `{b}\n{d}%`
}
},
labelLine: {
show: false
},
data: this.chartData.map((item, index) => ({ ...item, selected: index === 0 }))
// (() => {
// if (this.percent || this.percent === 0) {
// return [
// { value: parseFloat(this.percent), name: `${this.title}率` },
// { value: 100 - parseFloat(this.percent), name: `未${this.title}率` }
// ]
// } else {
// return this.chartData
// }
// })()
}
],
graphic: {
elements: []
}
}
}
},
watch: {
chartData() {
this.setOptions()
}
},
mounted() {
this.$nextTick(() => {
this.initChart()
})
},
methods: {
initChart() {
this.chart = echarts.init(this.$el, 'macarons')
this.setOptions()
},
setOptions() {
this.chart?.setOption(this.options)
if (this.chart && Array.isArray(this.chartData) && this.chartData.length > 0) {
try {
this.chart.dispatchAction({ type: 'downplay', seriesIndex: 0 })
this.chart.dispatchAction({ type: 'highlight', seriesIndex: 0, dataIndex: 0 })
} catch (e) {
// ignore
}
// persistent center label via graphic
const data = this.chartData
const total = data.reduce((s, it) => s + Number(it.value || 0), 0)
const first = data[0]
const percent = total > 0 ? Math.round((Number(first.value || 0) / total) * 10000) / 100 : 0
this.chart.setOption({
graphic: {
elements: [
{
type: 'text',
left: 'center',
top: '36%',
style: {
text: `${first.name}\n${percent}%`,
textAlign: 'center',
fill: '#333',
fontSize: 14,
fontWeight: 500
},
z: 100
}
]
}
})
// update on hover and restore on mouse out
this.chart.off('mouseover')
this.chart.off('globalout')
this.chart.on('mouseover', { seriesIndex: 0 }, (params) => {
if (!params || typeof params.dataIndex === 'undefined') return
const item = data[params.dataIndex]
const p = total > 0 ? Math.round((Number(item.value || 0) / total) * 10000) / 100 : 0
this.chart.setOption({
graphic: {
elements: [
{
type: 'text',
left: 'center',
top: '36%',
style: {
text: `${item.name}\n${p}%`,
textAlign: 'center',
fill: '#333',
fontSize: 14,
fontWeight: 500
},
z: 100
}
]
}
})
})
this.chart.on('globalout', () => {
this.chart.setOption({
graphic: {
elements: [
{
type: 'text',
left: 'center',
top: '36%',
style: {
text: `${first.name}\n${percent}%`,
textAlign: 'center',
fill: '#333',
fontSize: 14,
fontWeight: 500
},
z: 100
}
]
}
})
})
}
// this.setGraphics()
},
setGraphics() {
const getPointOnCircle = (angle, radiusPercent) => {
const radian = (angle - 90 - 12) * Math.PI / 180
const radius = this.chart.getWidth() / 2 * radiusPercent / 100
const x = this.chart.getWidth() / 2 + Math.cos(radian) * radius
const y = this.chart.getHeight() / 2 + Math.sin(radian) * radius - this.chart.getHeight() * 0.1
return { x: x, y: y }
}
const graphicElements = []
let total = 0
if (!this.options?.series[0]?.data) return
this.options?.series[0]?.data.forEach(function(item) {
total += item.value
})
let startAngle = 0
this.options?.series[0]?.data.forEach(function(item) {
const angle = (item.value / total) * 360
const endAngle = startAngle + angle
const middleAngle = (startAngle + endAngle) / 2
// 计算开始和结束点的位置
const pointStart = getPointOnCircle(middleAngle - angle / 2, 60)
const pointEnd = getPointOnCircle(middleAngle + angle / 2, 60)
// 添加小圆的装饰
graphicElements.push({
type: 'circle',
shape: {
cx: pointStart.x,
cy: pointStart.y,
r: 3
},
style: {
fill: '#fff'
},
z: 100
})
graphicElements.push({
type: 'circle',
shape: {
cx: pointEnd.x,
cy: pointEnd.y,
r: 3
},
style: {
fill: '#fff'
},
z: 100
})
startAngle += angle
})
this.chart.setOption({
graphic: {
elements: graphicElements
}
})
}
}
}
</script>
<style scoped lang="scss">
</style>