This commit is contained in:
jiaming743
2019-01-16 16:56:11 +08:00
parent e2e51986cb
commit 9de66342ce
99 changed files with 1102 additions and 11649 deletions

View File

@ -0,0 +1,339 @@
<template>
<div class="arc-ring-chart">
<loading v-if="!status" />
<div class="canvas-container">
<canvas :ref="ref" />
</div>
<label-line :label="dealAfterLabelLine" :colors="drawColors" />
</div>
</template>
<script>
import colorsMixin from '../../mixins/colorsMixin.js'
import canvasMixin from '../../mixins/canvasMixin.js'
export default {
name: 'ArcRingChart',
props: ['data', 'labelLine', 'colors'],
mixins: [colorsMixin, canvasMixin],
data () {
return {
ref: `concentric-arc-chart-${(new Date()).getTime()}`,
status: false,
defaultDecorationCircleRadius: 0.65,
defaultArcRadiusArea: [0.3, 0.4],
defaultArcWidthArea: [2, 10],
defaultLabelFontSize: 12,
decorationRadius: '',
radianOffset: Math.PI / -2,
totalValue: 0,
arcRadius: [],
arcRadian: [],
arcWidth: [],
labelLinePoints: [],
dealAfterLabelLine: []
}
},
watch: {
data (d) {
const { checkData, draw } = this
checkData() && draw()
}
},
methods: {
async init () {
const { initCanvas, initColors, checkData, draw } = this
await initCanvas()
initColors()
checkData() && draw()
},
checkData () {
const { data } = this
this.status = false
if (!data || !data.series) return false
this.status = true
return true
},
draw () {
const { clearCanvas, calcLabelLineData, drawDecorationCircle } = this
clearCanvas()
calcLabelLineData()
drawDecorationCircle()
const { calcArcRadius, calcArcRadian, calcArcWidth, drawArc } = this
calcArcRadius()
calcArcRadian()
calcArcWidth()
drawArc()
const { calcLableLinePoints, drawLabelLine, drawLabelText } = this
calcLableLinePoints()
drawLabelLine()
drawLabelText()
},
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)
},
drawDecorationCircle () {
const { ctx, data: { decorationCircleRadius }, defaultDecorationCircleRadius, centerPos } = this
const radius = this.decorationRadius = Math.min(...centerPos) * (decorationCircleRadius || defaultDecorationCircleRadius)
ctx.beginPath()
ctx.strokeStyle = 'rgba(250, 250, 250, 0.2)'
ctx.lineWidth = 4
ctx.arc(...centerPos, radius, 0, Math.PI * 2)
ctx.stroke()
ctx.beginPath()
ctx.lineWidth = 1
ctx.arc(...centerPos, radius - 7, 0, Math.PI * 2)
ctx.closePath()
ctx.stroke()
},
calcArcRadius () {
const { data: { series, arcRadiusArea }, defaultArcRadiusArea, centerPos, randomExtend } = this
const fullRadius = Math.min(...centerPos)
const currentArcRaidusArea = arcRadiusArea || defaultArcRadiusArea
const maxRadius = fullRadius * Math.max(...currentArcRaidusArea)
const minRadius = fullRadius * Math.min(...currentArcRaidusArea)
this.arcRadius = series.map(t => randomExtend(minRadius, maxRadius))
},
calcArcRadian () {
const { data: { series }, multipleSum, radianOffset } = this
const valueSum = this.totalValue = multipleSum(...series.map(({ value }) => value))
let radian = radianOffset
const fullRadian = Math.PI * 2
const avgRadian = fullRadian / series.length
this.arcRadian = series.map(({ value }) => {
const valueRadian = valueSum === 0 ? avgRadian : value / valueSum * fullRadian
return [radian, (radian += valueRadian)]
})
},
calcArcWidth () {
const { data: { series, arcWidthArea }, defaultArcWidthArea, randomExtend } = this
const currentArea = arcWidthArea || defaultArcWidthArea
const maxWidth = Math.max(...currentArea)
const minWidth = Math.min(...currentArea)
this.arcWidth = series.map(t => randomExtend(minWidth, maxWidth))
},
drawArc () {
const { ctx, arcRadius, arcRadian, arcWidth, drawColors, centerPos } = this
const colorNum = drawColors.length
arcRadius.forEach((radius, i) => {
ctx.beginPath()
ctx.arc(...centerPos, radius, ...arcRadian[i])
ctx.strokeStyle = drawColors[i % colorNum]
ctx.lineWidth = arcWidth[i]
ctx.stroke()
})
},
calcLableLinePoints () {
const { arcRadian, arcRadius, centerPos: [x, y], totalValue } = this
const { canvas: { getCircleRadianPoint }, data: { series } } = this
let [leftlabelLineNum, rightLabelLineNum] = [0, 0]
const arcMiddlePoints = arcRadian.map((radian, i) => {
const middleRadian = (radian[1] - radian[0]) / 2 + radian[0]
const point = getCircleRadianPoint(x, y, arcRadius[i], middleRadian)
point[0] > x && (series[i].value || !totalValue) && rightLabelLineNum++
point[0] <= x && (series[i].value || !totalValue) && leftlabelLineNum++
return point
})
const { getYPos, decorationRadius } = this
const labelLineYArea = [y - decorationRadius + 10, y + decorationRadius - 10]
const leftYPos = getYPos(labelLineYArea, leftlabelLineNum)
const rightYPos = getYPos(labelLineYArea, rightLabelLineNum)
const offsetX = decorationRadius + 10
const leftlabelLineEndX = x - offsetX
const rightLableLineEndX = x + offsetX
const maxRadius = Math.max(...arcRadius)
const leftNearRadiusX = x - maxRadius - 8
const rightNearRadiusX = x + maxRadius + 8
this.labelLinePoints = arcMiddlePoints.map(([px, py], i) => {
if (!series[i].value && totalValue) return [false, false, false, false]
if (px > x) {
const yPos = rightYPos.shift()
return [
[px, py],
[rightNearRadiusX, py],
[rightLableLineEndX - 10, yPos],
[rightLableLineEndX, yPos]
]
} else {
const yPos = leftYPos.pop()
return [
[px, py],
[leftNearRadiusX, py],
[leftlabelLineEndX + 10, yPos],
[leftlabelLineEndX, yPos]
]
}
})
},
getYPos (area, num) {
let gap = 0
const minus = area[1] - area[0]
if (num === 1) {
return [area[0] + minus / 2]
} else if (num === 2) {
const offset = minus * 0.1
return [area[0] + offset, area[1] - offset]
} else {
gap = minus / (num - 1)
return new Array(num).fill(0).map((t, i) => area[0] + i * gap)
}
},
drawLabelLine () {
const { ctx, labelLinePoints, canvas: { drawPolyline }, drawColors } = this
const colorNum = drawColors.length
labelLinePoints.forEach((polyline, i) =>
polyline[0] && drawPolyline(ctx, polyline, 2, drawColors[i % colorNum], false, [10, 0], true))
},
drawLabelText () {
const { ctx, labelLinePoints, data: { series, labelFontSize, fixed }, totalValue, defaultLabelFontSize, centerPos: [x] } = this
ctx.font = `${labelFontSize || defaultLabelFontSize}px Arial`
ctx.fillStyle = '#fff'
let totalPercent = 0
const dataLast = series.length - 1
series.forEach(({ value, title }, i) => {
if (!value && totalValue) return
let currentPercent = (value / totalValue * 100).toFixed(fixed || 1)
i === dataLast && (currentPercent = (100 - totalPercent).toFixed(fixed || 1))
!totalValue && (currentPercent = 0)
const textPos = labelLinePoints[i][2]
const isLeft = textPos[0] < x
ctx.textAlign = isLeft ? 'end' : 'start'
ctx.textBaseline = 'bottom'
ctx.fillText(`${currentPercent}%`, ...textPos)
ctx.textBaseline = 'top'
ctx.fillText(title, ...textPos)
totalPercent += Number(currentPercent)
})
}
},
mounted () {
const { init } = this
init()
}
}
</script>
<style lang="less">
.arc-ring-chart {
position: relative;
display: flex;
flex-direction: column;
color: #fff;
.canvas-container {
position: relative;
flex: 1;
}
canvas {
width: 100%;
height: 100%;
}
}
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

View File

@ -0,0 +1,54 @@
<template>
<div class="dv-border-box-1">
<img class="dv-flash-left-top" src="./img/border.gif">
<img class="dv-flash-right-top" src="./img/border.gif">
<img class="dv-flash-left-bottom" src="./img/border.gif">
<img class="dv-flash-right-bottom" src="./img/border.gif">
<slot></slot>
</div>
</template>
<script>
export default {
name: 'BorderBox1'
}
</script>
<style lang="less">
.dv-border-box-1 {
position: relative;
box-sizing: border-box;
min-width: 300px;
min-height: 300px;
padding: 35px;
overflow: hidden;
.dv-flash-left-top, .dv-flash-right-top, .dv-flash-left-bottom, .dv-flash-right-bottom {
position: absolute;
width: 250px;
}
.dv-flash-left-top {
top: 0px;
left: -70px;
}
.dv-flash-right-top {
top: 0px;
right: -70px;
transform: rotateY(180deg);
}
.dv-flash-left-bottom {
bottom: 0px;
left: -70px;
transform: rotateX(180deg);
}
.dv-flash-right-bottom {
bottom: 0px;
right: -70px;
transform: rotate(180deg);
}
}
</style>

View File

