<template>
<div class="alarm-statistics-wrapper">
<div ref="alarmPie" class="alarm-pie"></div>
</div>
</template>
<script>
import 'echarts-gl'
export default {
name: 'AlarmStatistics',
props: {
data: {
type: Array,
default: () => ([
{ name: '待处置', value: 120, color: '#02ABF9' },
{ name: '处置中', value: 160, color: '#FFAE3A' },
{ name: '已处置', value: 200, color: '#0CC2C2' },
])
},
innerRatio: { type: Number, default: 0.78 },
heightPx: { type: Number, default: 26 },
viewAlpha: { type: Number, default: 40 },
viewDistance: { type: Number, default: 180 }
},
data() {
return {
chart: null,
option: null
}
},
mounted() {
this.initChart()
window.addEventListener('resize', this.onResize)
},
beforeDestroy() {
window.removeEventListener('resize', this.onResize)
this.chart && this.chart.dispose()
},
methods: {
onResize() { this.chart && this.chart.resize() },
initChart() {
this.chart && this.chart.dispose()
this.chart = this.$echarts.init(this.$refs.alarmPie)
this.option = this.build3DPie(this.data, this.innerRatio)
this.chart.setOption(this.option)
this.addLabelLayer()
this.chart.setOption(this.option)
this.bindListen(this.chart)
},
build3DPie(pieData, internalDiameterRatio) {
let series = []
let sumValue = 0
let startValue = 0
let endValue = 0
let legendData = []
let legendPercent = []
const k = 1 - internalDiameterRatio
const sorted = [...pieData].sort((a, b) => b.value - a.value)
sorted.forEach((item, i) => {
sumValue += item.value
series.push({
name: item.name || `item${i}`,
type: 'surface',
parametric: true,
wireframe: { show: false },
pieData: item,
pieStatus: { selected: false, hovered: false, k },
itemStyle: { color: item.color }
})
})
series.forEach(s => {
endValue = startValue + s.pieData.value
s.pieData.startRatio = startValue / sumValue
s.pieData.endRatio = endValue / sumValue
s.parametricEquation = this.getParametricEquation(
s.pieData.startRatio,
s.pieData.endRatio,
false,
false,
k,
s.pieData.value
)
startValue = endValue
const pct = this.fmtFloat(s.pieData.value / sumValue, 4)
legendData.push({ name: s.name, value: pct })
legendPercent.push({ name: s.name, value: pct })
})
const boxHeight = this.getHeight3D(series, this.heightPx)
return {
legend: {
data: legendData.map(l => l.name),
top: 8,
left: 8,
itemWidth: 12,
itemHeight: 12,
textStyle: { color: '#A1E2FF', fontSize: 12 },
icon: 'circle',
formatter: name => {
const item = legendPercent.find(i => i.name === name)
return `${name} ${(item.value * 100).toFixed(2)}%`
}
},
tooltip: {
formatter: p => {
if (p.seriesName !== 'pie2d') {
const slice = series[p.seriesIndex]
const percent = ((slice.pieData.endRatio - slice.pieData.startRatio) * 100).toFixed(2)
return `${p.seriesName}<br/>
<span style="display:inline-block;width:10px;height:10px;background:${p.color};margin-right:5px;border-radius:2px;"></span>${percent}%`
}
},
backgroundColor: 'rgba(5,35,70,.85)',
borderWidth: 0,
padding: [6, 10]
},
xAxis3D: { min: -1, max: 1 },
yAxis3D: { min: -1, max: 1 },
zAxis3D: { min: -1, max: 1 },
grid3D: {
show: false,
boxHeight,
viewControl: {
alpha: this.viewAlpha,
distance: this.viewDistance,
rotateSensitivity: 0,
zoomSensitivity: 0,
panSensitivity: 0,
autoRotate: false
}
},
series
}
},
addLabelLayer() {
this.option.series.push({
name: 'pie2d',
type: 'pie',
radius: ['10%', '75%'],
center: ['50%', '50%'],
startAngle: -20,
clockwise: false,
silent: true,
hoverAnimation: false,
labelLine: { show: true, length: 38, length2: 18, lineStyle: { color: '#6EC9FF' } },
label: {
show: true,
rich: {
name: { color: '#fff', fontSize: 12, lineHeight: 16 },
val: { color: '#6FE2FF', fontSize: 12, fontWeight: 600 }
},
formatter: p => `{name|${p.name}}\n{val|${p.value}}`
},
data: this.data.map(d => ({
name: d.name,
value: d.value,
itemStyle: { color: 'rgba(0,0,0,0)' },
tooltip: { show: false }
}))
})
},
getHeight3D(series, height) {
const sorted = [...series].sort((a, b) => b.pieData.value - a.pieData.value)
return (height * 25) / sorted[0].pieData.value
},
getParametricEquation(startRatio, endRatio, isSelected, isHovered, k, h) {
const midRatio = (startRatio + endRatio) / 2
const startR = startRatio * Math.PI * 2
const endR = endRatio * Math.PI * 2
const midR = midRatio * Math.PI * 2
if (startRatio === 0 && endRatio === 1) isSelected = false
k = k || 1 / 3
const offsetX = isSelected ? Math.cos(midR) * 0.1 : 0
const offsetY = isSelected ? Math.sin(midR) * 0.1 : 0
const hoverRate = isHovered ? 1.05 : 1
return {
u: { min: -Math.PI, max: Math.PI * 3, step: Math.PI / 32 },
v: { min: 0, max: Math.PI * 2, step: Math.PI / 20 },
x: (u, v) => {
if (u < startR) return offsetX + Math.cos(startR) * (1 + Math.cos(v) * k) * hoverRate
if (u > endR) return offsetX + Math.cos(endR) * (1 + Math.cos(v) * k) * hoverRate
return offsetX + Math.cos(u) * (1 + Math.cos(v) * k) * hoverRate
},
y: (u, v) => {
if (u < startR) return offsetY + Math.sin(startR) * (1 + Math.cos(v) * k) * hoverRate
if (u > endR) return offsetY + Math.sin(endR) * (1 + Math.cos(v) * k) * hoverRate
return offsetY + Math.sin(u) * (1 + Math.cos(v) * k) * hoverRate
},
z: (u, v) => {
if (u < -Math.PI * 0.5) return Math.sin(u)
if (u > Math.PI * 2.5) return Math.sin(u) * h * 0.1
return Math.sin(v) > 0 ? 1 * h * 0.1 : -1
}
}
},
fmtFloat(num, n) {
const f = Math.round(parseFloat(num) * Math.pow(10, n)) / Math.pow(10, n)
let s = f.toString()
let rs = s.indexOf('.')
if (rs < 0) { rs = s.length; s += '.' }
while (s.length <= rs + n) s += '0'
return parseFloat(s)
},
bindListen(chart) {
let selectedIndex = ''
let hoveredIndex = ''
chart.on('click', params => {
if (params.seriesName === 'pie2d') return
const slice = this.option.series[params.seriesIndex]
const isSelected = !slice.pieStatus.selected
const { hovered, k } = slice.pieStatus
const { startRatio, endRatio, value } = slice.pieData
if (selectedIndex !== '' && selectedIndex !== params.seriesIndex) {
const prev = this.option.series[selectedIndex]
prev.parametricEquation = this.getParametricEquation(prev.pieData.startRatio, prev.pieData.endRatio, false, false, prev.pieStatus.k, prev.pieData.value)
prev.pieStatus.selected = false
}
slice.parametricEquation = this.getParametricEquation(startRatio, endRatio, isSelected, hovered, k, value)
slice.pieStatus.selected = isSelected
selectedIndex = isSelected ? params.seriesIndex : ''
chart.setOption(this.option)
})
chart.on('mouseover', params => {
if (params.seriesName === 'pie2d') return
if (hoveredIndex === params.seriesIndex) return
if (hoveredIndex !== '') {
const prev = this.option.series[hoveredIndex]
prev.parametricEquation = this.getParametricEquation(prev.pieData.startRatio, prev.pieData.endRatio, prev.pieStatus.selected, false, prev.pieStatus.k, prev.pieData.value)
prev.pieStatus.hovered = false
hoveredIndex = ''
}
const cur = this.option.series[params.seriesIndex]
cur.parametricEquation = this.getParametricEquation(cur.pieData.startRatio, cur.pieData.endRatio, cur.pieStatus.selected, true, cur.pieStatus.k, cur.pieData.value + 5)
cur.pieStatus.hovered = true
hoveredIndex = params.seriesIndex
chart.setOption(this.option)
})
chart.on('globalout', () => {
if (hoveredIndex === '') return
const cur = this.option.series[hoveredIndex]
cur.parametricEquation = this.getParametricEquation(cur.pieData.startRatio, cur.pieData.endRatio, cur.pieStatus.selected, false, cur.pieStatus.k, cur.pieData.value)
cur.pieStatus.hovered = false
hoveredIndex = ''
chart.setOption(this.option)
})
}
},
watch: {
data: {
deep: true,
handler() { this.initChart() }
}
}
}
</script>
<style scoped>
.alarm-statistics-wrapper {
width:100%;
height:100%;
display:flex;
flex-direction:column;
}
.alarm-pie {
width:100%;
height:100%;
min-height:240px;
}
</style>