DemuMesDataV/components/ringChart/index.vue

435 lines
10 KiB
Vue
Raw Normal View History

2018-12-05 18:31:36 +08:00
<template>
<div class="ring-chart">
2019-01-16 16:56:11 +08:00
<loading v-if="!status" />
2018-12-06 18:53:31 +08:00
2019-01-16 16:56:11 +08:00
<div class="canvas-container">
<canvas :ref="ref" />
2018-12-06 18:53:31 +08:00
2018-12-07 15:50:25 +08:00
<div class="center-info" v-if="data.active">
<div class="percent-show">{{percent}}</div>
2019-01-16 16:56:11 +08:00
<div class="current-label" :ref="labelRef">{{data.series[activeIndex].title}}</div>
2018-12-07 15:50:25 +08:00
</div>
2019-01-16 16:56:11 +08:00
</div>
2018-12-06 18:53:31 +08:00
2019-01-16 16:56:11 +08:00
<label-line :label="dealAfterLabelLine" :colors="drawColors" />
2018-12-05 18:31:36 +08:00
</div>
</template>
<script>
2019-01-16 16:56:11 +08:00
import colorsMixin from '../../mixins/colorsMixin.js'
import canvasMixin from '../../mixins/canvasMixin.js'
2018-12-05 18:31:36 +08:00
export default {
2018-12-06 18:53:31 +08:00
name: 'RingChart',
2019-01-16 16:56:11 +08:00
props: ['data', 'labelLine', 'colors'],
mixins: [colorsMixin, canvasMixin],
2018-12-06 18:53:31 +08:00
data () {
return {
ref: `ring-chart-${(new Date()).getTime()}`,
2019-01-16 16:56:11 +08:00
status: false,
2018-12-06 18:53:31 +08:00
labelRef: `label-ref-${(new Date()).getTime()}`,
labelDom: '',
ringRadius: '',
ringLineWidth: '',
maxRingWidthP: 1.15,
activeIndex: 1,
activePercent: 1,
activeAddStatus: true,
arcData: [],
radiusData: [],
2018-12-07 15:50:25 +08:00
aroundLineData: [],
aroundTextData: [],
aroundTextFont: '13px Arial',
2018-12-06 18:53:31 +08:00
activeIncrease: 0.005,
2018-12-14 11:18:09 +08:00
activeTime: 4500,
2018-12-06 18:53:31 +08:00
offsetAngle: Math.PI * 0.5 * -1,
2019-01-16 16:56:11 +08:00
dealAfterLabelLine: [],
2018-12-06 18:53:31 +08:00
percent: 0,
totalValue: 0,
2018-12-07 15:50:25 +08:00
activeAnimationHandler: '',
awaitActiveHandler: ''
2018-12-06 18:53:31 +08:00
}
},
watch: {
data (d) {
2019-01-16 16:56:11 +08:00
const { checkData, reDraw } = this
2018-12-07 15:50:25 +08:00
2019-01-16 16:56:11 +08:00
checkData() && reDraw()
2018-12-06 18:53:31 +08:00
},
activeIndex () {
2018-12-07 15:50:25 +08:00
const { doPercentAnimation, doLabelTextAnimation } = this
2018-12-06 18:53:31 +08:00
2018-12-07 15:50:25 +08:00
doPercentAnimation()
doLabelTextAnimation()
2018-12-06 18:53:31 +08:00
}
},
methods: {
2019-01-16 16:56:11 +08:00
async init () {
const { initCanvas, initColors, calcRingConfig, checkData, draw } = this
2018-12-06 18:53:31 +08:00
2019-01-16 16:56:11 +08:00
await initCanvas()
2018-12-06 18:53:31 +08:00
2019-01-16 16:56:11 +08:00
initColors()
2018-12-06 18:53:31 +08:00
2019-01-16 16:56:11 +08:00
calcRingConfig()
2018-12-06 18:53:31 +08:00
2019-01-16 16:56:11 +08:00
checkData() && draw()
2018-12-06 18:53:31 +08:00
},
calcRingConfig () {
2019-01-16 16:56:11 +08:00
const { canvasWH } = this
2018-12-06 18:53:31 +08:00
const ringRadius = this.ringRadius = Math.min(...canvasWH) * 0.6 / 2
this.ringLineWidth = ringRadius * 0.3
},
2019-01-16 16:56:11 +08:00
checkData () {
const { data } = this
this.status = false
if (!data || !data.series) return false
this.status = true
return true
},
2018-12-06 18:53:31 +08:00
draw () {
2019-01-16 16:56:11 +08:00
const { clearCanvas, calcLabelLineData } = this
2018-12-12 18:48:43 +08:00
2019-01-16 16:56:11 +08:00
clearCanvas()
calcLabelLineData()
2018-12-12 18:48:43 +08:00
2018-12-07 15:50:25 +08:00
const { caclArcData, data: { active }, drawActive, drwaStatic } = this
2018-12-06 18:53:31 +08:00
caclArcData()
2018-12-07 15:50:25 +08:00
active ? drawActive() : drwaStatic()
2018-12-06 18:53:31 +08:00
},
2019-01-16 16:56:11 +08:00
calcLabelLineData () {
const { labelLine, deepClone, data: { series } } = this
if (!labelLine) return
const dealAfterLabelLine = this.dealAfterLabelLine = deepClone(labelLine)
if (labelLine.labels === 'inherit') dealAfterLabelLine.labels = series.map(({ title }) => title)
},
2018-12-06 18:53:31 +08:00
caclArcData () {
2019-01-16 16:56:11 +08:00
const { data: { series } } = this
2018-12-06 18:53:31 +08:00
const { getTotalValue, offsetAngle } = this
const totalValue = getTotalValue()
const full = 2 * Math.PI
2019-01-16 16:56:11 +08:00
const aveAngle = full / series.length
2018-12-12 18:48:43 +08:00
2018-12-06 18:53:31 +08:00
let currentPercent = offsetAngle
2018-12-07 15:50:25 +08:00
this.arcData = []
2019-01-16 16:56:11 +08:00
series.forEach(({ value }) => {
2018-12-12 18:48:43 +08:00
const valueAngle = totalValue === 0 ? aveAngle : value / totalValue * full
2018-12-06 18:53:31 +08:00
2018-12-07 15:50:25 +08:00
this.arcData.push([
2018-12-06 18:53:31 +08:00
currentPercent,
2018-12-12 18:48:43 +08:00
currentPercent += valueAngle
2018-12-06 18:53:31 +08:00
])
})
},
getTotalValue () {
2019-01-16 16:56:11 +08:00
const { data: { series } } = this
2018-12-06 18:53:31 +08:00
let totalValue = 0
2019-01-16 16:56:11 +08:00
series.forEach(({ value }) => (totalValue += value))
2018-12-06 18:53:31 +08:00
this.totalValue = totalValue
return totalValue
},
2018-12-07 15:50:25 +08:00
drawActive () {
2018-12-06 18:53:31 +08:00
const { ctx, canvasWH } = this
ctx.clearRect(0, 0, ...canvasWH)
2018-12-07 15:50:25 +08:00
const { calcRadiusData, drawRing, drawActive } = this
2018-12-06 18:53:31 +08:00
calcRadiusData()
drawRing()
2018-12-07 15:50:25 +08:00
this.activeAnimationHandler = requestAnimationFrame(drawActive)
2018-12-06 18:53:31 +08:00
},
calcRadiusData () {
const { arcData, activeAddStatus, activePercent, activeIncrease, activeIndex } = this
const radiusData = new Array(arcData.length).fill(1)
const activeRadius = (activeAddStatus ? this.activePercent += activeIncrease : activePercent)
radiusData[activeIndex] = activeRadius
const { maxRingWidthP, ringRadius, awaitActive } = this
const prevRadius = maxRingWidthP - activeRadius + 1
const prevIndex = activeIndex - 1
radiusData[prevIndex < 0 ? arcData.length - 1 : prevIndex] = prevRadius
this.radiusData = radiusData.map(v => (v * ringRadius))
if (activeRadius >= maxRingWidthP && activeAddStatus) awaitActive()
},
awaitActive () {
const { activeTime, turnToNextActive } = this
this.activeAddStatus = false
2018-12-07 15:50:25 +08:00
this.awaitActiveHandler = setTimeout(turnToNextActive, activeTime)
2018-12-06 18:53:31 +08:00
},
turnToNextActive () {
const { arcData, activeIndex } = this
this.activePercent = 1
this.activeIndex = (activeIndex + 1 === arcData.length ? 0 : activeIndex + 1)
this.activeAddStatus = true
},
drawRing () {
2019-01-16 16:56:11 +08:00
const { arcData, ctx, centerPos, radiusData } = this
2018-12-06 18:53:31 +08:00
2019-01-16 16:56:11 +08:00
const { ringLineWidth, drawColors } = this
2018-12-06 18:53:31 +08:00
const arcNum = arcData.length
arcData.forEach((arc, i) => {
ctx.beginPath()
2019-01-16 16:56:11 +08:00
ctx.arc(...centerPos, radiusData[i], ...arc)
2018-12-06 18:53:31 +08:00
ctx.lineWidth = ringLineWidth
2019-01-16 16:56:11 +08:00
ctx.strokeStyle = drawColors[i % arcNum]
2018-12-06 18:53:31 +08:00
ctx.stroke()
})
},
2018-12-07 15:50:25 +08:00
doPercentAnimation () {
2019-01-16 16:56:11 +08:00
const { totalValue, percent, activeIndex, data: { series }, doPercentAnimation } = this
2018-12-07 15:50:25 +08:00
2018-12-12 18:48:43 +08:00
if (!totalValue) return
2019-01-16 16:56:11 +08:00
const currentValue = series[activeIndex].value
2018-12-14 11:18:09 +08:00
let currentPercent = Math.trunc(currentValue / totalValue * 100)
2018-12-12 18:48:43 +08:00
currentPercent === 0 && (currentPercent = 1)
2018-12-14 11:18:09 +08:00
currentValue === 0 && (currentPercent = 0)
2018-12-07 15:50:25 +08:00
if (currentPercent === percent) return
currentPercent > percent ? this.percent++ : this.percent--
2018-12-14 11:18:09 +08:00
setTimeout(doPercentAnimation, 10)
2018-12-07 15:50:25 +08:00
},
doLabelTextAnimation () {
let { labelDom, $refs, labelRef } = this
if (!labelDom) labelDom = this.labelDom = $refs[labelRef]
labelDom.setAttribute('class', 'current-label transform-text')
setTimeout(() => {
labelDom.setAttribute('class', 'current-label')
}, 2000)
},
drwaStatic () {
const { drawStaticRing, calcAroundLineData, drawAroundLine, calcAroundTextData, drawAroundText } = this
drawStaticRing()
calcAroundLineData()
drawAroundLine()
calcAroundTextData()
drawAroundText()
},
drawStaticRing () {
const { arcData, ringRadius, drawRing } = this
this.radiusData = new Array(arcData.length).fill(1).map(v => v * ringRadius)
drawRing()
},
calcAroundLineData () {
2019-01-16 16:56:11 +08:00
const { arcData, ringRadius, ringLineWidth, centerPos: [x, y], data: { series }, canvas, totalValue } = this
2018-12-07 15:50:25 +08:00
2018-12-12 18:48:43 +08:00
const { getCircleRadianPoint } = canvas
2018-12-07 15:50:25 +08:00
const radian = arcData.map(([a, b]) => (a + (b - a) / 2))
const radius = ringRadius + ringLineWidth / 2
2018-12-12 18:48:43 +08:00
const aroundLineData = radian.map(r => getCircleRadianPoint(x, y, radius, r))
2018-12-07 15:50:25 +08:00
const lineLength = 35
2018-12-12 18:48:43 +08:00
this.aroundLineData = aroundLineData.map(([bx, by], i) => {
2019-01-16 16:56:11 +08:00
if (!series[i].value && totalValue) return [false, false]
2018-12-12 18:48:43 +08:00
2018-12-07 15:50:25 +08:00
const lineEndXPos = (bx > x ? bx + lineLength : bx - lineLength)
return [
[bx, by],
[lineEndXPos, by]
]
})
},
drawAroundLine () {
2019-01-16 16:56:11 +08:00
const { aroundLineData, drawColors, ctx, canvas: { drawLine } } = this
2018-12-07 15:50:25 +08:00
2019-01-16 16:56:11 +08:00
const colorNum = drawColors.length
2018-12-07 15:50:25 +08:00
2018-12-12 18:48:43 +08:00
aroundLineData.forEach(([lineBegin, lineEnd], i) =>
lineBegin !== false &&
2019-01-16 16:56:11 +08:00
drawLine(ctx, lineBegin, lineEnd, 1, drawColors[i % colorNum]))
2018-12-07 15:50:25 +08:00
},
calcAroundTextData () {
2019-01-16 16:56:11 +08:00
const { data: { series, fixed }, totalValue } = this
2018-12-07 15:50:25 +08:00
2018-12-12 18:48:43 +08:00
const aroundTextData = this.aroundTextData = []
2018-12-07 15:50:25 +08:00
2018-12-14 11:18:09 +08:00
if (!totalValue) return data.forEach(({ v, title }, i) => aroundTextData.push([0, title]))
2019-01-16 16:56:11 +08:00
const dataLast = series.length - 1
2018-12-14 11:18:09 +08:00
let totalPercent = 0
2019-01-16 16:56:11 +08:00
series.forEach(({ value, title }, i) => {
2018-12-14 11:18:09 +08:00
if (!value) return aroundTextData.push([false, false])
2018-12-12 18:48:43 +08:00
2018-12-14 11:18:09 +08:00
let percent = Number((value / totalValue * 100).toFixed(fixed || 1))
2018-12-07 15:50:25 +08:00
2018-12-14 11:18:09 +08:00
percent < 0.1 && (percent = 0.1)
2018-12-07 15:50:25 +08:00
2018-12-14 11:18:09 +08:00
const currentPercent = (i === dataLast ? 100 - totalPercent : percent).toFixed(fixed || 1)
2018-12-07 15:50:25 +08:00
2018-12-14 11:18:09 +08:00
aroundTextData.push([currentPercent, title])
2018-12-07 15:50:25 +08:00
2018-12-14 11:18:09 +08:00
totalPercent += percent
2018-12-07 15:50:25 +08:00
})
},
drawAroundText () {
2019-01-16 16:56:11 +08:00
const { ctx, aroundTextData, aroundTextFont, aroundLineData, centerPos: [x] } = this
2018-12-07 15:50:25 +08:00
ctx.font = aroundTextFont
ctx.fillStyle = '#fff'
2018-12-12 18:48:43 +08:00
aroundTextData.forEach(([percent, title], i) => {
if (percent === false) return
const currentPos = aroundLineData[i][1]
ctx.textAlign = 'start'
currentPos[0] < x && (ctx.textAlign = 'end')
ctx.textBaseline = 'bottom'
2018-12-14 11:18:09 +08:00
ctx.fillText(`${percent}%`, ...currentPos)
2018-12-12 18:48:43 +08:00
ctx.textBaseline = 'top'
ctx.fillText(title, ...currentPos)
2018-12-07 15:50:25 +08:00
})
},
reDraw () {
const { activeAnimationHandler, draw } = this
cancelAnimationFrame(activeAnimationHandler)
draw()
}
2018-12-06 18:53:31 +08:00
},
mounted () {
const { init } = this
init()
}
2018-12-05 18:31:36 +08:00
}
</script>
<style lang="less">
2018-12-06 18:53:31 +08:00
.ring-chart {
position: relative;
2019-01-16 16:56:11 +08:00
display: flex;
flex-direction: column;
color: #fff;
.canvas-container {
position: relative;
flex: 1;
}
2018-12-06 18:53:31 +08:00
canvas {
width: 100%;
height: 100%;
}
.center-info {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
text-align: center;
font-family: "Microsoft Yahei", Arial, sans-serif;
2018-12-07 15:50:25 +08:00
max-width: 25%;
2018-12-06 18:53:31 +08:00
.percent-show {
2018-12-11 14:52:12 +08:00
font-size: 28px;
2018-12-06 18:53:31 +08:00
&::after {
content: '%';
2018-12-11 14:52:12 +08:00
font-size: 15px;
2018-12-06 18:53:31 +08:00
margin-left: 5px;
}
}
.current-label {
2018-12-11 14:52:12 +08:00
font-size: 16px;
2018-12-07 15:50:25 +08:00
margin-top: 5%;
2018-12-06 18:53:31 +08:00
transform: rotateY(0deg);
2018-12-07 15:50:25 +08:00
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
2018-12-06 18:53:31 +08:00
}
.transform-text {
2018-12-07 15:50:25 +08:00
animation: transform-text 2s linear;
2018-12-06 18:53:31 +08:00
}
@keyframes transform-text {
to {
transform: rotateY(360deg);
}
}
}
}
2018-12-05 18:31:36 +08:00
</style>