@ -0,0 +1,63 @@
<template>
<div class="dv-border-box-2" :ref="ref">
<svg class="dv-border-svg-container">
<polyline class="dv-bb2-line1"
:points="`2, 2 ${width - 2} ,2 ${width - 2}, ${height - 2} 2, ${height - 2} 2, 2`" />
<polyline class="dv-bb2-line2"
:points="`6, 6 ${width - 6}, 6 ${width - 6}, ${height - 6} 6, ${height - 6} 6, 6`" />
<circle cx="11" cy="11" r="1" />
<circle :cx="width - 11" cy="11" r="1" />
<circle :cx="width - 11" :cy="height - 11" r="1" />
<circle cx="11" :cy="height - 11" r="1" />
</svg>
<slot></slot>
</div>
</template>
<script>
import borderBoxMixin from '../../mixins/borderBoxMixin.js'
export default {
name: 'BorderBox2',
mixins: [borderBoxMixin],
data () {
return {
ref: `border-box-2-${(new Date()).getTime()}`
}
}
}
</script>
<style lang="less">
.dv-border-box-2 {
position: relative;
box-sizing: border-box;
padding: 30px;
.dv-border-svg-container {
position: absolute;
width: 100%;
height: 100%;
top: 0px;
left: 0px;
polyline {
fill: none;
stroke-width: 1;
}
circle {
fill: #fff;
}
}
.dv-bb2-line1 {
stroke: #fff;
}
.dv-bb2-line2 {
stroke: fade(#fff, 60);
}
}
</style>

View File

@ -0,0 +1,59 @@
<template>
<div class="dv-border-box-3" :ref="ref">
<svg class="dv-border-svg-container">
<polyline class="dv-bb3-line1"
:points="`4, 4 ${width - 22} ,4 ${width - 22}, ${height - 22} 4, ${height - 22} 4, 4`" />
<polyline class="dv-bb3-line2"
:points="`10, 10 ${width - 16}, 10 ${width - 16}, ${height - 16} 10, ${height - 16} 10, 10`" />
<polyline class="dv-bb3-line2"
:points="`16, 16 ${width - 10}, 16 ${width - 10}, ${height - 10} 16, ${height - 10} 16, 16`" />
<polyline class="dv-bb3-line2"
:points="`22, 22 ${width - 4}, 22 ${width - 4}, ${height - 4} 22, ${height - 4} 22, 22`" />
</svg>
<slot></slot>
</div>
</template>
<script>
import borderBoxMixin from '../../mixins/borderBoxMixin.js'
export default {
name: 'BorderBox3',
mixins: [borderBoxMixin],
data () {
return {
ref: `border-box-3-${(new Date()).getTime()}`
}
}
}
</script>
<style lang="less">
.dv-border-box-3 {
position: relative;
box-sizing: border-box;
padding: 30px;
.dv-border-svg-container {
position: absolute;
width: 100%;
height: 100%;
top: 0px;
left: 0px;
polyline {
fill: none;
stroke: #2862b7;
}
}
.dv-bb3-line1 {
stroke-width: 3;
}
.dv-bb3-line2 {
stroke-width: 1;
}
}
</style>

View File

@ -0,0 +1,128 @@
<template>
<div class="dv-border-box-4" :ref="ref">
<svg :class="`dv-border-svg-container ${reverse && 'dv-reverse'}`">
<polyline class="dv-bb4-line-1" :points="`145, ${height - 5} 40, ${height - 5} 10, ${height - 35}
10, 40 40, 5 150, 5 170, 20 ${width - 15}, 20`"/>
<polyline class="dv-bb4-line-2" :points="`245, ${height - 1} 36, ${height - 1} 14, ${height - 23}
14, ${height - 100}`" />
<polyline class="dv-bb4-line-3" :points="`7, ${height - 40} 7, ${height - 75}`" />
<polyline class="dv-bb4-line-4" :points="`28, 24 13, 41 13, 64`" />
<polyline class="dv-bb4-line-5" :points="`5, 45 5, 140`" />
<polyline class="dv-bb4-line-6" :points="`14, 75 14, 180`" />
<polyline class="dv-bb4-line-7" :points="`55, 11 147, 11 167, 26 250, 26`" />
<polyline class="dv-bb4-line-8" :points="`158, 5 173, 16`" />
<polyline class="dv-bb4-line-9" :points="`200, 17 ${width - 10}, 17`" />
<polyline class="dv-bb4-line-10" :points="`385, 17 ${width - 10}, 17`" />
</svg>
<slot></slot>
</div>
</template>
<script>
import borderBoxMixin from '../../mixins/borderBoxMixin.js'
export default {
name: 'BorderBox4',
mixins: [borderBoxMixin],
data () {
return {
ref: `border-box-4-${(new Date()).getTime()}`
}
},
props: ['reverse']
}
</script>
<style lang="less" scoped>
.dv-border-box-4 {
position: relative;
box-sizing: border-box;
padding: 30px;
.dv-reverse {
transform: rotate(180deg);
}
.dv-border-svg-container {
position: absolute;
width: 100%;
height: 100%;
top: 0px;
left: 0px;
polyline {
fill: none;
}
}
.sred {
stroke: red;
}
.sblue {
stroke: fade(blue, 80);
}
.sw1 {
stroke-width: 1;
}
.sw3 {
stroke-width: 3px;
stroke-linecap: round;
}
.dv-bb4-line-1 {
.sred;
.sw1;
}
.dv-bb4-line-2 {
.sblue;
.sw1;
}
.dv-bb4-line-3 {
.sred;
.sw3;
}
.dv-bb4-line-4 {
.sred;
.sw3;
}
.dv-bb4-line-5 {
.sred;
.sw1;
}
.dv-bb4-line-6 {
.sblue;
.sw1;
}
.dv-bb4-line-7 {
.sblue;
.sw1;
}
.dv-bb4-line-8 {
.sblue;
.sw3;
}
.dv-bb4-line-9 {
.sred;
.sw3;
stroke-dasharray: 100 250;
}
.dv-bb4-line-10 {
.sblue;
.sw1;
stroke-dasharray: 80 270;
}
}
</style>

View File

@ -0,0 +1,75 @@
<template>
<div class="dv-border-box-5" :ref="ref">
<svg :class="`dv-svg-container ${reverse && 'dv-reverse'}`">
<polyline class="dv-bb5-line-1" :points="`8, 5 ${width - 5}, 5 ${width - 5}, ${height - 100}
${width - 100}, ${height - 5} 8, ${height - 5} 8, 5`" />
<polyline class="dv-bb5-line-2" :points="`3, 5 ${width - 20}, 5 ${width - 20}, ${height - 60}
${width - 74}, ${height - 5} 3, ${height - 5} 3, 5`" />
<polyline class="dv-bb5-line-3" :points="`50, 13 ${width - 35}, 13`" />
<polyline class="dv-bb5-line-4" :points="`15, 20 ${width - 35}, 20`" />
<polyline class="dv-bb5-line-5" :points="`15, ${height - 20} ${width - 110}, ${height - 20}`" />
<polyline class="dv-bb5-line-6" :points="`15, ${height - 13} ${width - 110}, ${height - 13}`" />
</svg>
<slot></slot>
</div>
</template>
<script>
import borderBoxMixin from '../../mixins/borderBoxMixin.js'
export default {
name: 'BorderBox5',
mixins: [borderBoxMixin],
data () {
return {
ref: `border-box-5-${(new Date()).getTime()}`
}
},
props: ['reverse']
}
</script>
<style lang="less">
.dv-border-box-5 {
position: relative;
box-sizing: border-box;
padding: 20px;
.dv-reverse {
transform: rotate(180deg);
}
.dv-svg-container {
position: absolute;
top: 0px;
left: 0px;
width: 100%;
height: 100%;
polyline {
fill: none;
}
}
.dv-bb5-line-1 {
stroke-width: 1;
stroke: fade(#fff, 35);
}
.dv-bb5-line-2 {
stroke: fade(#fff, 20);
}
.dv-bb5-line-3, .dv-bb5-line-6 {
stroke-width: 5;
stroke: fade(#fff, 15);
}
.dv-bb5-line-4, .dv-bb5-line-5 {
stroke-width: 2;
stroke: fade(#fff, 15);
}
}
</style>

View File

@ -0,0 +1,64 @@
<template>
<div class="dv-border-box-6" :ref="ref">
<svg class="dv-svg-container">
<circle cx="5" cy="5" r="2"/>
<circle :cx="width - 5" cy="5" r="2" />
<circle :cx="width - 5" :cy="height - 5" r="2" />
<circle cx="5" :cy="height - 5" r="2" />
<polyline :points="`10, 4 ${width - 10}, 4`" />
<polyline :points="`10, ${height - 4} ${width - 10}, ${height - 4}`" />
<polyline :points="`5, 70 5, ${height - 70}`" />
<polyline :points="`${width - 5}, 70 ${width - 5}, ${height - 70}`" />
<polyline :points="`3, 10, 3, 50`" />
<polyline :points="`7, 30 7, 80`" />
<polyline :points="`${width - 3}, 10 ${width - 3}, 50`" />
<polyline :points="`${width - 7}, 30 ${width - 7}, 80`" />
<polyline :points="`3, ${height - 10} 3, ${height - 50}`" />
<polyline :points="`7, ${height - 30} 7, ${height - 80}`" />
<polyline :points="`${width - 3}, ${height - 10} ${width - 3}, ${height - 50}`" />
<polyline :points="`${width - 7}, ${height - 30} ${width - 7}, ${height - 80}`" />
</svg>
<slot></slot>
</div>
</template>
<script>
import borderBoxMixin from '../../mixins/borderBoxMixin.js'
export default {
name: 'BorderBox6',
mixins: [borderBoxMixin],
data () {
return {
ref: `border-box-6-${(new Date()).getTime()}`
}
}
}
</script>
<style lang="less">
.dv-border-box-6 {
position: relative;
box-sizing: border-box;
padding: 10px;
.dv-svg-container {
position: absolute;
top: 0px;
left: 0px;
width: 100%;
height: 100%;
circle {
fill: gray;
}
polyline {
fill: none;
stroke-width: 1;
stroke: fade(#fff, 35);
}
}
}
</style>

View File

@ -0,0 +1,66 @@
<template>
<div class="dv-border-box-7" :ref="ref">
<svg class="dv-svg-container">
<polyline class="dv-bb7-line-width-2" :points="`0, 25 0, 0 25, 0`" />
<polyline class="dv-bb7-line-width-2" :points="`${width - 25}, 0 ${width}, 0 ${width}, 25`" />
<polyline class="dv-bb7-line-width-2" :points="`${width - 25}, ${height} ${width}, ${height} ${width}, ${height - 25}`" />
<polyline class="dv-bb7-line-width-2" :points="`0, ${height - 25} 0, ${height} 25, ${height}`" />
<polyline class="dv-bb7-line-width-5" :points="`0, 10 0, 0 10, 0`" />
<polyline class="dv-bb7-line-width-5" :points="`${width - 10}, 0 ${width}, 0 ${width}, 10`" />
<polyline class="dv-bb7-line-width-5" :points="`${width - 10}, ${height} ${width}, ${height} ${width}, ${height - 10}`" />
<polyline class="dv-bb7-line-width-5" :points="`0, ${height - 10} 0, ${height} 10, ${height}`" />
</svg>
<slot></slot>
</div>
</template>
<script>
import borderBoxMixin from '../../mixins/borderBoxMixin.js'
export default {
name: 'BorderBox7',
mixins: [borderBoxMixin],
data () {
return {
ref: `border-box-7-${(new Date()).getTime()}`
}
}
}
</script>
<style lang="less">
@color: fade(gray, 30);
.dv-border-box-7 {
position: relative;
box-shadow: inset 0 0 40px fade(@color, 30);
box-sizing: border-box;
border: 1px solid @color;
padding: 10px;
.dv-svg-container {
position: absolute;
top: 0px;
left: 0px;
width: 100%;
height: 100%;
polyline {
fill: none;
stroke-linecap: round;
}
}
.dv-bb7-line-width-2 {
stroke: @color;
stroke-width: 2;
}
.dv-bb7-line-width-5 {
stroke: fade(gray, 50);
stroke-width: 5;
}
}
</style>

View File

@ -0,0 +1,167 @@
<template>
<div class="capsule-chart">
<loading v-if="!status" />
<template v-else>
<div class="label-column">
<div v-for="item in data.series" :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: ${drawColors[index % drawColors.length]};`"></div>
</div>
<div class="unit-label">
<div class="unit-container">
<div v-for="(unit, index) in unitData" :key="unit + index">{{ unit }}</div>
</div>
<div class="unit-text">单位</div>
</div>
</div>
<div class="for-solt">
<slot></slot>
</div>
</template>
</div>
</template>
<script>
import colorsMixin from '../../mixins/colorsMixin.js'
export default {
name: 'CapsuleChart',
props: ['data', 'colors'],
mixins: [colorsMixin],
data () {
return {
status: false,
capsuleData: [],
unitData: []
}
},
watch: {
data () {
const { init } = this
init()
}
},
methods: {
init () {
const { checkData, initColors, calcCapsuleAndUnitData } = this
initColors()
checkData() && calcCapsuleAndUnitData()
},
checkData () {
const { data } = this
this.status = false
if (!data || !data.series) return false
this.status = true
return true
},
calcCapsuleAndUnitData () {
const { data: { series } } = this
const capsuleData = series.map(({ value }) => value)
const maxValue = Math.max(...capsuleData)
this.capsuleData = capsuleData.map(v => maxValue ? v / maxValue : 0)
const oneSixth = maxValue / 5
this.unitData = new Array(6).fill(0).map((v, i) => Math.ceil(i * oneSixth))
}
},
mounted () {
const { init } = this
init()
}
}
</script>
<style lang="less">
.capsule-chart {
position: relative;
display: flex;
flex-direction: row;
box-sizing: border-box;
padding: 10px;
color: #fff;
.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;
}
.for-solt {
position: absolute;
width: 100%;
height: 100%;
top: 0px;
left: 0px;
}
}
</style>

View File

