303 lines
6.5 KiB
Vue
303 lines
6.5 KiB
Vue
<template>
|
|
<div class="dv-active-ring-chart">
|
|
<div class="active-ring-chart-container" ref="active-ring-chart" />
|
|
<div class="active-ring-info">
|
|
<dv-digital-flop :config="digitalFlop" />
|
|
<div class="active-ring-name" :style="fontSize">{{ ringName }}</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
import Charts from '@jiaminghi/charts'
|
|
|
|
import dvDigitalFlop from '../digitalFlop'
|
|
|
|
import { deepMerge } from '@jiaminghi/charts/lib/util/index'
|
|
|
|
import { deepClone } from '@jiaminghi/c-render/lib/plugin/util'
|
|
|
|
export default {
|
|
name: 'ActiveRingChart',
|
|
components: {
|
|
dvDigitalFlop
|
|
},
|
|
props: {
|
|
config: {
|
|
type: Object,
|
|
default: () => ({})
|
|
}
|
|
},
|
|
data () {
|
|
return {
|
|
defaultConfig: {
|
|
/**
|
|
* @description Ring radius
|
|
* @type {String|Number}
|
|
* @default radius = '50%'
|
|
* @example radius = '50%' | 100
|
|
*/
|
|
radius: '50%',
|
|
/**
|
|
* @description Active ring radius
|
|
* @type {String|Number}
|
|
* @default activeRadius = '55%'
|
|
* @example activeRadius = '55%' | 110
|
|
*/
|
|
activeRadius: '55%',
|
|
/**
|
|
* @description Ring data
|
|
* @type {Array}
|
|
* @default data = [{ name: '', value: 0 }]
|
|
*/
|
|
data: [{ name: '', value: 0 }],
|
|
/**
|
|
* @description Ring line width
|
|
* @type {Number}
|
|
* @default lineWidth = 20
|
|
*/
|
|
lineWidth: 20,
|
|
/**
|
|
* @description Active time gap (ms)
|
|
* @type {Number}
|
|
* @default activeTimeGap = 3000
|
|
*/
|
|
activeTimeGap: 3000,
|
|
/**
|
|
* @description Ring color (hex|rgb|rgba)
|
|
* @type {Array<String>}
|
|
* @default color = [Charts Default Color]
|
|
*/
|
|
color: [],
|
|
/**
|
|
* @description Digital flop style
|
|
* @type {Object}
|
|
*/
|
|
digitalFlopStyle: {
|
|
fontSize: 25,
|
|
fill: '#fff'
|
|
},
|
|
/**
|
|
* @description CRender animationCurve
|
|
* @type {String}
|
|
* @default animationCurve = 'easeOutCubic'
|
|
*/
|
|
animationCurve: 'easeOutCubic',
|
|
/**
|
|
* @description CRender animationFrame
|
|
* @type {String}
|
|
* @default animationFrame = 50
|
|
*/
|
|
animationFrame: 50
|
|
},
|
|
|
|
mergedConfig: null,
|
|
|
|
chart: null,
|
|
|
|
activeIndex: 0,
|
|
|
|
animationHandler: ''
|
|
}
|
|
},
|
|
computed: {
|
|
digitalFlop () {
|
|
const { mergedConfig, activeIndex } = this
|
|
|
|
if (!mergedConfig) return {}
|
|
|
|
const { digitalFlopStyle, data } = mergedConfig
|
|
|
|
const value = data.map(({ value }) => value)
|
|
|
|
const sum = value.reduce((all, v) => all + v, 0)
|
|
|
|
const percent = parseInt(value[activeIndex] / sum * 100)
|
|
|
|
return {
|
|
content: '{nt}%',
|
|
number: [percent],
|
|
style: digitalFlopStyle
|
|
}
|
|
},
|
|
ringName () {
|
|
const { mergedConfig, activeIndex } = this
|
|
|
|
if (!mergedConfig) return ''
|
|
|
|
return mergedConfig.data[activeIndex].name
|
|
},
|
|
fontSize () {
|
|
const { mergedConfig } = this
|
|
|
|
if (!mergedConfig) return ''
|
|
|
|
return `font-size: ${mergedConfig.digitalFlopStyle.fontSize}px;`
|
|
}
|
|
},
|
|
watch: {
|
|
config () {
|
|
const { animationHandler, mergeConfig, setRingOption } = this
|
|
|
|
clearTimeout(animationHandler)
|
|
|
|
this.activeIndex = 0
|
|
|
|
mergeConfig()
|
|
|
|
setRingOption()
|
|
}
|
|
},
|
|
methods: {
|
|
init () {
|
|
const { initChart, mergeConfig, setRingOption } = this
|
|
|
|
initChart()
|
|
|
|
mergeConfig()
|
|
|
|
setRingOption()
|
|
},
|
|
initChart () {
|
|
const { $refs } = this
|
|
|
|
this.chart = new Charts($refs['active-ring-chart'])
|
|
},
|
|
mergeConfig () {
|
|
const { defaultConfig, config } = this
|
|
|
|
this.mergedConfig = deepMerge(deepClone(defaultConfig, true), config || {})
|
|
},
|
|
setRingOption () {
|
|
const { getRingOption, chart, ringAnimation } = this
|
|
|
|
const option = getRingOption()
|
|
|
|
chart.setOption(option)
|
|
|
|
ringAnimation()
|
|
},
|
|
getRingOption () {
|
|
const { mergedConfig, getRealRadius } = this
|
|
|
|
const radius = getRealRadius()
|
|
|
|
mergedConfig.data.forEach(dataItem => {
|
|
dataItem.radius = radius
|
|
})
|
|
|
|
return {
|
|
series: [
|
|
{
|
|
type: 'pie',
|
|
...mergedConfig,
|
|
outsideLabel: {
|
|
show: false
|
|
}
|
|
}
|
|
]
|
|
}
|
|
},
|
|
getRealRadius (active = false) {
|
|
const { mergedConfig, chart } = this
|
|
|
|
const { radius, activeRadius, lineWidth } = mergedConfig
|
|
|
|
const maxRadius = Math.min(...chart.render.area) / 2
|
|
|
|
const halfLineWidth = lineWidth / 2
|
|
|
|
let realRadius = active ? activeRadius : radius
|
|
|
|
if (typeof realRadius !== 'number') realRadius = parseInt(realRadius) / 100 * maxRadius
|
|
|
|
const insideRadius = realRadius - halfLineWidth
|
|
const outSideRadius = realRadius + halfLineWidth
|
|
|
|
return [insideRadius, outSideRadius]
|
|
},
|
|
ringAnimation () {
|
|
let { activeIndex, getRingOption, chart, getRealRadius } = this
|
|
|
|
const radius = getRealRadius()
|
|
const active = getRealRadius(true)
|
|
|
|
const option = getRingOption()
|
|
|
|
const { data } = option.series[0]
|
|
|
|
data.forEach((dataItem, i) => {
|
|
if (i === activeIndex) {
|
|
dataItem.radius = active
|
|
} else {
|
|
dataItem.radius = radius
|
|
}
|
|
})
|
|
|
|
chart.setOption(option)
|
|
|
|
const { activeTimeGap } = option.series[0]
|
|
|
|
this.animationHandler = setTimeout(foo => {
|
|
activeIndex += 1
|
|
|
|
if (activeIndex >= data.length) activeIndex = 0
|
|
|
|
this.activeIndex = activeIndex
|
|
|
|
this.ringAnimation()
|
|
}, activeTimeGap)
|
|
}
|
|
},
|
|
mounted () {
|
|
const { init } = this
|
|
|
|
init()
|
|
},
|
|
beforeDestroy () {
|
|
const { animationHandler } = this
|
|
|
|
clearTimeout(animationHandler)
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style lang="less">
|
|
.dv-active-ring-chart {
|
|
position: relative;
|
|
|
|
.active-ring-chart-container {
|
|
width: 100%;
|
|
height: 100%;
|
|
}
|
|
|
|
.active-ring-info {
|
|
position: absolute;
|
|
width: 100%;
|
|
height: 100%;
|
|
left: 0px;
|
|
top: 0px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: center;
|
|
align-items: center;
|
|
|
|
.dv-digital-flop {
|
|
width: 100px;
|
|
height: 30px;
|
|
}
|
|
|
|
.active-ring-name {
|
|
width: 100px;
|
|
height: 30px;
|
|
color: #fff;
|
|
text-align: center;
|
|
vertical-align: middle;
|
|
text-overflow: ellipsis;
|
|
overflow: hidden;
|
|
white-space: nowrap;
|
|
}
|
|
}
|
|
}
|
|
</style>
|