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
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>
|