@ -0,0 +1,620 @@
<template>
<div class="column-chart">
<loading v-if="!status" />
<div class="canvas-container">
<canvas :ref="ref" />
</div>
<label-line :label="labelLine" :colors="drawColors" />
<for-slot><slot></slot></for-slot>
</div>
</template>
<script>
import colorsMixin from '../../mixins/colorsMixin.js'
import canvasMixin from '../../mixins/canvasMixin.js'
import axisMixin from '../../mixins/axisMixin.js'
export default {
name: 'ColumnChart',
mixins: [colorsMixin, canvasMixin, axisMixin],
props: ['data', 'labelLine', 'colors'],
data () {
return {
ref: `radar-chart-${(new Date()).getTime()}`,
status: false,
// axis base config
boundaryGap: true,
mulValueAdd: true,
horizon: false,
echelonOffset: 10,
defaultColumnBGColor: 'rgba(100, 100, 100, 0.2)',
defaultValueFontSize: 10,
defaultValueColor: '#666',
columnData: [],
columnItemSeriesNum: 0,
columnItemAllWidth: 0,
columnItemWidth: 0,
columnBGWidth: 0,
columnItemOffset: [],
valueTextOffset: [],
valuePointPos: []
}
},
watch: {
data () {
const { checkData, draw } = this
checkData() && draw()
}
},
methods: {
async init () {
const { initCanvas, initColors } = this
await initCanvas()
initColors()
const { checkData, draw } = this
checkData() && draw()
},
checkData () {
const { data } = this
this.status = false
if (!data || !data.series) return false
this.status = true
return true
},
draw () {
const { clearCanvas } = this
clearCanvas()
const { calcHorizon, initAxis, drawAxis } = this
calcHorizon()
initAxis()
drawAxis()
const { switchNormalOrCenterOriginType } = this
switchNormalOrCenterOriginType()
},
calcHorizon () {
const { data: { horizon } } = this
this.horizon = horizon
},
switchNormalOrCenterOriginType () {
const { centerOrigin, drawNormalTypeColumnChart, drawCenterOriginTypeColumnChart } = this
if (centerOrigin) drawCenterOriginTypeColumnChart()
if (!centerOrigin) drawNormalTypeColumnChart()
},
drawNormalTypeColumnChart () {
const { calcColumnConfig, calcColumnItemOffset, calcValuePointPos } = this
calcColumnConfig()
calcColumnItemOffset()
calcValuePointPos()
const { drawColumnBG, drawFigure, drawValueText } = this
drawColumnBG()
drawFigure()
drawValueText()
},
calcColumnConfig () {
const { data: { series, spaceBetween }, labelAxisTagPos, axisOriginPos, horizon } = this
const columnData = this.columnData = series.filter(({ type }) =>
!(type === 'polyline' || type === 'smoothline'))
const columnItemSeriesNum = this.columnItemSeriesNum = columnData.length
const columnItemAllWidth = this.columnItemAllWidth = (horizon
? axisOriginPos[1] - labelAxisTagPos[0][1]
: labelAxisTagPos[0][0] - axisOriginPos[0]) * 2
const columnItemWidth = this.columnItemWidth = columnItemAllWidth / (columnItemSeriesNum + 1)
const spaceGap = columnItemWidth / (columnItemSeriesNum + 1)
let columnBGWidth = columnItemWidth * columnItemSeriesNum
spaceBetween && (columnBGWidth += spaceGap * (columnItemSeriesNum - 1))
this.columnBGWidth = columnBGWidth
},
calcColumnItemOffset () {
const { columnItemSeriesNum, columnItemAllWidth, columnItemWidth } = this
const { data: { spaceBetween, series } } = this
const halfColumnWidth = columnItemWidth / 2
const halfColumnItemAllWidth = columnItemAllWidth / 2
let columnItemOffset = new Array(columnItemSeriesNum).fill(0)
if (spaceBetween) {
const spaceGap = columnItemWidth / (columnItemSeriesNum + 1)
columnItemOffset = columnItemOffset.map((t, i) =>
spaceGap * (i + 1) + columnItemWidth * i + halfColumnWidth - halfColumnItemAllWidth)
}
if (!spaceBetween) {
columnItemOffset = columnItemOffset.map((t, i) =>
columnItemWidth * (i + 1) - halfColumnItemAllWidth)
}
this.columnItemOffset = series.map(({ type }) =>
(type === 'polyline' || type === 'smoothline')
? 0
: columnItemOffset.shift())
},
calcValuePointPos () {
const { getAxisPointsPos, valueAxisMaxMin, agValueAxisMaxMin } = this
const { labelAxisTagPos, deepClone, filterNull, multipleSum } = this
const { data: { series }, axisOriginPos, axisWH, horizon } = this
const dealAfterData = deepClone(series).map(({ value, againstAxis }) => {
if (!(value[0] instanceof Array)) return { value, againstAxis }
const valueData = value.map(valueSeries => valueSeries.map((v, i) => {
if (!v && v !== 0) return false
return multipleSum(...filterNull(valueSeries.slice(0, i + 1)))
}))
return { value: valueData, againstAxis }
})
this.valuePointPos = dealAfterData.map(({ value, againstAxis }) =>
getAxisPointsPos(
againstAxis ? agValueAxisMaxMin : valueAxisMaxMin,
value,
axisOriginPos,
axisWH,
labelAxisTagPos,
horizon
))
},
drawColumnBG () {
const { ctx, data: { showColumnBG, columnBGColor, roundColumn } } = this
if (!showColumnBG) return
const { columnBGWidth, defaultColumnBGColor, horizon, axisWH: [w, h], labelAxisTagPos } = this
const trueColumnColor = columnBGColor || defaultColumnBGColor
ctx.lineWidth = columnBGWidth
ctx.strokeStyle = trueColumnColor
ctx.setLineDash([10, 0])
const { getRoundColumnPoint, labelAxisTag } = this
ctx.lineCap = roundColumn ? 'round' : 'butt'
labelAxisTagPos.forEach(([x, y], i) => {
if (!labelAxisTag[i] && labelAxisTag[i] !== 0) return
const topPoint = horizon ? [x + w, y] : [x, y - h]
let columnBGPoints = [[x, y], topPoint]
roundColumn && (columnBGPoints = getRoundColumnPoint(columnBGPoints, columnBGWidth))
ctx.beginPath()
ctx.moveTo(...columnBGPoints[0])
ctx.lineTo(...columnBGPoints[1])
ctx.stroke()
})
},
drawFigure () {
const { data: { series }, valuePointPos } = this
const { drawColumn, drawEchelon, drawline } = this
series.forEach((valueSeries, i) => {
switch (valueSeries.type) {
case 'leftEchelon':
case 'rightEchelon': drawEchelon(valueSeries, valuePointPos[i], i)
break
case 'polyline':
case 'smoothline': drawline(valueSeries, valuePointPos[i], i)
break
default: drawColumn(valueSeries, valuePointPos[i], i)
break
}
})
},
getGradientColor (value, colors) {
const { data: { localGradient }, axisAnglePos, horizon } = this
const { ctx, canvas: { getLinearGradientColor } } = this
if (localGradient) {
return getLinearGradientColor(ctx,
...(horizon
? [value, [axisAnglePos.leftTop[0], value[1]]]
: [value, [value[0], axisAnglePos.leftBottom[1]]]),
colors)
} else {
return getLinearGradientColor(ctx,
...(horizon
? [axisAnglePos.leftTop, axisAnglePos.rightTop]
: [axisAnglePos.leftTop, axisAnglePos.leftBottom]),
colors)
}
},
drawColumn ({ fillColor }, points, i) {
const { ctx, columnItemWidth, drawColors } = this
ctx.setLineDash([10, 0])
ctx.lineWidth = columnItemWidth
const color = fillColor || drawColors[i]
const colorNum = color.length
const drawColorNum = drawColors.length
const { columnItemOffset, labelAxisTagPos, getOffsetPoint } = this
const currentOffset = columnItemOffset[i]
const offsetTagPos = labelAxisTagPos.map(p => getOffsetPoint(p, currentOffset))
const { getGradientColor, getRoundColumnPoint, data: { roundColumn } } = this
ctx.lineCap = roundColumn ? 'round' : 'butt'
const seriesColumn = points[0][0] instanceof Array
seriesColumn && points.forEach((series, j) => {
let lastEnd = offsetTagPos[j]
series.forEach((item, k) => {
if (!item && item !== 0) return
const currentPoint = getOffsetPoint(item, currentOffset)
let columnPoint = [lastEnd, currentPoint]
roundColumn && (columnPoint = getRoundColumnPoint(columnPoint))
if (typeof color === 'string') {
ctx.strokeStyle = drawColors[(i + k) % drawColorNum]
} else {
ctx.strokeStyle = color[k % colorNum]
}
ctx.beginPath()
ctx.moveTo(...columnPoint[0])
ctx.lineTo(...columnPoint[1])
ctx.stroke()
lastEnd = currentPoint
})
})
!seriesColumn && points.forEach((point, i) => {
if (!point && point !== 0) return
let columnPoint = [offsetTagPos[i], getOffsetPoint(point, currentOffset)]
roundColumn && (columnPoint = getRoundColumnPoint(columnPoint))
ctx.beginPath()
ctx.strokeStyle = getGradientColor(point, color)
ctx.moveTo(...columnPoint[0])
ctx.lineTo(...columnPoint[1])
ctx.stroke()
})
},
getOffsetPoint ([x, y], offset) {
const { horizon } = this
return horizon
? [x, y + offset]
: [x + offset, y]
},
getOffsetPoints (points, offset) {
const { getOffsetPoint } = this
return points.map(point => point ? getOffsetPoint(point, offset) : false)
},
getRoundColumnPoint ([pa, pb], cw = false) {
const { horizon, columnItemWidth: dciw } = this
const columnWidth = cw || dciw
const radius = columnWidth / 2
let [a, b, c, d] = [0, 0, 0, 0]
if (horizon) {
a = pa[0] + radius
b = pa[1]
c = pb[0] - radius
d = pb[1]
} else {
a = pa[0]
b = pa[1] - radius
c = pb[0]
d = pb[1] + radius
}
return horizon ? [
[a > c ? c : a, b],
[c, d]
] : [
[a, b],
[c, b > d ? d : b]
]
},
drawline ({ lineColor, fillColor, pointColor, lineType, lineDash, type }, points, i) {
const { drawColors, ctx, axisOriginPos: [x, y], horizon } = this
const { color: { hexToRgb }, getGradientColor, getTopPoint } = this
const drawColorNum = drawColors.length
const currentColor = drawColors[i % drawColorNum]
let currentLineColor = hexToRgb(currentColor, 0.6)
let currentPointColor = currentColor
lineColor && (currentLineColor = lineColor)
pointColor && (currentPointColor = pointColor)
let currentLineType = lineType || 'line'
let currentLineDash = currentLineType === 'dashed' ? (lineDash || [5, 5]) : [10, 0]
ctx.strokeStyle = currentLineColor
const { canvas: { drawPolylinePath, drawPolyline, drawPoints } } = this
const { canvas: { drawSmoothlinePath, drawSmoothline } } = this
const lineFun = type === 'polyline' ? [drawPolylinePath, drawPolyline] : [drawSmoothlinePath, drawSmoothline]
if (fillColor) {
const lastPoint = points[points.length - 1]
ctx.fillStyle = getGradientColor(getTopPoint(points), fillColor)
lineFun[0](ctx, points, false, true, true)
ctx.lineTo(...(horizon ? [x, lastPoint[1]] : [lastPoint[0], y]))
ctx.lineTo(...(horizon ? [x, points[0][1]] : [points[0][0], y]))
ctx.closePath()
ctx.fill()
}
lineFun[1](ctx, points, 1, currentLineColor, false, currentLineDash, true, true)
drawPoints(ctx, points, 2, currentPointColor)
},
getTopPoint (points) {
const { horizon } = this
let topIndex = 0
const xPos = points.map(([x]) => x)
const yPos = points.map(([, y]) => y)
if (horizon) {
const top = Math.max(...xPos)
topIndex = xPos.findIndex(v => v === top)
}
if (!horizon) {
const top = Math.min(...yPos)
topIndex = yPos.findIndex(v => v === top)
}
return points[topIndex]
},
drawEchelon ({ fillColor, type }, points, i) {
const { data: { roundColumn } } = this
const seriesColumn = points[0][0] instanceof Array
if (seriesColumn || roundColumn) return
const { ctx, columnItemOffset, labelAxisTagPos, getOffsetPoint } = this
const currentOffset = columnItemOffset[i]
const offsetTagPos = labelAxisTagPos.map(p => getOffsetPoint(p, currentOffset))
const { drawColors, getGradientColor, getEchelonPoints } = this
const drawColorsNum = drawColors.length
const color = fillColor || drawColors[i % drawColorsNum]
const { canvas: { drawPolylinePath } } = this
points.forEach((point, i) => {
const topPoint = getOffsetPoint(point, currentOffset)
const bottomPoint = offsetTagPos[i]
const echelonPoints = getEchelonPoints(topPoint, bottomPoint, type)
drawPolylinePath(ctx, echelonPoints, true, true)
ctx.fillStyle = getGradientColor(point, color)
ctx.fill()
})
},
getEchelonPoints ([tx, ty], [bx, by], type) {
const { columnItemWidth, echelonOffset, horizon } = this
const halfWidth = columnItemWidth / 2
const echelonPoint = []
if (horizon) {
let enhance = tx - bx < echelonOffset
if (type === 'leftEchelon') {
echelonPoint[0] = [tx, ty + halfWidth]
echelonPoint[1] = [bx, ty + halfWidth]
echelonPoint[2] = [bx + echelonOffset, by - halfWidth]
echelonPoint[3] = [tx, ty - halfWidth]
}
if (type === 'rightEchelon') {
echelonPoint[0] = [tx, ty - halfWidth]
echelonPoint[1] = [bx, ty - halfWidth]
echelonPoint[2] = [bx + echelonOffset, by + halfWidth]
echelonPoint[3] = [tx, ty + halfWidth]
}
if (enhance) echelonPoint.splice(2, 1)
}
if (!horizon) {
let enhance = by - ty < echelonOffset
if (type === 'leftEchelon') {
echelonPoint[0] = [tx + halfWidth, ty]
echelonPoint[1] = [tx + halfWidth, by]
echelonPoint[2] = [tx - halfWidth, by - echelonOffset]
echelonPoint[3] = [tx - halfWidth, ty]
}
if (type === 'rightEchelon') {
echelonPoint[0] = [tx - halfWidth, ty]
echelonPoint[1] = [tx - halfWidth, by]
echelonPoint[2] = [tx + halfWidth, by - echelonOffset]
echelonPoint[3] = [tx + halfWidth, ty]
}
if (enhance) echelonPoint.splice(2, 1)
}
return echelonPoint
},
drawValueText () {
const { data: { showValueText }, horizon } = this
if (!showValueText) return
const { data: { valueTextFontSize, valueTextOffset, series } } = this
const { ctx, defaultValueFontSize } = this
const offset = horizon ? [5, 0] : [0, -5]
const trueOffset = valueTextOffset || offset
ctx.font = `${valueTextFontSize || defaultValueFontSize}px Arial`
ctx.textAlign = horizon ? 'left' : 'center'
ctx.textBaseline = horizon ? 'middle' : 'bottom'
const { drawSeriesTextValue } = this
series.forEach((seriesItem, i) => drawSeriesTextValue(seriesItem, i, trueOffset))
},
drawSeriesTextValue ({ value, valueTextColor, fillColor, lineColor }, i, trueOffset) {
const { ctx, valuePointPos, columnItemOffset, drawTexts, getOffsetPoints, drawColors } = this
const { data: { valueTextColor: outerValueTC }, defaultValueColor } = this
const drawColorsNum = drawColors.length
let currentColor = valueTextColor
currentColor === 'inherit' && (currentColor = fillColor || lineColor || drawColors[i % drawColorsNum])
const mulColor = currentColor instanceof Array
const colorNum = mulColor ? currentColor.length : 0
const currentPos = valuePointPos[i]
const currentOffset = columnItemOffset[i]
const mulSeries = value[0] instanceof Array
if (mulSeries) {
value.forEach((item, j) => {
const pointPos = getOffsetPoints(currentPos[j], currentOffset)
item.forEach((v, l) => {
!currentColor && (ctx.fillStyle = defaultValueColor)
currentColor && (ctx.fillStyle = mulColor ? currentColor[l % colorNum] : currentColor)
drawTexts(ctx, [item[l]], [pointPos[l]], trueOffset)
})
})
}
if (!mulSeries) {
mulColor && (currentColor = currentColor[0])
ctx.fillStyle = currentColor || outerValueTC || defaultValueColor
drawTexts(ctx, value, getOffsetPoints(currentPos, currentOffset), trueOffset)
}
},
drawTexts (ctx, values, points, [x, y] = [0, 0]) {
values.forEach((v, i) => {
if (!v && v !== 0) return
ctx.fillText(v, points[i][0] + x, points[i][1] + y)
})
},
drawCenterOriginTypeColumnChart () {}
},
mounted () {
const { init } = this
init()
}
}
</script>
<style lang="less">
.column-chart {
position: relative;
display: flex;
flex-direction: column;
.canvas-container {
flex: 1;
}
canvas {
width: 100%;
height: 100%;
}
}
</style>

