optimization
This commit is contained in:
parent
e7db599043
commit
6572401cbf
|
@ -1,15 +1,138 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="capsule-chart">
|
<div class="capsule-chart">
|
||||||
this is capsule chart
|
|
||||||
|
<loading v-if="!data" />
|
||||||
|
|
||||||
|
<template v-else>
|
||||||
|
<div class="label-column">
|
||||||
|
<div v-for="item in data.data" :key="item.title">{{ item.title }}</div>
|
||||||
|
<div> </div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="capsule-container">
|
||||||
|
<div class="capsule-item" v-for="(capsule, index) in capsuleData" :key="index">
|
||||||
|
<div :style="`width: ${capsule * 100}%; background-color: ${data.color[index % data.data.length]};`"></div>
|
||||||
|
</div>
|
||||||
|
<div class="unit-label">
|
||||||
|
<div class="unit-container">
|
||||||
|
<div v-for="unit in unitData" :key="unit">{{ unit }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="unit-text">单位</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
name: 'CapsuleChart'
|
name: 'CapsuleChart',
|
||||||
|
props: ['data'],
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
capsuleData: [],
|
||||||
|
unitData: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
data () {
|
||||||
|
const { init } = this
|
||||||
|
|
||||||
|
init()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
init () {
|
||||||
|
const { data, calcCapsuleAndUnitData } = this
|
||||||
|
|
||||||
|
if (!data) return
|
||||||
|
|
||||||
|
calcCapsuleAndUnitData()
|
||||||
|
},
|
||||||
|
calcCapsuleAndUnitData () {
|
||||||
|
const { data: { data } } = this
|
||||||
|
|
||||||
|
const capsuleData = data.map(({ value }) => value)
|
||||||
|
|
||||||
|
const maxValue = Math.max(...capsuleData)
|
||||||
|
|
||||||
|
this.capsuleData = capsuleData.map(v => v / maxValue)
|
||||||
|
|
||||||
|
const oneSixth = maxValue / 5
|
||||||
|
|
||||||
|
this.unitData = new Array(6).fill(0).map((v, i) => Math.ceil(i * oneSixth))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted () {
|
||||||
|
const { init } = this
|
||||||
|
|
||||||
|
init()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less">
|
<style lang="less">
|
||||||
.capsule-chart {}
|
.capsule-chart {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 10px;
|
||||||
|
|
||||||
|
.label-column {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding-right: 10px;
|
||||||
|
text-align: right;
|
||||||
|
font-size: 12px;
|
||||||
|
|
||||||
|
div {
|
||||||
|
height: 20px;
|
||||||
|
line-height: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.capsule-container {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.capsule-item {
|
||||||
|
box-shadow: 0 0 3px #999;
|
||||||
|
height: 10px;
|
||||||
|
margin: 5px 0px;
|
||||||
|
border-radius: 5px;
|
||||||
|
width: calc(~"100% - 50px");
|
||||||
|
|
||||||
|
div {
|
||||||
|
height: 8px;
|
||||||
|
margin-top: 1px;
|
||||||
|
border-radius: 5px;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.unit-label {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
line-height: 20px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.unit-text {
|
||||||
|
width: 40px;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.unit-container {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="number-show">
|
<div class="number-show">
|
||||||
<div class="number-itme" v-for="n in number.toString()" :key="n">
|
<div class="number-itme" v-for="(n, i) in number.toString()" :key="`${n}-${i}`">
|
||||||
{{ n }}
|
{{ n }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -2,21 +2,25 @@
|
||||||
<div class="ring-chart">
|
<div class="ring-chart">
|
||||||
<canvas :ref="ref" />
|
<canvas :ref="ref" />
|
||||||
|
|
||||||
<div class="center-info">
|
<loading v-if="!data" />
|
||||||
<div class="percent-show">{{percent}}</div>
|
|
||||||
<div class="current-label" :ref="labelRef">{{data.data[activeIndex].title}}</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="label-line">
|
|
||||||
<div class="label-container">
|
|
||||||
|
|
||||||
<div class="label" v-for="(label, index) in data.data" :key="label.title">
|
|
||||||
<div :style="`background-color: ${data.color[index % data.data.length]}`" />
|
|
||||||
<div>{{ label.title }}</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
<template v-else>
|
||||||
|
<div class="center-info" v-if="data.active">
|
||||||
|
<div class="percent-show">{{percent}}</div>
|
||||||
|
<div class="current-label" :ref="labelRef">{{data.data[activeIndex].title}}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
<div class="label-line">
|
||||||
|
<div class="label-container">
|
||||||
|
|
||||||
|
<div class="label" v-for="(label, index) in data.data" :key="label.title">
|
||||||
|
<div :style="`background-color: ${data.color[index % data.data.length]}`" />
|
||||||
|
<div>{{ label.title }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -27,7 +31,7 @@ export default {
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
ref: `ring-chart-${(new Date()).getTime()}`,
|
ref: `ring-chart-${(new Date()).getTime()}`,
|
||||||
canvas: '',
|
canvasDom: '',
|
||||||
canvasWH: [0, 0],
|
canvasWH: [0, 0],
|
||||||
ctx: '',
|
ctx: '',
|
||||||
|
|
||||||
|
@ -45,6 +49,9 @@ export default {
|
||||||
|
|
||||||
arcData: [],
|
arcData: [],
|
||||||
radiusData: [],
|
radiusData: [],
|
||||||
|
aroundLineData: [],
|
||||||
|
aroundTextData: [],
|
||||||
|
aroundTextFont: '13px Arial',
|
||||||
|
|
||||||
activeIncrease: 0.005,
|
activeIncrease: 0.005,
|
||||||
activeTime: 2500,
|
activeTime: 2500,
|
||||||
|
@ -54,15 +61,24 @@ export default {
|
||||||
percent: 0,
|
percent: 0,
|
||||||
totalValue: 0,
|
totalValue: 0,
|
||||||
|
|
||||||
activeAnimationHandler: ''
|
activeAnimationHandler: '',
|
||||||
|
awaitActiveHandler: ''
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
data (d) {
|
data (d) {
|
||||||
console.error(d)
|
const { reDraw } = this
|
||||||
|
|
||||||
|
if (!d) return
|
||||||
|
|
||||||
|
reDraw()
|
||||||
},
|
},
|
||||||
activeIndex () {
|
activeIndex () {
|
||||||
|
const { doPercentAnimation, doLabelTextAnimation } = this
|
||||||
|
|
||||||
|
doPercentAnimation()
|
||||||
|
|
||||||
|
doLabelTextAnimation()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -80,7 +96,7 @@ export default {
|
||||||
initCanvas () {
|
initCanvas () {
|
||||||
const { $refs, ref, labelRef, canvasWH } = this
|
const { $refs, ref, labelRef, canvasWH } = this
|
||||||
|
|
||||||
const canvas = this.canvas = $refs[ref]
|
const canvas = this.canvasDom = $refs[ref]
|
||||||
|
|
||||||
this.labelDom = $refs[labelRef]
|
this.labelDom = $refs[labelRef]
|
||||||
|
|
||||||
|
@ -103,14 +119,14 @@ export default {
|
||||||
this.ringLineWidth = ringRadius * 0.3
|
this.ringLineWidth = ringRadius * 0.3
|
||||||
},
|
},
|
||||||
draw () {
|
draw () {
|
||||||
const { caclArcData, data: { active }, drwaActive, drwaStatic } = this
|
const { caclArcData, data: { active }, drawActive, drwaStatic } = this
|
||||||
|
|
||||||
caclArcData()
|
caclArcData()
|
||||||
|
|
||||||
active ? drwaActive() : drwaStatic()
|
active ? drawActive() : drwaStatic()
|
||||||
},
|
},
|
||||||
caclArcData () {
|
caclArcData () {
|
||||||
const { data: { data }, arcData } = this
|
const { data: { data } } = this
|
||||||
|
|
||||||
const { getTotalValue, offsetAngle } = this
|
const { getTotalValue, offsetAngle } = this
|
||||||
|
|
||||||
|
@ -120,10 +136,12 @@ export default {
|
||||||
|
|
||||||
let currentPercent = offsetAngle
|
let currentPercent = offsetAngle
|
||||||
|
|
||||||
|
this.arcData = []
|
||||||
|
|
||||||
data.forEach(({ value }) => {
|
data.forEach(({ value }) => {
|
||||||
const currentAngle = value / totalValue * full + currentPercent
|
const currentAngle = value / totalValue * full + currentPercent
|
||||||
|
|
||||||
arcData.push([
|
this.arcData.push([
|
||||||
currentPercent,
|
currentPercent,
|
||||||
currentAngle
|
currentAngle
|
||||||
])
|
])
|
||||||
|
@ -142,18 +160,18 @@ export default {
|
||||||
|
|
||||||
return totalValue
|
return totalValue
|
||||||
},
|
},
|
||||||
drwaActive () {
|
drawActive () {
|
||||||
const { ctx, canvasWH } = this
|
const { ctx, canvasWH } = this
|
||||||
|
|
||||||
ctx.clearRect(0, 0, ...canvasWH)
|
ctx.clearRect(0, 0, ...canvasWH)
|
||||||
|
|
||||||
const { calcRadiusData, drawRing, drwaActive } = this
|
const { calcRadiusData, drawRing, drawActive } = this
|
||||||
|
|
||||||
calcRadiusData()
|
calcRadiusData()
|
||||||
|
|
||||||
drawRing()
|
drawRing()
|
||||||
|
|
||||||
this.activeAnimationHandler = requestAnimationFrame(drwaActive)
|
this.activeAnimationHandler = requestAnimationFrame(drawActive)
|
||||||
},
|
},
|
||||||
calcRadiusData () {
|
calcRadiusData () {
|
||||||
const { arcData, activeAddStatus, activePercent, activeIncrease, activeIndex } = this
|
const { arcData, activeAddStatus, activePercent, activeIncrease, activeIndex } = this
|
||||||
|
@ -181,7 +199,7 @@ export default {
|
||||||
|
|
||||||
this.activeAddStatus = false
|
this.activeAddStatus = false
|
||||||
|
|
||||||
setTimeout(turnToNextActive, activeTime)
|
this.awaitActiveHandler = setTimeout(turnToNextActive, activeTime)
|
||||||
},
|
},
|
||||||
turnToNextActive () {
|
turnToNextActive () {
|
||||||
const { arcData, activeIndex } = this
|
const { arcData, activeIndex } = this
|
||||||
|
@ -211,7 +229,124 @@ export default {
|
||||||
ctx.stroke()
|
ctx.stroke()
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
drwaStatic () {}
|
doPercentAnimation () {
|
||||||
|
const { totalValue, percent, activeIndex, data: { data }, doPercentAnimation } = this
|
||||||
|
|
||||||
|
const currentPercent = Math.trunc(data[activeIndex].value / totalValue * 100)
|
||||||
|
|
||||||
|
if (currentPercent === percent) return
|
||||||
|
|
||||||
|
currentPercent > percent ? this.percent++ : this.percent--
|
||||||
|
|
||||||
|
setTimeout(doPercentAnimation, 20)
|
||||||
|
},
|
||||||
|
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 { ctx, canvasWH } = this
|
||||||
|
|
||||||
|
ctx.clearRect(0, 0, ...canvasWH)
|
||||||
|
|
||||||
|
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 () {
|
||||||
|
const { arcData, ringRadius, ringLineWidth, ringOriginPos: [x, y] } = this
|
||||||
|
|
||||||
|
const { sin, cos } = Math
|
||||||
|
|
||||||
|
const radian = arcData.map(([a, b]) => (a + (b - a) / 2))
|
||||||
|
|
||||||
|
const radius = ringRadius + ringLineWidth / 2
|
||||||
|
|
||||||
|
const aroundLineData = radian.map(r => [x + cos(r) * radius, y + sin(r) * radius])
|
||||||
|
|
||||||
|
const lineLength = 35
|
||||||
|
|
||||||
|
this.aroundLineData = aroundLineData.map(([bx, by]) => {
|
||||||
|
const lineEndXPos = (bx > x ? bx + lineLength : bx - lineLength)
|
||||||
|
|
||||||
|
return [
|
||||||
|
[bx, by],
|
||||||
|
[lineEndXPos, by]
|
||||||
|
]
|
||||||
|
})
|
||||||
|
},
|
||||||
|
drawAroundLine () {
|
||||||
|
const { aroundLineData, data: { color }, ctx, canvas: { drawLine } } = this
|
||||||
|
|
||||||
|
const colorNum = color.length
|
||||||
|
|
||||||
|
aroundLineData.forEach(([lineBegin, lineEnd], i) => drawLine(ctx, lineBegin, lineEnd, 1, color[i % colorNum]))
|
||||||
|
},
|
||||||
|
calcAroundTextData () {
|
||||||
|
const { ctx, data: { data }, totalValue, aroundLineData } = this
|
||||||
|
|
||||||
|
const { ringOriginPos: [x], aroundTextFont } = this
|
||||||
|
|
||||||
|
ctx.font = aroundTextFont
|
||||||
|
|
||||||
|
this.aroundTextData = []
|
||||||
|
|
||||||
|
data.forEach(({ value, title }, i) => {
|
||||||
|
const percent = Math.trunc(value / totalValue * 100) + '%'
|
||||||
|
|
||||||
|
const percentWidth = ctx.measureText(percent).width
|
||||||
|
const titleWidth = ctx.measureText(title).width
|
||||||
|
|
||||||
|
const lineEndXPos = aroundLineData[i][1][0]
|
||||||
|
const lineEndYPos = aroundLineData[i][1][1]
|
||||||
|
|
||||||
|
const leftTrue = lineEndXPos < x
|
||||||
|
|
||||||
|
this.aroundTextData.push(
|
||||||
|
[percent, (leftTrue ? lineEndXPos - percentWidth : lineEndXPos), lineEndYPos],
|
||||||
|
[title, (leftTrue ? lineEndXPos - titleWidth : lineEndXPos), lineEndYPos + 15]
|
||||||
|
)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
drawAroundText () {
|
||||||
|
const { ctx, aroundTextData, aroundTextFont } = this
|
||||||
|
|
||||||
|
ctx.font = aroundTextFont
|
||||||
|
ctx.fillStyle = '#fff'
|
||||||
|
|
||||||
|
aroundTextData.forEach(item => {
|
||||||
|
ctx.fillText(...item)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
reDraw () {
|
||||||
|
const { activeAnimationHandler, draw } = this
|
||||||
|
|
||||||
|
cancelAnimationFrame(activeAnimationHandler)
|
||||||
|
|
||||||
|
draw()
|
||||||
|
}
|
||||||
},
|
},
|
||||||
mounted () {
|
mounted () {
|
||||||
const { init } = this
|
const { init } = this
|
||||||
|
@ -238,6 +373,7 @@ export default {
|
||||||
margin-top: -20px;
|
margin-top: -20px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-family: "Microsoft Yahei", Arial, sans-serif;
|
font-family: "Microsoft Yahei", Arial, sans-serif;
|
||||||
|
max-width: 25%;
|
||||||
|
|
||||||
.percent-show {
|
.percent-show {
|
||||||
font-size: 28px;
|
font-size: 28px;
|
||||||
|
@ -251,19 +387,18 @@ export default {
|
||||||
|
|
||||||
.current-label {
|
.current-label {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
margin-top: 15px;
|
margin-top: 5%;
|
||||||
transform: rotateY(0deg);
|
transform: rotateY(0deg);
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
.transform-text {
|
.transform-text {
|
||||||
animation: tranform-text 1s linear;
|
animation: transform-text 2s linear;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes transform-text {
|
@keyframes transform-text {
|
||||||
from {
|
|
||||||
transform: rotateY(0deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
to {
|
to {
|
||||||
transform: rotateY(360deg);
|
transform: rotateY(360deg);
|
||||||
}
|
}
|
||||||
|
@ -284,12 +419,15 @@ export default {
|
||||||
.label-container {
|
.label-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.label {
|
.label {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
margin: 0 3px;
|
margin: 0 3px;
|
||||||
|
height: 20px;
|
||||||
|
|
||||||
:nth-child(1) {
|
:nth-child(1) {
|
||||||
width: 10px;
|
width: 10px;
|
||||||
|
|
Loading…
Reference in New Issue