354 lines
7.3 KiB
Vue
354 lines
7.3 KiB
Vue
<template>
|
|
<div class="dv-water-pond-level">
|
|
<svg v-if="render">
|
|
<defs>
|
|
<linearGradient :id="gradientId" x1="0%" y1="0%" x2="0%" y2="100%">
|
|
<stop v-for="lc in svgBorderGradient" :key="lc[0]"
|
|
:offset="lc[0]"
|
|
:stop-color="lc[1]" />
|
|
</linearGradient>
|
|
</defs>
|
|
|
|
<text
|
|
v-if="render"
|
|
:stroke="`url(#${gradientId})`"
|
|
:fill="`url(#${gradientId})`"
|
|
:x="render.area[0] / 2 + 8"
|
|
:y="render.area[1] / 2 + 8"
|
|
>
|
|
{{ details }}
|
|
</text>
|
|
|
|
<ellipse v-if="!shape || shape === 'round'"
|
|
:cx="render.area[0] / 2 + 8"
|
|
:cy="render.area[1] / 2 + 8"
|
|
:rx="render.area[0] / 2 + 5"
|
|
:ry="render.area[1] / 2 + 5"
|
|
:stroke="`url(#${gradientId})`" />
|
|
|
|
<rect v-else
|
|
x="2" y="2"
|
|
:rx="shape === 'roundRect' ? 10 : 0"
|
|
:ry="shape === 'roundRect' ? 10 : 0"
|
|
:width="render.area[0] + 12"
|
|
:height="render.area[1] + 12"
|
|
:stroke="`url(#${gradientId})`" />
|
|
</svg>
|
|
|
|
<canvas ref="water-pond-level" :style="`border-radius: ${radius};`" />
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
import { deepMerge } from '@jiaminghi/charts/lib/util/index'
|
|
|
|
import { deepClone } from '@jiaminghi/c-render/lib/plugin/util'
|
|
|
|
import CRender from '@jiaminghi/c-render'
|
|
|
|
export default {
|
|
name: 'waterLevelPond',
|
|
props: {
|
|
config: Object,
|
|
default: () => ({})
|
|
},
|
|
data () {
|
|
return {
|
|
gradientId: `water-level-pond-${(new Date()).getTime()}`,
|
|
|
|
defaultConfig: {
|
|
/**
|
|
* @description Data
|
|
* @type {Array<Number>}
|
|
* @default data = []
|
|
* @example data = [60, 40]
|
|
*/
|
|
data: [],
|
|
/**
|
|
* @description Shape of wanter level pond
|
|
* @type {String}
|
|
* @default shape = 'rect'
|
|
* @example shape = 'rect' | 'roundRect' | 'round'
|
|
*/
|
|
shape: 'rect',
|
|
/**
|
|
* @description Water wave number
|
|
* @type {Number}
|
|
* @default waveNum = 3
|
|
*/
|
|
waveNum: 3,
|
|
/**
|
|
* @description Water wave height (px)
|
|
* @type {Number}
|
|
* @default waveHeight = 40
|
|
*/
|
|
waveHeight: 40,
|
|
/**
|
|
* @description Wave opacity
|
|
* @type {Number}
|
|
* @default waveOpacity = 0.4
|
|
*/
|
|
waveOpacity: 0.4,
|
|
/**
|
|
* @description Colors (Hex|rgb|rgba)
|
|
* @type {Array<String>}
|
|
* @default colors = ['#00BAFF', '#3DE7C9']
|
|
*/
|
|
colors: ['#3DE7C9', '#00BAFF'],
|
|
/**
|
|
* @description Formatter
|
|
* @type {String}
|
|
* @default formatter = '{value}%'
|
|
*/
|
|
formatter: '{value}%'
|
|
},
|
|
|
|
mergedConfig: {},
|
|
|
|
render: null,
|
|
|
|
svgBorderGradient: [],
|
|
|
|
details: '',
|
|
|
|
waves: [],
|
|
|
|
animation: false
|
|
}
|
|
},
|
|
computed: {
|
|
radius () {
|
|
const { shape } = this.mergedConfig
|
|
|
|
if (shape === 'round') return '50%'
|
|
|
|
if (shape === 'rect') return '0'
|
|
|
|
if (shape === 'roundRect') return '10px'
|
|
|
|
return '0'
|
|
},
|
|
shape () {
|
|
const { shape } = this.mergedConfig
|
|
|
|
if (!shape) return 'rect'
|
|
|
|
return shape
|
|
}
|
|
},
|
|
watch: {
|
|
config () {
|
|
const { calcData, render } = this
|
|
|
|
render.delAllGraph()
|
|
|
|
this.waves = []
|
|
|
|
setTimeout(calcData, 0)
|
|
}
|
|
},
|
|
methods: {
|
|
init () {
|
|
const { initRender, config, calcData } = this
|
|
|
|
initRender()
|
|
|
|
if (!config) return
|
|
|
|
calcData()
|
|
},
|
|
initRender () {
|
|
const { $refs } = this
|
|
|
|
this.render = new CRender($refs['water-pond-level'])
|
|
},
|
|
calcData () {
|
|
const { mergeConfig, calcSvgBorderGradient, calcDetails } = this
|
|
|
|
mergeConfig()
|
|
|
|
calcSvgBorderGradient()
|
|
|
|
calcDetails()
|
|
|
|
const { addWave, animationWave } = this
|
|
|
|
addWave()
|
|
|
|
animationWave()
|
|
},
|
|
mergeConfig () {
|
|
const { config, defaultConfig } = this
|
|
|
|
this.mergedConfig = deepMerge(deepClone(defaultConfig, true), config)
|
|
},
|
|
calcSvgBorderGradient () {
|
|
const { colors } = this.mergedConfig
|
|
|
|
const colorNum = colors.length
|
|
|
|
const colorOffsetGap = 100 / (colorNum - 1)
|
|
|
|
this.svgBorderGradient = colors.map((c, i) => [colorOffsetGap * i, c])
|
|
},
|
|
calcDetails () {
|
|
const { data, formatter } = this.mergedConfig
|
|
|
|
if (!data.length) {
|
|
this.details = ''
|
|
|
|
return
|
|
}
|
|
|
|
const maxValue = Math.max(...data)
|
|
|
|
this.details = formatter.replace('{value}', maxValue)
|
|
},
|
|
addWave () {
|
|
const { render, getWaveShapes, getWaveStyle, drawed } = this
|
|
|
|
const shapes = getWaveShapes()
|
|
const style = getWaveStyle()
|
|
|
|
this.waves = shapes.map(shape => render.add({
|
|
name: 'smoothline',
|
|
animationFrame: 300,
|
|
shape,
|
|
style,
|
|
drawed
|
|
}))
|
|
},
|
|
getWaveShapes () {
|
|
const { mergedConfig, render, mergeOffset } = this
|
|
|
|
const { waveNum, waveHeight, data } = mergedConfig
|
|
|
|
const [w, h] = render.area
|
|
|
|
const pointsNum = waveNum * 4 + 4
|
|
|
|
const pointXGap = w / waveNum / 2
|
|
|
|
return data.map(v => {
|
|
let points = new Array(pointsNum).fill(0).map((foo, j) => {
|
|
const x = w - pointXGap * j
|
|
|
|
const startY = (1 - v / 100) * h
|
|
|
|
const y = j % 2 === 0 ? startY : startY - waveHeight
|
|
|
|
return [x, y]
|
|
})
|
|
|
|
points = points.map(p => mergeOffset(p, [pointXGap * 2, 0]))
|
|
|
|
return { points }
|
|
})
|
|
},
|
|
mergeOffset ([x, y], [ox, oy]) {
|
|
return [x + ox, y + oy]
|
|
},
|
|
getWaveStyle () {
|
|
const { render, mergedConfig } = this
|
|
|
|
const h = render.area[1]
|
|
|
|
return {
|
|
gradientColor: mergedConfig.colors,
|
|
gradientType: 'linear',
|
|
gradientParams: [0, 0, 0, h],
|
|
gradientWith: 'fill',
|
|
opacity: mergedConfig.waveOpacity,
|
|
translate: [0, 0]
|
|
}
|
|
},
|
|
drawed ({ shape: { points } }, { ctx, area }) {
|
|
const firstPoint = points[0]
|
|
const lastPoint = points.slice(-1)[0]
|
|
|
|
const [w, h] = area
|
|
|
|
ctx.lineTo(lastPoint[0], h)
|
|
ctx.lineTo(firstPoint[0], h)
|
|
|
|
ctx.closePath()
|
|
|
|
ctx.fill()
|
|
},
|
|
async animationWave (repeat = 1) {
|
|
const { waves, render, animation } = this
|
|
|
|
if (animation) return
|
|
|
|
this.animation = true
|
|
|
|
const w = render.area[0]
|
|
|
|
waves.forEach(graph => {
|
|
const reset = repeat % 2 === 0
|
|
|
|
graph.attr('style', { translate: [0, 0] })
|
|
|
|
graph.animation('style', {
|
|
translate: [w, 0]
|
|
}, true)
|
|
})
|
|
|
|
await render.launchAnimation()
|
|
|
|
this.animation = false
|
|
|
|
if (!render.graphs.length) return
|
|
|
|
this.animationWave(repeat + 1)
|
|
}
|
|
},
|
|
mounted () {
|
|
const { init } = this
|
|
|
|
init()
|
|
},
|
|
beforeDestroy () {
|
|
const { calcData, render } = this
|
|
|
|
render.delAllGraph()
|
|
|
|
this.waves = []
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style lang="less">
|
|
.dv-water-pond-level {
|
|
position: relative;
|
|
|
|
svg {
|
|
position: absolute;
|
|
width: 100%;
|
|
height: 100%;
|
|
top: 0px;
|
|
left: 0px;
|
|
}
|
|
|
|
text {
|
|
font-size: 25px;
|
|
font-weight: bold;
|
|
text-anchor: middle;
|
|
dominant-baseline: middle;
|
|
}
|
|
|
|
ellipse, rect {
|
|
fill: none;
|
|
stroke-width: 3;
|
|
}
|
|
|
|
canvas {
|
|
margin-top: 8px;
|
|
margin-left: 8px;
|
|
width: calc(~"100% - 16px");
|
|
height: calc(~"100% - 16px");
|
|
box-sizing: border-box;
|
|
}
|
|
}
|
|
</style>
|