View File

@ -0,0 +1,179 @@
<template>
<div class="concentric-arc-chart">
<loading v-if="!status" />
<div class="canvas-container">
<canvas :ref="ref" />
</div>
</div>
</template>
<script>
import colorsMixin from '../../mixins/colorsMixin.js'
import canvasMixin from '../../mixins/canvasMixin.js'
export default {
name: 'ConcentricArcChart',
props: ['data', 'colors'],
mixins: [colorsMixin, canvasMixin],
data () {
return {
ref: `concentric-arc-chart-${(new Date()).getTime()}`,
status: false,
arcOriginPos: [],
defaultArcRadiusArea: [0.2, 0.8],
defaultArcGap: 3,
defaultArcColor: ['#00c0ff', '#3de7c9'],
arcRadius: [],
arcRadian: [],
arcLineWidth: 0,
arcColor: ''
}
},
watch: {
data () {
const { checkData, draw } = this
checkData() && draw()
}
},
methods: {
async init () {
const { initCanvas, initColors, checkData, draw } = this
await initCanvas()
initColors()
checkData() && draw()
},
checkData () {
const { data } = this
this.status = false
if (!data || !data.series) return false
this.status = true
return true
},
draw () {
const { clearCanvas, calcArcRadius, calcArcRadian, calcArcColor, drawArc, drawTitle } = this
clearCanvas()
calcArcRadius()
calcArcRadian()
calcArcColor()
drawArc()
drawTitle()
},
calcArcRadius () {
const { data: { series, arcArea, arcGap }, centerPos, defaultArcRadiusArea, defaultArcGap } = this
const arcNum = series.length
const fullRadius = (centerPos[0] > centerPos[1] ? centerPos[1] : centerPos[0])
const currentArcArea = arcArea || defaultArcRadiusArea
const maxRadius = fullRadius * Math.max(...currentArcArea)
const minRadius = fullRadius * Math.min(...currentArcArea)
const currentArcGap = arcGap || defaultArcGap
const arcLineWidth = this.arcLineWidth = (maxRadius - minRadius - currentArcGap * (arcNum - 1)) / arcNum
const fullArcLineWidth = arcLineWidth + currentArcGap
const halfArcLineWidth = arcLineWidth / 2
this.arcRadius = new Array(arcNum).fill(0).map((t, i) => maxRadius - halfArcLineWidth - fullArcLineWidth * i)
},
calcArcRadian () {
const { data: { series } } = this
const fullRadian = Math.PI / 2 * 3
const offsetRadian = Math.PI * 0.5
this.arcRadian = new Array(series.length).fill(0).map((t, i) => series[i].value * fullRadian - offsetRadian)
},
calcArcColor () {
const { ctx, arcLineWidth, defaultArcColor, canvas: { getLinearGradientColor } } = this
const { drawColors, arcRadius: [ radius ], centerPos: [x, y] } = this
const colors = drawColors || defaultArcColor
this.arcColor = getLinearGradientColor(ctx,
[x, y - radius - arcLineWidth],
[x, y + radius + arcLineWidth], colors)
},
drawArc () {
const { ctx, arcRadius, arcRadian, centerPos, arcLineWidth, arcColor } = this
const offsetRadian = Math.PI / -2
ctx.lineWidth = arcLineWidth
ctx.strokeStyle = arcColor
arcRadius.forEach((radius, i) => {
ctx.beginPath()
ctx.arc(...centerPos, radius, offsetRadian, arcRadian[i])
ctx.stroke()
})
},
drawTitle () {
const { ctx, data: { series, fontSize }, arcRadius, centerPos: [ x, y ], arcLineWidth } = this
const textEndX = x - 10
ctx.font = `${fontSize || arcLineWidth}px Arial`
ctx.textAlign = 'right'
ctx.textBaseline = 'middle'
ctx.fillStyle = '#fff'
ctx.beginPath()
series.forEach(({ title }, i) => {
ctx.fillText(title, textEndX, y - arcRadius[i])
})
}
},
mounted () {
const { init } = this
init()
}
}
</script>
<style lang="less">
.concentric-arc-chart {
position: relative;
display: flex;
flex-direction: column;
color: #fff;
.canvas-container {
position: relative;
flex: 1;
}
canvas {
width: 100%;
height: 100%;
}
}
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

View File

@ -0,0 +1,20 @@
<template>
<div class="decoration-1">
<img src="./img/decoration.gif" />
</div>
</template>
<script>
export default {
name: 'Decoration1'
}
</script>
<style lang="less">
.decoration-1 {
img {
width: 100%;
}
}
</style>

View File

@ -0,0 +1,77 @@
<template>
<div class="decoration-2" :ref="ref">
<div :class="reverse ? 'reverse' : 'normal'" />
</div>
</template>
<script>
export default {
name: 'Decoration2',
data () {
return {
ref: `decoration-2-${(new Date()).getTime()}`,
width: 0,
height: 0
}
},
props: ['reverse'],
methods: {
init () {
const { $nextTick, $refs, ref } = this
$nextTick(e => {
this.width = $refs[ref].clientWidth
this.height = $refs[ref].clientHeight
})
}
},
mounted () {
const { init } = this
init()
}
}
</script>
<style lang="less" scoped>
.decoration-2 {
.reverse, .normal {
background-color: #3faacb;
}
.normal {
width: 0%;
height: 1px;
border-right: 1px solid #fff;
animation: normal-amt 6s ease-in-out infinite;
}
.reverse {
width: 1px;
height: 0%;
border-bottom: 1px solid #fff;
animation: reverse-amt 6s ease-in-out infinite;
}
@keyframes reverse-amt {
70% {
height: 100%;
}
100% {
height: 100%;
}
}
@keyframes normal-amt {
70% {
width: 100%;
}
100% {
width: 100%;
}
}
}
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

View File

@ -0,0 +1,20 @@
<template>
<div class="decoration-3">
<img src="./img/decoration.gif" />
</div>
</template>
<script>
export default {
name: 'Decoration3'
}
</script>
<style lang="less">
.decoration-3 {
img {
width: 100%;
}
}
</style>

View File

@ -0,0 +1,112 @@
<template>
<div :class="`decoration-4 ${reverse ? 'reverse' : 'normal'}`" :ref="ref">
<svg :class="`svg-container ${reverse ? 'ani-width' : 'ani-height'}`">
<template v-if="!reverse">
<polyline class="lighter-line" :points="`3, 5 3, ${height - 5}`" />
<polyline class="bolder-line" :points="`3, 5 3, ${height - 5}`" />
</template>
<template v-else>
<polyline class="lighter-line" :points="`5, 3 ${width - 5},3`" />
<polyline class="bolder-line" :points="`5, 3 ${width - 5},3`" />
<!-- <polyline class="lighter-line" :points="`5, 3 ${width - 5},3`" />
<polyline class="bolder-line" :points="`5, 3 ${width - 5},3`" /> -->
</template>
</svg>
</div>
</template>
<script>
export default {
name: 'Decoration4',
data () {
return {
ref: `decoration-4-${(new Date()).getTime()}`,
width: 0,
height: 0
}
},
props: ['reverse'],
methods: {
init () {
const { $nextTick, $refs, ref } = this
$nextTick(e => {
this.width = $refs[ref].clientWidth
this.height = $refs[ref].clientHeight
})
}
},
mounted () {
const { init } = this
init()
}
}
</script>
<style lang="less">
.decoration-4 {
position: relative;
&.normal {
width: 6px;
}
&.reverse {
height: 6px;
}
.svg-container {
position: absolute;
&.ani-height {
width: 100%;
height: 0%;
animation: ani-height 3s ease-in-out infinite;
}
&.ani-width {
width: 0%;
height: 100%;
animation: ani-width 3s ease-in-out infinite;
}
polyline {
fill: none;
stroke: fade(gray, 25);
}
}
.lighter-line {
stroke-width: 1px;
}
.bolder-line {
stroke-width: 3px;
stroke-dasharray: 20, 80;
stroke-dashoffset: -30;
}
@keyframes ani-height {
70% {
height: 100%;
}
100% {
height: 100%;
}
}
@keyframes ani-width {
70% {
width: 100%;
}
100% {
width: 100%;
}
}
}
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

@ -0,0 +1,22 @@
<template>
<div class="dv-decoration-5">
<img src="./img/decoration.gif" />
</div>
</template>
<script>
export default {
name: 'Decoration5'
}
</script>
<style lang="less">
.dv-decoration-5 {
img {
width: 100%;
margin-top: -21%;
margin-bottom: -25%;
}
}
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 242 KiB

View File

@ -0,0 +1,20 @@
<template>
<div class="decoration-6">
<img src="./img/decoration.gif" />
</div>
</template>
<script>
export default {
name: 'Decoration6'
}
</script>
<style lang="less">
.decoration-6 {
img {
width: 100%;
}
}
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 496 B

View File

@ -0,0 +1,26 @@
<template>
<div class="decoration-7">
<img src="./img/decoration.png" />
<slot></slot>
<img class="reverse" src="./img/decoration.png" />
</div>
</template>
<script>
export default {
name: 'Decoration7'
}
</script>
<style lang="less">
.decoration-7 {
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
.reverse {
transform: rotate(180deg);
}
}
</style>

View File

@ -0,0 +1,21 @@
<template>
<div class="for-slot">
<slot />
</div>
</template>
<script>
export default {
name: 'ForSlot'
}
</script>
<style lang="less">
.for-slot {
position: absolute;
width: 100%;
height: 100%;
top: 0px;
left: 0px;
}
</style>

View File

@ -0,0 +1,68 @@
<template>
<div id="dv-full-screen-container" ref="full-screen-container">
<slot></slot>
</div>
</template>
<script>
export default {
name: 'FullScreenContainer',
data () {
return {
scale: 0,
datavRoot: ''
}
},
methods: {
init () {
const { initConfig, setAppScale, bindReSizeEventHandler } = this
initConfig()
setAppScale()
bindReSizeEventHandler()
},
initConfig () {
const { width, height } = screen
this.allWidth = width
const datavRoot = this.datavRoot = this.$refs['full-screen-container']
datavRoot.style.width = `${width}px`
datavRoot.style.height = `${height}px`
},
setAppScale () {
const { allWidth, datavRoot } = this
const currentWidth = document.body.clientWidth
datavRoot.style.transform = `scale(${currentWidth / allWidth})`
},
bindReSizeEventHandler () {
const { debounce, setAppScale } = this
if (!debounce) return
window.addEventListener('resize', debounce(100, setAppScale))
}
},
mounted () {
const { init } = this
init()
}
}
</script>
<style lang="less">
#dv-full-screen-container {
position: fixed;
top: 0px;
left: 0px;
overflow: hidden;
transform-origin: left top;
z-index: 999;
}
</style>

73
components/index.js Normal file
View File

