optimization

This commit is contained in:
jiaming 2018-12-07 15:50:25 +08:00
parent e7db599043
commit 6572401cbf
3 changed files with 297 additions and 36 deletions

View File

@ -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>&nbsp;</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>

View File

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

View File

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