@ -0,0 +1,73 @@
import borderBox1 from './borderBox1/index'
import borderBox2 from './borderBox2/index'
import borderBox3 from './borderBox3/index'
import borderBox4 from './borderBox4/index'
import borderBox5 from './borderBox5/index'
import borderBox6 from './borderBox6/index'
import borderBox7 from './borderBox7/index'
import decoration1 from './decoration1/index'
import decoration2 from './decoration2/index'
import decoration3 from './decoration3/index'
import decoration4 from './decoration4/index'
import decoration5 from './decoration5/index'
import decoration6 from './decoration6/index'
import decoration7 from './decoration7/index'
import loading from './loading/index.vue'
import capsuleChart from './capsuleChart/index.vue'
import ringChart from './ringChart/index.vue'
import polylineChart from './polylineChart/index.vue'
import concentricArcChart from './concentricArcChart/index.vue'
import arcRingChart from './arcRingChart/index.vue'
import radarChart from './radarChart/index.vue'
import columnChart from './columnChart/index.vue'
import pointChart from './pointChart/index.vue'
import numberShow from './numberShow/index.vue'
import percentPond from './percentPond/index.vue'
import percentArc from './percentArc/index.vue'
import waterLevelPond from './waterLevelPond/index.vue'
import scrollBoard from './scrollBoard/index.vue'
import fullScreenContainer from './fullScreenContainer'
import labelLine from './labelLine'
import forSlot from './forSlot'
export default function (Vue) {
Vue.component('borderBox1', borderBox1)
Vue.component('borderBox2', borderBox2)
Vue.component('borderBox3', borderBox3)
Vue.component('borderBox4', borderBox4)
Vue.component('borderBox5', borderBox5)
Vue.component('borderBox6', borderBox6)
Vue.component('borderBox7', borderBox7)
Vue.component('decoration1', decoration1)
Vue.component('decoration2', decoration2)
Vue.component('decoration3', decoration3)
Vue.component('decoration4', decoration4)
Vue.component('decoration5', decoration5)
Vue.component('decoration6', decoration6)
Vue.component('decoration7', decoration7)
Vue.component('loading', loading)
Vue.component('capsuleChart', capsuleChart)
Vue.component('polylineChart', polylineChart)
Vue.component('ringChart', ringChart)
Vue.component('concentricArcChart', concentricArcChart)
Vue.component('arcRingChart', arcRingChart)
Vue.component('radarChart', radarChart)
Vue.component('columnChart', columnChart)
Vue.component('pointChart', pointChart)
Vue.component('numberShow', numberShow)
Vue.component('percentPond', percentPond)
Vue.component('percentArc', percentArc)
Vue.component('waterLevelPond', waterLevelPond)
Vue.component('scrollBoard', scrollBoard)
Vue.component('fullScreenContainer', fullScreenContainer)
Vue.component('labelLine', labelLine)
Vue.component('forSlot', forSlot)
}

View File

@ -0,0 +1,96 @@
<template>
<div class="label-line">
<div class="label-item"
v-for="(labelItem, i) in labelData"
:key="labelItem">
<div :class="type" :style="`background-color: ${labelColor[i % labelColorNum]};`"></div>
<div>{{ labelItem }}</div>
</div>
</div>
</template>
<script>
export default {
name: 'LabelLine',
props: ['label', 'colors'],
data () {
return {
labelData: [],
labelColor: [],
labelColorNum: 0,
type: 'rect'
}
},
watch: {
label () {
const { init } = this
init()
},
colors () {
const { init } = this
init()
}
},
methods: {
init () {
const { label, colors } = this
if (!label) return
const { labels, color, type } = label
if (!labels) return
this.labelData = labels
let trueColor = color || colors
typeof trueColor === 'string' && (trueColor = [trueColor])
this.labelColor = trueColor
this.labelColorNum = trueColor.length
this.type = type || 'rect'
}
},
mounted () {
const { init } = this
init()
}
}
</script>
<style lang="less">
.label-line {
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: center;
font-size: 10px;
color: #fff;
.label-item {
height: 20px;
display: flex;
flex-direction: row;
align-items: center;
margin: 0px 5px 5px 5px;
}
.rect {
width: 10px;
height: 10px;
margin-right: 5px;
}
.rectangle {
width: 30px;
height: 10px;
margin-right: 5px;
}
}
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

View File

@ -0,0 +1,39 @@
<template>
<div class="loading">
<img class="loading-img" src="./img/loading.png">
</div>
</template>
<script>
export default {
name: 'Loading'
}
</script>
<style lang="less">
.loading {
position: absolute;
top: 0px;
left: 0px;
width: 100%;
height: 100%;
.loading-img {
position: absolute;
top: 50%;
left: 50%;
width: 30px;
height: 30px;
margin-left: -15px;
margin-top: -15px;
transform: rotate(0deg);
animation: round 1s linear infinite;
}
@keyframes round {
to {
transform: rotate(360deg)
}
}
}
</style>

View File

@ -0,0 +1,33 @@
<template>
<div class="number-show">
<div class="number-itme" v-for="(n, i) in number.toString()" :key="`${n}-${i}`">
{{ n }}
</div>
</div>
</template>
<script>
export default {
name: 'NumberShow',
props: ['number']
}
</script>
<style lang="less">
.number-show {
display: inline-block;
.number-itme {
display: inline-block;
height: 70px;
padding: 0 20px;
line-height: 70px;
text-align: center;
background-color: rgba(4, 49, 128, 0.6);
margin-right: 20px;
color: rgb(8, 229, 255);
font-weight: bold;
font-size: 45px;
}
}
</style>

View File

@ -0,0 +1,222 @@
<template>
<div class="percent-arc">
<loading v-if="!percent || percent === 0" />
<div class="canvas-container">
<canvas :ref="ref" />
</div>
<div class="for-slot">
<slot></slot>
</div>
</div>
</template>
<script>
import canvasMixin from '../../mixins/canvasMixin.js'
export default {
name: 'PercentArc',
props: ['percent', 'ringColor', 'arcColor', 'ringLineWidth', 'arcLineWidth', 'radius', 'arcType'],
mixins: [canvasMixin],
data () {
return {
ref: `percent-arc-${(new Date()).getTime()}`,
defaultRadius: 0.9,
defaultRingLineWidth: 4,
defaultArcLineWidth: 10,
defaultArcType: 'butt',
defaultRingColor: '#00BAFF',
defaultArcColor: ['#00BAFF', '#3DE7C9'],
trueArcType: '',
trueRadius: 0,
ringRadius: 0,
arcRadius: 0,
trueRingColor: '',
trueArcColor: '',
trueRingLineWidth: 0,
trueArcLineWidth: 0
}
},
watch: {
percent () {
const { init } = this
init()
},
ringColor () {
const { init } = this
init()
},
arcColor () {
const { init } = this
init()
},
ringLineWidth () {
const { init } = this
init()
},
arcLineWidth () {
const { init } = this
init()
},
radius () {
const { init } = this
init()
},
arcType () {
const { init } = this
init()
}
},
methods: {
async init () {
const { initCanvas, draw } = this
await initCanvas()
draw()
},
draw () {
const { percent } = this
if (!percent && percent !== 0) return
const { clearCanvas } = this
clearCanvas()
const { calcLineWidth, calcRaidus, calcColors } = this
calcLineWidth()
calcRaidus()
calcColors()
const { calcArcType, drawRing, drawArc } = this
calcArcType()
drawRing()
drawArc()
},
calcLineWidth () {
const { defaultRingLineWidth, defaultArcLineWidth, ringLineWidth, arcLineWidth } = this
this.trueRingLineWidth = ringLineWidth || defaultRingLineWidth
this.trueArcLineWidth = arcLineWidth || defaultArcLineWidth
},
calcRaidus () {
const { radius, defaultRadius, canvasWH } = this
const trueRadius = this.trueRadius = radius || defaultRadius
const ringRadius = this.ringRadius = Math.min(...canvasWH) * trueRadius / 2
const { trueRingLineWidth, trueArcLineWidth } = this
const halfRingLineWidth = trueRingLineWidth / 2
const halfArcLineWidth = trueArcLineWidth / 2
this.arcRadius = ringRadius - halfRingLineWidth - halfArcLineWidth
},
calcColors () {
const { ringColor, defaultRingColor } = this
this.trueRingColor = ringColor || defaultRingColor
const { arcColor, defaultArcColor } = this
this.trueArcColor = arcColor || defaultArcColor
},
calcArcType () {
const { arcType, defaultArcType } = this
this.trueArcType = arcType || defaultArcType
},
drawRing () {
const { ringRadius, ctx, trueRingLineWidth, centerPos, trueRingColor } = this
ctx.lineWidth = trueRingLineWidth
ctx.strokeStyle = trueRingColor
ctx.beginPath()
ctx.arc(...centerPos, ringRadius, 0, Math.PI * 2)
ctx.stroke()
},
drawArc () {
const { ctx, centerPos, percent, trueArcType, canvasWH } = this
const { trueArcLineWidth, arcRadius, trueArcColor } = this
const { canvas: { getLinearGradientColor } } = this
const fullArc = Math.PI * 2
const offsetArc = Math.PI / 2
const arcBegin = offsetArc * -1
const arcEnd = fullArc * percent / 100 - offsetArc
ctx.lineCap = trueArcType
ctx.lineWidth = trueArcLineWidth
ctx.strokeStyle = getLinearGradientColor(ctx, [0, 0], [0, canvasWH[1]], trueArcColor)
ctx.beginPath()
ctx.arc(...centerPos, arcRadius, arcBegin, arcEnd)
ctx.stroke()
}
},
mounted () {
const { init } = this
init()
}
}
</script>
<style lang="less">
.percent-arc {
position: relative;
display: flex;
.canvas-container {
flex: 1;
canvas {
width: 100%;
height: 100%;
}
}
.for-slot {
position: absolute;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
}
</style>

View File

@ -0,0 +1,139 @@
<template>
<div class="percent-pond">
<div class="percent-text">
<span :style="`margin-left: ${percent * (width - 2) / 100 + 6}px`">{{ percent || 0 }}%</span>
</div>
<div class="percent-container">
<div class="p-decoration-box" />
<div class="p-svg-container" :ref="ref">
<svg :width="width" :height="height">
<defs>
<linearGradient id="linear">
<stop v-for="lc in linearGradient" :key="lc[0]"
:offset="lc[0]"
:stop-color="lc[1]" />
</linearGradient>
</defs>
<polyline :stroke-width="height - 1"
stroke="url(#linear)"
:points="`1, ${height * 0.5} ${percent * (width - 2) / 100}, ${height * 0.5 + 0.0001}`" />
</svg>
</div>
<div class="p-decoration-box" />
</div>
</div>
</template>
<script>
export default {
name: 'PercentPond',
props: ['percent', 'colors'],
data () {
return {
ref: `percent-pond-${(new Date()).getTime()}`,
width: 0,
height: 0,
defaultColor: ['#00BAFF', '#3DE7C9'],
linearGradient: []
}
},
watch: {
colors () {
const { calcLinearColor } = this
calcLinearColor()
}
},
methods: {
init () {
const { $nextTick, $refs, ref, calcLinearColor } = this
$nextTick(e => {
this.width = $refs[ref].clientWidth
this.height = $refs[ref].clientHeight
})
calcLinearColor()
},
calcLinearColor () {
const { colors, defaultColor } = this
let trueColor = colors || defaultColor
typeof trueColor === 'string' && (trueColor = [trueColor, trueColor])
const colorNum = trueColor.length
const colorOffsetGap = 100 / (colorNum - 1)
this.linearGradient = trueColor.map((c, i) => [colorOffsetGap * i, c])
}
},
mounted () {
const { init } = this
init()
}
}
</script>
<style lang="less">
.percent-pond {
display: flex;
flex-direction: column;
.percent-text {
height: 30px;
font-size: 15px;
flex-shrink: 0;
span {
position: relative;
box-shadow: 0 0 3px gray;
padding: 0px 5px;
display: inline-block;
transform: translateX(-50%);
&::after {
position: absolute;
display: block;
left: 50%;
transform: translateX(-50%);
content: '';
width: 0px;
height: 0px;
border-width: 8px;
border-style: solid;
border-color: fade(gray, 50) transparent transparent transparent;
}
}
}
.percent-container {
flex: 1;
display: flex;
flex-direction: row;
.p-decoration-box {
width: 3px;
flex-shrink: 0;
box-shadow: 0 0 3px gray;
}
.p-svg-container {
flex: 1;
margin: 0px 3px;
box-shadow: 0 0 3px gray;
polyline {
fill: none;
stroke-dasharray: 5, 1;
}
}
}
}
</style>

View File

@ -0,0 +1,175 @@
<template>
<div class="point-chart">
<loading v-if="!data" />
<div class="canvas-container">
<canvas :ref="ref" />
</div>
<label-line :label="labelLine" :colors="drawColors" />
<for-slot><slot></slot></for-slot>
</div>
</template>
<script>
import canvasMixin from '../../mixins/canvasMixin.js'
import colorsMixin from '../../mixins/colorsMixin.js'
import axisMixin from '../../mixins/axisMixin.js'
export default {
name: 'PointChart',
mixins: [canvasMixin, colorsMixin, axisMixin],
props: ['data', 'labelLine', 'colors'],
data () {
return {
ref: `point-chart-${(new Date()).getTime()}`,
// axis base config
boundaryGap: true,
horizon: false,
mulValueAdd: false,
defaultPointRadius: 2,
valuePointPos: []
}
},
watch: {
data () {
const { checkData, draw } = this
checkData() && draw()
}
},
methods: {
async init () {
const { initCanvas, initColors } = this
await initCanvas()
initColors()
const { checkData, draw } = this
checkData() && draw()
},
checkData () {
const { data } = this
this.status = false
if (!data || !data.series) return false
this.status = true
return true
},
draw () {
const { clearCanvas } = this
clearCanvas()
const { initAxis, drawAxis, calcValuePointPos } = this
initAxis()
drawAxis()
calcValuePointPos()
const { drawPoints } = this
drawPoints()
},
calcValuePointPos () {
const { data: { series }, valueAxisMaxMin, getAxisPointsPos } = this
const { axisOriginPos, axisWH, labelAxisTagPos } = this
this.valuePointPos = series.map(({ value }, i) =>
getAxisPointsPos(
valueAxisMaxMin,
value,
axisOriginPos,
axisWH,
labelAxisTagPos,
false
))
},
drawPoints () {
const { data: { series }, drawSeriesPoint, ctx } = this
ctx.setLineDash([10, 0])
series.forEach((seriesItem, i) => drawSeriesPoint(seriesItem, i))
},
drawSeriesPoint ({ color: cr, edgeColor, fillColor, radius, opacity }, i) {
const { drawColors, defaultPointRadius, valuePointPos, drawPoint } = this
const { color: { hexToRgb }, data: { radius: outerRadius } } = this
const drawColorsNum = drawColors.length
const baseColor = drawColors[i % drawColorsNum]
const trueEdgeColor = edgeColor || cr || baseColor
let trueFillColor = fillColor || cr || baseColor
opacity && (trueFillColor = hexToRgb(trueFillColor, opacity))
const trueRadius = radius || outerRadius || defaultPointRadius
valuePointPos[i].forEach(cp => {
if (!cp && cp !== 0) return
const isSeries = cp[0] instanceof Array
isSeries && cp.forEach(p => (p || p === 0) && drawPoint(p, trueEdgeColor, trueFillColor, trueRadius))
!isSeries && drawPoint(cp, trueEdgeColor, trueFillColor, trueRadius)
})
},
drawPoint (pos, edgeColor, fillColor, radius) {
const { ctx } = this
ctx.beginPath()
ctx.arc(...pos, radius, 0, Math.PI * 2)
ctx.closePath()
ctx.strokeStyle = edgeColor
ctx.fillStyle = fillColor
ctx.fill()
ctx.stroke()
}
},
mounted () {
const { init } = this
init()
}
}
</script>
<style lang="less">
.point-chart {
position: relative;
display: flex;
flex-direction: column;
.canvas-container {
flex: 1;
canvas {
width: 100%;
height: 100%;
}
}
}
</style>

View File

@ -0,0 +1,281 @@
<template>
<div class="polyline-chart">
<loading v-if="!status" />
<div class="canvas-container">
<canvas :ref="ref" />
</div>
<label-line :label="labelLine" :colors="drawColors" />
<for-slot><slot></slot></for-slot>
</div>
</template>
<script>
import colorsMixin from '../../mixins/colorsMixin.js'
import canvasMixin from '../../mixins/canvasMixin.js'
import axisMixin from '../../mixins/axisMixin.js'
export default {
name: 'PolylineChart',
mixins: [colorsMixin, canvasMixin, axisMixin],
props: ['data', 'labelLine', 'colors'],
data () {
return {
ref: `polyline-chart-${(new Date()).getTime()}`,
status: false,
// axis base config
boundaryGap: false,
mulValueAdd: false,
horizon: false,
defaultLineDash: [2, 2],
defaultPointRadius: 2,
defaultValueFontSize: 10,
defaultValueColor: '#999',
valuePointPos: []
}
},
watch: {
data (d) {
const { checkData, draw } = this
checkData() && draw()
}
},
methods: {
async init () {
const { initCanvas, initColors } = this
await initCanvas()
initColors()
const { checkData, draw } = this
checkData() && draw()
},
checkData () {
const { data } = this
this.status = false
if (!data || !data.series) return false
this.status = true
return true
},
draw () {
const { clearCanvas } = this
clearCanvas()
const { initAxisConfig, initAxis, drawAxis } = this
initAxisConfig()
initAxis()
drawAxis()
const { calcValuePointPos, drawLines, drawFills } = this
calcValuePointPos()
drawLines()
drawFills()
const { drawPoints, drawValues } = this
drawPoints()
drawValues()
},
initAxisConfig () {
const { data: { boundaryGap } } = this
this.boundaryGap = boundaryGap
},
calcValuePointPos () {
const { valueAxisMaxMin, axisOriginPos, axisWH, labelAxisTagPos, horizon } = this
const { data: { series }, getAxisPointsPos } = this
this.valuePointPos = series.map(({ value, againstAxis }) =>
getAxisPointsPos(
valueAxisMaxMin,
value,
axisOriginPos,
axisWH,
labelAxisTagPos,
horizon
))
},
drawLines () {
const { data: { series }, valuePointPos, drawLine } = this
series.forEach((line, i) => drawLine(line, valuePointPos[i], i))
},
drawLine ({ type, lineType, lineDash, lineColor }, points, i) {
const { ctx, drawColors, defaultLineDash } = this
const { canvas: { drawPolyline, drawSmoothline } } = this
const { color: { hexToRgb } } = this
let drawLineFun = drawPolyline
type === 'smoothline' && (drawLineFun = drawSmoothline)
let color = hexToRgb(drawColors[i], 0.8)
lineColor && (color = lineColor)
let tureLineType = lineType || 'line'
const tureLineDash = tureLineType === 'dashed' ? (lineDash || defaultLineDash) : [10, 0]
drawLineFun(ctx, points, 1, color, false, tureLineDash, true, true)
},
drawFills () {
const { data: { series }, valuePointPos, drawFill } = this
series.forEach((line, i) => drawFill(line, valuePointPos[i]))
},
drawFill ({ fillColor, type, value }, points) {
if (!fillColor) return
const { canvas: { drawPolylinePath, drawSmoothlinePath } } = this
const { ctx, getGradientColor, filterNull, axisOriginPos: [, y] } = this
let drawLineFun = drawPolylinePath
type === 'smoothline' && (drawLineFun = drawSmoothlinePath)
const maxValue = Math.max(...filterNull(value))
const maxValueIndex = value.findIndex(v => v === maxValue)
const color = getGradientColor(points[maxValueIndex], fillColor)
const lastPoint = points[points.length - 1]
ctx.fillStyle = color
drawLineFun(ctx, points, false, true, true)
ctx.lineTo(lastPoint[0], y)
ctx.lineTo(points[0][0], y)
ctx.closePath()
ctx.fill()
},
getGradientColor (value, colors) {
const { data: { localGradient }, axisAnglePos, horizon } = this
const { ctx, canvas: { getLinearGradientColor } } = this
if (localGradient) {
return getLinearGradientColor(ctx,
...(horizon
? [value, [axisAnglePos.leftTop[0], value[1]]]
: [value, [value[0], axisAnglePos.leftBottom[1]]]),
colors)
} else {
return getLinearGradientColor(ctx,
...(horizon
? [axisAnglePos.leftTop, axisAnglePos.rightTop]
: [axisAnglePos.leftTop, axisAnglePos.leftBottom]),
colors)
}
},
drawPoints () {
const { data: { series }, valuePointPos, drawPoint } = this
series.forEach((line, i) => drawPoint(line, valuePointPos[i], i))
},
drawPoint ({ pointColor }, points, i) {
const { ctx, drawColors, defaultPointRadius } = this
const { canvas: { drawPoints: drawPFun } } = this
const color = pointColor || drawColors[i]
drawPFun(ctx, points, defaultPointRadius, color)
},
drawValues () {
const { ctx, data: { series, showValueText, valueTextOffset, valueTextFontSize } } = this
if (!showValueText) return
const { defaultValueFontSize, drawValue } = this
const offset = valueTextOffset || [0, -5]
ctx.font = `${valueTextFontSize || defaultValueFontSize}px Arial`
ctx.textAlign = 'center'
ctx.textBaseline = 'bottom'
series.forEach((line, i) => drawValue(line, i, offset))
},
drawValue ({ value, valueTextColor, lineColor, pointColor, fillColor }, i, offset) {
const { ctx, getOffsetPoints, valuePointPos, drawColors, defaultValueColor } = this
const { data: { valueTextColor: outerValueTC } } = this
const drawColorsNum = drawColors.length
let currentColor = valueTextColor
currentColor === 'inherit' && (currentColor = pointColor || lineColor || fillColor || drawColors[i % drawColorsNum])
currentColor instanceof Array && (currentColor = currentColor[0])
ctx.fillStyle = currentColor || outerValueTC || defaultValueColor
const pointsPos = valuePointPos[i]
getOffsetPoints(pointsPos, offset).forEach((pos, i) => {
if (!value[i] && value[i] !== 0) return
ctx.fillText(value[i], ...pos)
})
},
getOffsetPoint ([x, y], [ox, oy]) {
return [x + ox, y + oy]
},
getOffsetPoints (points, offset) {
const { getOffsetPoint } = this
return points.map(point => point ? getOffsetPoint(point, offset) : false)
}
},
mounted () {
const { init } = this
init()
}
}
</script>
<style lang="less">
.polyline-chart {
position: relative;
display: flex;
flex-direction: column;
.canvas-container {
flex: 1;
canvas {
width: 100%;
height: 100%;
}
}
}
</style>

View File

@ -0,0 +1,558 @@
<template>
<div class="radar-chart">
<loading v-if="!data" />
<div class="canvas-container">
<canvas :ref="ref" />
</div>
<label-line :label="labelLine" :colors="drawColors" />
</div>
</template>
<script>
import colorsMixin from '../../mixins/colorsMixin.js'
import canvasMixin from '../../mixins/canvasMixin.js'
export default {
name: 'RadarChart',
mixins: [canvasMixin, colorsMixin],
props: ['data', 'labelLine', 'colors'],
data () {
return {
ref: `radar-chart-${(new Date()).getTime()}`,
defaultRadius: 0.8,
defaultRingNum: 4,
defaultRingType: 'circle',
defaultRingLineType: 'dashed',
defaultRingLineColor: '#666',
defaultRingFillType: 'none',
defaultRayLineType: 'line',
defaultRayLineColor: '#666',
defaultRayLineOffset: Math.PI * -0.5,
defaultLabelColor: '#fff',
defaultLabelFS: 10,
defaultValueFontSize: 10,
defaultValueColor: '#999',
drawColors: '',
radius: '',
ringType: '',
rayLineRadianData: [],
ringRadiusData: [],
ringPolylineData: [],
ringLineDash: [],
ringlineMultipleColor: false,
ringLineColor: '',
ringFillType: '',
ringFillMultipleColor: false,
ringFillColor: '',
rayLineColor: '',
rayLineDash: '',
rayLineMultipleColor: false,
labelPosData: [],
labelColor: '',
labelFontSize: '',
labelMultipleColor: false,
valuePointData: []
}
},
watch: {
data (d) {
const { checkData, reDraw } = this
checkData && reDraw(d)
},
color (d) {
const { checkData, reDraw } = this
checkData && reDraw(d)
}
},
methods: {
async init () {
const { initCanvas, checkData, draw } = this
await initCanvas()
checkData() && draw()
},
checkData () {
const { data } = this
this.status = false
if (!data || !data.series) return false
this.status = true
return true
},
draw () {
const { ctx, canvasWH } = this
ctx.clearRect(0, 0, ...canvasWH)
const { initColors, calcRadarRadius, calcRingType } = this
initColors()
calcRadarRadius()
calcRingType()
const { calcRayLineRadianData, calcRingRadiusData, calcRingPolylineData } = this
calcRayLineRadianData()
calcRingRadiusData()
calcRingPolylineData()
const { calcRingDrawConfig, calcRingFillConfig, fillRing } = this
calcRingDrawConfig()
calcRingFillConfig()
fillRing()
const { drawCircleRing, drawPolylineRing, calcRayLineConfig } = this
drawCircleRing()
drawPolylineRing()
calcRayLineConfig()
const { drawRayLine, calcLabelPosData, calcLabelConfig } = this
drawRayLine()
calcLabelPosData()
calcLabelConfig()
const { drawLable, caclValuePointData, fillRadar } = this
drawLable()
caclValuePointData()
fillRadar()
const { fillValueText } = this
fillValueText()
},
calcRadarRadius () {
const { canvasWH, data: { radius }, defaultRadius } = this
this.radius = Math.min(...canvasWH) * (radius || defaultRadius) * 0.5
},
calcRingType () {
const { data: { ringType }, defaultRingType } = this
this.ringType = ringType || defaultRingType
},
calcRayLineRadianData () {
const { data: { label, rayLineOffset }, defaultRayLineOffset } = this
const { tags } = label
const fullRadian = Math.PI * 2
const radianGap = fullRadian / tags.length
const radianOffset = rayLineOffset || defaultRayLineOffset
this.rayLineRadianData = tags.map((t, i) => radianGap * i + radianOffset)
},
calcRingRadiusData () {
const { data: { ringNum }, defaultRingNum, radius } = this
const num = ringNum || defaultRingNum
const radiusGap = radius / num
this.ringRadiusData = new Array(num).fill(0).map((t, i) =>
radiusGap * (i + 1))
},
calcRingPolylineData () {
const { ringRadiusData, rayLineRadianData, centerPos } = this
const { canvas: { getCircleRadianPoint } } = this
this.ringPolylineData = ringRadiusData.map((r, i) =>
rayLineRadianData.map(radian =>
getCircleRadianPoint(...centerPos, r, radian)))
},
calcRingDrawConfig () {
const { defaultRingLineType, defaultRingLineColor } = this
const { data: { ringLineType, ringLineColor }, drawColors } = this
this.ringLineDash = (ringLineType || defaultRingLineType) === 'dashed' ? [5, 5] : [10, 0]
const trueRingLineColor = ringLineColor === 'colors' ? drawColors : ringLineColor
this.ringlineMultipleColor = typeof trueRingLineColor === 'object'
this.ringLineColor = trueRingLineColor || defaultRingLineColor
},
calcRingFillConfig () {
const { data: { ringFillType, ringFillColor }, defaultRingFillType, drawColors } = this
this.ringFillType = ringFillType || defaultRingFillType
const trueRingFillColor = this.ringFillColor = (!ringFillColor || ringFillColor === 'colors') ? drawColors : ringFillColor
this.ringFillMultipleColor = typeof trueRingFillColor === 'object'
},
fillRing () {
const { ringFillType, fillCoverRing, fillMulCoverRing, fillRingRing } = this
switch (ringFillType) {
case 'cover': fillCoverRing()
break
case 'mulCover': fillMulCoverRing()
break
case 'ring': fillRingRing()
break
}
},
fillCoverRing () {
const { ctx, centerPos, ringFillColor, ringType, radius, ringPolylineData } = this
const { canvas: { getRadialGradientColor, drawPolylinePath } } = this
const color = getRadialGradientColor(ctx, centerPos, 0, radius, ringFillColor)
ctx.beginPath()
ringType === 'circle' && ctx.arc(...centerPos, radius, 0, Math.PI * 2)
ringType === 'polyline' && drawPolylinePath(ctx, ringPolylineData[ringPolylineData.length - 1])
ctx.closePath()
ctx.fillStyle = color
ctx.fill()
},
fillMulCoverRing () {
const { ctx, ringType, ringFillColor, centerPos } = this
const { ringFillMultipleColor, ringPolylineData, ringRadiusData, deepClone } = this
const { canvas: { drawPolylinePath } } = this
!ringFillMultipleColor && (ctx.fillStyle = ringFillColor)
const colorNum = ringFillColor.length
const LastRingIndex = ringRadiusData.length - 1
ringType === 'circle' &&
deepClone(ringRadiusData).reverse().forEach((radius, i) => {
ctx.beginPath()
ctx.arc(...centerPos, radius, 0, Math.PI * 2)
ringFillMultipleColor && (ctx.fillStyle = ringFillColor[(LastRingIndex - i) % colorNum])
ctx.fill()
})
ringType === 'polyline' &&
deepClone(ringPolylineData).reverse().forEach((line, i) => {
drawPolylinePath(ctx, line, true, true)
ringFillMultipleColor && (ctx.fillStyle = ringFillColor[(LastRingIndex - i) % colorNum])
ctx.fill()
})
},
fillRingRing () {
const { ctx, ringType, ringRadiusData, rayLineRadianData, getPointToLineDistance } = this
const { ringFillMultipleColor, centerPos, ringFillColor, ringPolylineData } = this
const { canvas: { drawPolylinePath, getCircleRadianPoint } } = this
let lineWidth = ctx.lineWidth = ringRadiusData[0]
const halfLineWidth = lineWidth / 2
const colorNum = ringFillColor.length
!ringFillMultipleColor && (ctx.strokeStyle = ringFillColor)
ringType === 'circle' &&
ringRadiusData.forEach((r, i) => {
ctx.beginPath()
ctx.arc(...centerPos, r - halfLineWidth, 0, Math.PI * 2)
ringFillMultipleColor && (ctx.strokeStyle = ringFillColor[i % colorNum])
ctx.stroke()
})
ctx.lineCap = 'round'
ctx.lineWidth = getPointToLineDistance(centerPos, ringPolylineData[0][0], ringPolylineData[0][1])
ringType === 'polyline' &&
ringRadiusData.map(r => r - halfLineWidth).map(r =>
rayLineRadianData.map(radian =>
getCircleRadianPoint(...centerPos, r, radian))).forEach((line, i) => {
drawPolylinePath(ctx, line, true, true)
ringFillMultipleColor && (ctx.strokeStyle = ringFillColor[i % colorNum])
ctx.stroke()
})
},
drawCircleRing () {
const { data: { ringType }, defaultRingType } = this
if ((ringType && ringType !== 'circle') || (!ringType && defaultRingType !== 'circle')) return
const { ctx, ringRadiusData, centerPos, ringLineDash, ringlineMultipleColor, ringLineColor } = this
ctx.setLineDash(ringLineDash)
ctx.lineWidth = 1
!ringlineMultipleColor && (ctx.strokeStyle = ringLineColor)
const colorNum = ringLineColor.length
ringRadiusData.forEach((r, i) => {
ctx.beginPath()
ctx.arc(...centerPos, r, 0, Math.PI * 2)
ringlineMultipleColor && (ctx.strokeStyle = ringLineColor[i % colorNum])
ctx.stroke()
})
},
drawPolylineRing () {
const { data: { ringType }, defaultRingType } = this
if ((ringType && ringType !== 'polyline') || (!ringType && defaultRingType !== 'polyline')) return
const { ctx, ringPolylineData, ringLineDash, ringlineMultipleColor, ringLineColor } = this
const { canvas: { drawPolyline } } = this
const colorNum = ringLineColor.length
ringPolylineData.forEach((line, i) =>
drawPolyline(ctx, line, 1,
(ringlineMultipleColor ? ringLineColor[i % colorNum] : ringLineColor),
true, ringLineDash, true))
},
calcRayLineConfig () {
const { data: { rayLineType, rayLineColor }, defaultRayLineType, defaultRayLineColor, drawColors } = this
this.rayLineDash = (rayLineType || defaultRayLineType) === 'line' ? [10, 0] : [5, 5]
const trueRayLineColor = rayLineColor === 'colors' ? drawColors : (rayLineColor || defaultRayLineColor)
this.rayLineColor = trueRayLineColor
this.rayLineMultipleColor = typeof trueRayLineColor === 'object'
},
drawRayLine () {
const { ctx, rayLineColor, rayLineDash, ringPolylineData, centerPos, rayLineMultipleColor } = this
const lastRingLineIndex = ringPolylineData.length - 1
ctx.setLineDash(rayLineDash)
!rayLineMultipleColor && (ctx.strokeStyle = rayLineColor)
ctx.lineWidth = 1
const colorNum = rayLineColor.length
ringPolylineData[lastRingLineIndex].forEach((point, i) => {
ctx.beginPath()
ctx.moveTo(...centerPos)
ctx.lineTo(...point)
rayLineMultipleColor && (ctx.strokeStyle = rayLineColor[i % colorNum])
ctx.stroke()
})
},
calcLabelPosData () {
const { rayLineRadianData, radius, centerPos } = this
const { canvas: { getCircleRadianPoint } } = this
const labelRadius = radius + 10
this.labelPosData = rayLineRadianData.map(radian =>
getCircleRadianPoint(...centerPos, labelRadius, radian))
},
calcLabelConfig () {
const { defaultLabelColor, defaultLabelFS, drawColors } = this
const { data: { label: { color, fontSize } } } = this
const trueLabelColor = color === 'colors' ? drawColors : (color || defaultLabelColor)
this.labelMultipleColor = typeof trueLabelColor === 'object'
this.labelFontSize = fontSize || defaultLabelFS
this.labelColor = trueLabelColor
},
drawLable () {
const { ctx, centerPos: [x], labelPosData, labelColor, labelFontSize, labelMultipleColor } = this
const { data: { label: { tags } } } = this
ctx.font = `${labelFontSize}px Arial`
!labelMultipleColor && (ctx.fillStyle = labelColor)
ctx.textBaseline = 'middle'
const colorNum = labelColor.length
labelPosData.forEach((pos, i) => {
ctx.textAlign = 'start'
pos[0] < x && (ctx.textAlign = 'end')
labelMultipleColor && (ctx.fillStyle = labelColor[i % colorNum])
ctx.fillText(tags[i], ...pos)
})
},
caclValuePointData () {
const { data: { series, max }, centerPos, radius, rayLineRadianData } = this
const { canvas: { getCircleRadianPoint } } = this
const maxValue = max || Math.max(...series.map(({ value }) => Math.max(...value)))
const valueRadius = series.map(({ value }) =>
value.map(v =>
Number.isFinite(v)
? v / maxValue * radius : false))
this.valuePointData = valueRadius.map(td =>
td.map((r, i) =>
(r || r === 0) ? getCircleRadianPoint(...centerPos, r, rayLineRadianData[i]) : false))
},
fillRadar () {
const { ctx, data: { series }, valuePointData, drawColors, filterNull } = this
const { canvas: { drawPolylinePath } } = this
const { color: { hexToRgb } } = this
const colorNum = drawColors.length
valuePointData.forEach((line, i) => {
const currentColor = drawColors[i % colorNum]
const lineColor = series[i].lineColor
const fillColor = series[i].fillColor
series[i].dashed ? ctx.setLineDash([5, 5]) : ctx.setLineDash([10, 0])
drawPolylinePath(ctx, filterNull(line), 1, true, true)
ctx.strokeStyle = lineColor || currentColor
ctx.stroke()
ctx.fillStyle = fillColor || hexToRgb(currentColor, 0.5)
ctx.fill()
})
},
fillValueText () {
const { data: { series, showValueText, valueTextFontSize } } = this
if (!showValueText) return
const { ctx, defaultValueFontSize } = this
ctx.font = `${valueTextFontSize || defaultValueFontSize}px Arial`
const { fillSeriesText } = this
series.forEach((item, i) => fillSeriesText(item, i))
},
fillSeriesText ({ valueTextColor, lineColor, fillColor, value }, i) {
const { ctx, drawColors, valuePointData, drawTexts } = this
const { data: { valueTextOffset, valueTextColor: outerValueTC }, defaultValueColor } = this
const trueOffset = valueTextOffset || [5, -5]
const drawColorsNum = drawColors.length
let currentColor = valueTextColor
currentColor === 'inherit' && (currentColor = lineColor || fillColor || drawColors[i % drawColorsNum])
currentColor instanceof Array && (currentColor = currentColor[0])
ctx.fillStyle = currentColor || outerValueTC || defaultValueColor
drawTexts(ctx, value, valuePointData[i], trueOffset)
},
drawTexts (ctx, values, points, [x, y] = [0, 0]) {
values.forEach((v, i) => {
if (!v && v !== 0) return
ctx.fillText(v, points[i][0] + x, points[i][1] + y)
})
},
reDraw (d) {
const { draw } = this
d && draw()
}
},
mounted () {
const { init } = this
init()
}
}
</script>
<style lang="less">
.radar-chart {
position: relative;
display: flex;
flex-direction: column;
.canvas-container {
flex: 1;
}
canvas {
width: 100%;
height: 100%;
}
}
</style>

View File

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

View File

@ -0,0 +1,381 @@
<template>
<div class="scroll-board" :ref="ref">
<loading v-if="!status" />
<template v-else>
<div class="title-container"
v-if="titleData"
:style="`background-color:${titleTrueBG};`">
<div :class="`title-item ${textTrueAlign[ti]}`"
v-for="(title, ti) in titleData" :key="title + ti"
:style="`width: ${columnTrueWidth[ti]};`">
{{ title }}
</div>
</div>
<div class="row-container">
<div :class="`row-item ${row.fade && 'fade'}`"
:style="`height: ${rowHeight}%;background-color:${row.index % 2 === 0 ? evenTrueBG : oddTrueBG};`"
v-for="(row, ri) in scrollData"
:key="row.data + ri">
<div :class="`row-item-info ${textTrueAlign[ii]}`"
v-for="(info, ii) in row.data"
:key="info + Math.random()">
<div @click="emitClickEvent(row, ii)" :class="`rii-width ${ii === 0 && index && 'index-container'}`" :style="`width: ${columnTrueWidth[ii]};`">
<template v-if="ii === 0 && index">
<div class="index" :style="`background-color:${titleTrueBG};`">{{ info }}</div>
</template>
<span v-else v-html="info" />
</div>
</div>
</div>
</div>
</template>
</div>
</template>
<script>
export default {
name: 'scroll-board',
props: ['data', 'index', 'rowNum', 'titleBG', 'waitTime', 'oddBG', 'evenBG', 'columnWidth', 'textAlign', 'carousel'],
data () {
return {
ref: `scroll-board-${(new Date()).getTime()}`,
container: '',
containerWH: [],
status: false,
reAnimationTimer: '',
doFadeTimer: '',
defaultRowNum: 5,
defaultTitleBG: '#00BAFF',
defaultOddBG: '#003B51',
defaultEvenBG: '#0A2732',
defaultWaitTime: 2000,
rowTrueNum: '',
rowHeight: '',
titleTrueBG: '',
oddTrueBG: '',
evenTrueBG: '',
columnTrueWidth: [],
textTrueAlign: [],
currentIndex: 0,
allRowNum: 0,
allColumnNum: 0,
titleData: '',
allRowData: [],
scrollData: []
}
},
watch: {
data () {
const { checkData, init } = this
checkData() && init()
}
},
methods: {
init () {
const { checkData, initDom, stopAnimation, dealData, calcConfig, getCurrentScrollData } = this
initDom()
if (!checkData()) return
stopAnimation()
dealData()
calcConfig()
getCurrentScrollData(true)
},
initDom () {
const { $refs, ref } = this
const container = this.container = $refs[ref]
this.containerWH[0] = container.clientWidth
this.containerWH[1] = container.clientHeight
},
checkData () {
const { data } = this
this.status = false
if (!data || !data.series) return false
this.status = true
return true
},
dealData () {
const { data: { series, title }, index, deepClone } = this
if (title) (this.titleData = index ? ['', ...title] : [...title])
this.allRowData = deepClone(series).map((row, i) =>
({ index: i + 1, data: index ? [i + 1, ...row] : row }))
},
calcConfig () {
const { calcAllRowColumnNum, calcRowNum, calcRowHeight } = this
calcAllRowColumnNum()
calcRowNum()
calcRowHeight()
const { calcTitleBG, oddAndEvenRowBG, calcColumnWidth } = this
calcTitleBG()
oddAndEvenRowBG()
calcColumnWidth()
const { calcTextAlign } = this
calcTextAlign()
},
calcAllRowColumnNum () {
const { data: { series }, index } = this
this.allRowNum = series.length
this.allColumnNum = series[0].length + (index ? 1 : 0)
},
calcRowNum () {
const { rowNum, defaultRowNum } = this
this.rowTrueNum = parseInt(rowNum || defaultRowNum)
},
calcRowHeight () {
const { rowTrueNum } = this
this.rowHeight = 100 / rowTrueNum
},
calcTitleBG () {
const { titleBG, defaultTitleBG } = this
this.titleTrueBG = titleBG || defaultTitleBG
},
oddAndEvenRowBG () {
const { oddBG, evenBG, defaultOddBG, defaultEvenBG } = this
this.oddTrueBG = oddBG || defaultOddBG
this.evenTrueBG = evenBG || defaultEvenBG
},
calcColumnWidth () {
const { columnWidth, allColumnNum, filterNull, multipleSum, containerWH: [allWidth] } = this
const userSetColumnWidth = columnWidth || []
const setColumnData = filterNull(userSetColumnWidth)
const useWidth = multipleSum(...setColumnData.map(w => parseInt(w)))
const avgNum = allColumnNum - setColumnData.length
const avgWidth = (allWidth - useWidth) / avgNum + 'px'
this.columnTrueWidth = new Array(allColumnNum).fill(0).map((t, i) =>
userSetColumnWidth[i] ? `${userSetColumnWidth[i]}px` : avgWidth)
},
calcTextAlign () {
const { textAlign, allColumnNum, index } = this
const userSetTextAlign = textAlign || []
const textTrueAlign = new Array(allColumnNum).fill('left').map((d, i) =>
userSetTextAlign[i] || d)
index && textTrueAlign.unshift('')
this.textTrueAlign = textTrueAlign
},
getCurrentScrollData (init = false) {
init && (this.currentIndex = 0)
const { currentIndex, rowTrueNum, allRowData, deepClone, carousel } = this
let dealAfterRowData = deepClone(allRowData).map(row => ({ ...row, fade: false }))
if (init && allRowData.length < rowTrueNum) {
this.scrollData = dealAfterRowData
return
}
const needNum = carousel === 'page' ? rowTrueNum * 2 : rowTrueNum + 1
const tempScrollData = dealAfterRowData.slice(currentIndex, currentIndex + needNum)
let stillNum = needNum - tempScrollData.length
while (stillNum) {
tempScrollData.push(...deepClone(dealAfterRowData).slice(0, stillNum))
stillNum = needNum - tempScrollData.length
}
this.scrollData = tempScrollData
const { doFade, waitTime, defaultWaitTime } = this
this.doFadeTimer = setTimeout(doFade, waitTime || defaultWaitTime)
},
doFade () {
const { rowTrueNum, carousel, scrollData, allRowNum, currentIndex } = this
if (carousel === 'page') {
scrollData.forEach((item, i) => i < rowTrueNum && (item.fade = true))
const tempIndex = currentIndex + rowTrueNum
const minus = tempIndex - allRowNum
this.currentIndex = minus >= 0 ? minus : tempIndex
} else {
scrollData[0].fade = true
this.currentIndex = currentIndex + 1 === allRowNum ? 0 : currentIndex + 1
}
const { getCurrentScrollData } = this
this.reAnimationTimer = setTimeout(getCurrentScrollData, 1000)
},
emitClickEvent ({ data, index }, columnIndex) {
this.$emit('click', { data, rowIndex: index, columnIndex: columnIndex + 1 })
},
stopAnimation () {
const { reAnimationTimer, doFadeTimer } = this
reAnimationTimer && clearTimeout(reAnimationTimer)
doFadeTimer && clearTimeout(doFadeTimer)
}
},
mounted () {
const { init } = this
init()
},
destroyed () {
const { stopAnimation } = this
stopAnimation()
}
}
</script>
<style lang="less">
.scroll-board {
position: relative;
width: 100%;
height: 100%;
overflow: hidden;
display: flex;
flex-direction: column;
color: #fff;
.title-container {
height: 35px;
font-size: 14px;
flex-shrink: 0;
display: flex;
flex-direction: row;
}
.title-item {
display: flex;
align-items: center;
&.left {
justify-content: flex-start;
}
&.right {
justify-content: flex-end;
}
&.center {
justify-content: center;
}
}
.row-container {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
}
.row-item {
display: flex;
flex-direction: row;
flex-shrink: 0;
transition: all 0.5s;
overflow: hidden;
&.fade {
height: 0% !important;
color: transparent;
visibility: hidden;
}
}
.row-item-info {
position: relative;
display: flex;
vertical-align: middle;
align-items: center;
vertical-align:middle;
font-size: 13px;
&.left {
text-align: left;
}
&.right {
text-align: right;
}
&.center {
text-align: center;
}
}
.rii-width {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.index-container {
display: flex;
justify-content: center;
}
.index {
font-size: 12px;
width: 16px;
height: 16px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
}
</style>

View File

@ -0,0 +1,326 @@
<template>
<div class="water-level-pond">
<loading v-if="!status" />
<svg class="svg-container">
<defs>
<linearGradient :id="id" x1="0%" y1="100%" x2="0%" y2="0%">
<stop v-for="lc in linearGradient" :key="lc[0]"
:offset="lc[0]"
:stop-color="lc[1]" />
</linearGradient>
</defs>
<text :stroke="`url(#${id})`"
:fill="`url(#${id})`"
:x="centerPos[0] + 8"
:y="centerPos[1] + 8">
{{ (level && Math.max(...level)) || 0 }}%
</text>
<ellipse v-if="!type || type === 'circle'"
:cx="centerPos[0] + 8"
:cy="centerPos[1] + 8"
:rx="canvasWH[0] / 2 + 5"
:ry="canvasWH[1] / 2 + 5"
:stroke="`url(#${id})`" />
<rect v-else
x="2" y="2"
:rx="type === 'roundRect' && 10" :ry="type === 'roundRect' && 10"
:width="canvasWH[0] + 12"
:height="canvasWH[1] + 12"
:stroke="`url(#${id})`" />
</svg>
<canvas :ref="ref" :style="`border-radius: ${radius};`" />
</div>
</template>
<script>
import canvasMixin from '../../mixins/canvasMixin.js'
export default {
name: 'WaterLevelPond',
mixins: [canvasMixin],
data () {
return {
ref: `water-level-pond-${(new Date()).getTime()}`,
status: false,
id: `water-level-pond-${(new Date()).getTime()}`,
defaultColor: ['#00BAFF', '#3DE7C9'],
defaultWaveNum: 3,
defaultWaveHeight: 0.2,
defaultWaveOffset: -0.5,
waveAdded: 0.7,
drawColor: '',
linearGradient: [],
waveTrueNum: '',
waveTrueHeight: '',
waveTrueWidth: '',
wavePoints: [],
bottomPoints: [],
overXPos: 0,
currentPoints: [],
animationHandler: ''
}
},
props: ['level', 'type', 'colors', 'waveNum', 'waveHeight', 'borderColor', 'noGradient'],
watch: {
level () {
const { checkData, draw } = this
checkData() && draw()
}
},
computed: {
radius () {
const { type } = this
if (type === 'circle') return '50%'
if (type === 'rect') return '0'
if (type === 'roundRect') return '10px'
return '50%'
}
},
methods: {
async init () {
const { initCanvas, checkData, draw } = this
await initCanvas()
checkData() && draw()
},
checkData () {
const { level } = this
this.status = false
if (!level || !level.length) return false
this.status = true
return true
},
draw () {
const { stopAnimation, clearCanvas } = this
stopAnimation()
clearCanvas()
const { initColor, calcBorderLinearColor, calcWaveData } = this
initColor()
calcBorderLinearColor()
calcWaveData()
const { calcBottomPoints, calcOverXPos, drawWaveAnimation } = this
calcBottomPoints()
calcOverXPos()
drawWaveAnimation()
},
initColor () {
const { colors, defaultColor } = this
this.drawColor = colors || defaultColor
},
calcBorderLinearColor () {
const { colors, defaultColor, borderColor } = this
let trueColor = borderColor || colors || defaultColor
typeof trueColor === 'string' && (trueColor = [trueColor, trueColor])
const colorNum = trueColor.length
const colorOffsetGap = 100 / (colorNum - 1)
this.linearGradient = trueColor.map((c, i) => [colorOffsetGap * i, c])
},
calcWaveData () {
const { waveNum, waveHeight, defaultWaveNum, defaultWaveHeight, canvasWH } = this
const waveTrueNum = this.waveTrueNum = waveNum || defaultWaveNum
const waveTrueHeight = this.waveTrueHeight = (waveHeight || defaultWaveHeight) * canvasWH[1]
const waveWidth = this.waveTrueWidth = canvasWH[0] / waveTrueNum
const { waveOffset, defaultWaveOffset, addWavePoint } = this
const waveOffsetLength = waveTrueHeight * (waveOffset || defaultWaveOffset)
const waveTop = waveTrueHeight + waveOffsetLength + canvasWH[1]
const waveBottom = waveOffsetLength + canvasWH[1]
const halfWidth = waveWidth / 2
this.wavePoints = new Array(waveTrueNum * 2 + 1).fill(0).map((t, i) =>
[i * halfWidth, i % 2 === 0 ? waveBottom : waveTop])
addWavePoint() && addWavePoint() && addWavePoint()
},
addWavePoint () {
const { wavePoints, waveTrueWidth } = this
const addPoint = [wavePoints[1][0] - waveTrueWidth, wavePoints[1][1]]
return wavePoints.unshift(addPoint)
},
calcBottomPoints () {
const { canvasWH } = this
this.bottomPoints = [
[...canvasWH],
[0, canvasWH[1]]
]
},
calcOverXPos () {
const { canvasWH: [width], waveTrueWidth } = this
this.overXPos = width + waveTrueWidth
},
drawWaveAnimation () {
const { clearCanvas, drawWaveAnimation } = this
clearCanvas()
const { getCurrentPoints, drawCurrentWave, calcNextFramePoints } = this
getCurrentPoints()
drawCurrentWave()
calcNextFramePoints()
this.animationHandler = requestAnimationFrame(drawWaveAnimation)
},
getCurrentPoints () {
const { level, wavePoints, canvasWH: [, height] } = this
this.currentPoints = level.map(l =>
wavePoints.map(([x, y]) =>
[x, y - (l / 100 * height)]))
},
drawCurrentWave () {
const { currentPoints, ctx, bottomPoints, drawColor, canvasWH: [, y], noGradient } = this
const { canvas: { drawSmoothlinePath, getLinearGradientColor } } = this
const { color: { hexToRgb } } = this
const multipleColor = typeof drawColor === 'object'
!multipleColor && (ctx.fillStyle = drawColor)
multipleColor &&
!noGradient &&
(ctx.fillStyle = getLinearGradientColor(ctx, [0, y], [0, 0], drawColor.map(c => hexToRgb(c, 0.5))))
const colorNum = drawColor.length
currentPoints.forEach((line, i) => {
drawSmoothlinePath(ctx, line, false, true, true)
ctx.lineTo(...bottomPoints[0])
ctx.lineTo(...bottomPoints[1])
ctx.closePath()
multipleColor && noGradient && (ctx.fillStyle = drawColor[i % colorNum])
ctx.fill()
})
},
calcNextFramePoints () {
const { wavePoints, waveAdded, addWavePoint, overXPos } = this
const addedWavePoints = wavePoints.map(([x, y]) => [x + waveAdded, y])
const lastPointIndex = addedWavePoints.length - 1
let addStatus = false
addedWavePoints[lastPointIndex][0] > overXPos &&
addedWavePoints.pop() && (addStatus = true)
this.wavePoints = addedWavePoints
addStatus && addWavePoint()
},
stopAnimation () {
const { animationHandler } = this
animationHandler && cancelAnimationFrame(animationHandler)
}
},
mounted () {
const { init } = this
init()
},
destroyed () {
const { stopAnimation } = this
stopAnimation()
}
}
</script>
<style lang="less">
.water-level-pond {
position: relative;
.percent-text {
position: absolute;
left: 50%;
top: 50%;
font-weight: bold;
transform: translate(-50%, -50%);
}
.svg-container {
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;
border-radius: 50%;
}
}
</style>