update for v 2.0.0

This commit is contained in:
jiaming743 2019-06-25 19:57:04 +08:00
parent 5c52a7b49d
commit 31fa0c6698
57 changed files with 2696 additions and 4887 deletions

26
CHANGELOG.md Normal file
View File

@ -0,0 +1,26 @@
# 2.0.0-alpha (2019-06-25)
### Perfect
- **SVG:** Refactoring **borderbox** and **decoration** components with SVG (Volume dropped to 6%).
- **charts:** New charting component that supports **animation**.
- **compatibility:** Better data compatibility.
### New
- **digitalFlop**
- **flylineChart**
- **borderBox**
- **decoration**
### Components
- **prefix:** Components add **dv** prefix to prevent conflicts.
```html
<!-- v 1.x.x -->
<decoration-1 />
<!-- v 2.x.x -->
<dv-decoration-1 />
```

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2019-present, JiaMing
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@ -1,15 +1,20 @@
# DataV
<h1 align="center">DataV</h1>
# Version 1.1.1
<p align="center">
<a href="https://github.com/jiaming743/datav/blob/master/LICENSE">
<img src="https://img.shields.io/github/license/jiaming743/datav.svg" alt="LICENSE" />
</a>x
<a href="https://www.npmjs.com/package/@jiaminghi/datav">
<img src="https://img.shields.io/npm/v/@jiaminghi/data-view.svg" alt="LICENSE" />
</a>
</p>
## Vue 大屏数据展示组件库
<h2 align="center">Vue large screen data display component library</h2>
## Vue Large screen data display component library
### Install with npm
[文档官网](http://datav.jiaminghi.com/)
```shell
$ npm install @jiaminghi/data-view
```
[Document of official website](http://datav.jiaminghi.com/)
[项目地址](https://github.com/jiaming743/DataV)
[Project address](https://github.com/jiaming743/DataV)
Detailed documents and examples can be viewed on the [HomePage](http://datav.jiaminghi.com).

View File

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

View File

@ -1,339 +0,0 @@
<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.

Before

Width:  |  Height:  |  Size: 93 KiB

View File

@ -1,54 +1,99 @@
<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">
<div class="dv-border-box-1" :ref="ref">
<svg
width="150px"
height="150px"
:key="item"
v-for="item in border"
:class="`${item} border`"
>
<polygon
fill="#4fd2dd"
points="6,66 6,18 12,12 18,12 24,6 27,6 30,9 36,9 39,6 84,6 81,9 75,9 73.2,7 40.8,7 37.8,10.2 24,10.2 12,21 12,24 9,27 9,51 7.8,54 7.8,63"
>
<animate
attributeName="fill"
values="#4fd2dd;#235fa7;#4fd2dd"
dur=".5s"
begin="0s"
repeatCount="indefinite"
/>
</polygon>
<polygon
fill="#235fa7"
points="27.599999999999998,4.8 38.4,4.8 35.4,7.8 30.599999999999998,7.8"
>
<animate
attributeName="fill"
values="#235fa7;#4fd2dd;#235fa7"
dur=".5s"
begin="0s"
repeatCount="indefinite"
/>
</polygon>
<polygon
fill="#4fd2dd"
points="9,54 9,63 7.199999999999999,66 7.199999999999999,75 7.8,78 7.8,110 8.4,110 8.4,66 9.6,66 9.6,54"
>
<animate
attributeName="fill"
values="#4fd2dd;#235fa7;#transparent"
dur="1s"
begin="0s"
repeatCount="indefinite"
/>
</polygon>
</svg>
<div class="border-box-content">
<slot></slot>
</div>
</div>
</template>
<script>
export default {
name: 'BorderBox1'
name: 'BorderBox1',
data () {
return {
ref: 'border-box-1',
border: ['left-top', 'right-top', 'left-bottom', 'right-bottom']
}
}
}
</script>
<style lang="less">
.dv-border-box-1 {
position: relative;
box-sizing: border-box;
min-width: 300px;
min-height: 300px;
padding: 35px;
overflow: hidden;
width: 100%;
height: 100%;
.dv-flash-left-top, .dv-flash-right-top, .dv-flash-left-bottom, .dv-flash-right-bottom {
.border {
position: absolute;
width: 250px;
}
.dv-flash-left-top {
top: 0px;
left: -70px;
}
.dv-flash-right-top {
top: 0px;
right: -70px;
.right-top {
right: 0px;
transform: rotateY(180deg);
}
.dv-flash-left-bottom {
.left-bottom {
bottom: 0px;
left: -70px;
transform: rotateX(180deg);
}
.dv-flash-right-bottom {
.right-bottom {
right: 0px;
bottom: 0px;
right: -70px;
transform: rotate(180deg);
transform: rotateX(180deg) rotateY(180deg);
}
.border-box-content {
position: relative;
width: 100%;
height: 100%;
}
}
</style>

View File

@ -11,19 +11,21 @@
<circle cx="11" :cy="height - 11" r="1" />
</svg>
<div class="border-box-content">
<slot></slot>
</div>
</div>
</template>
<script>
import borderBoxMixin from '../../mixins/borderBoxMixin.js'
import autoResize from '../../mixins/autoResize.js'
export default {
name: 'BorderBox2',
mixins: [borderBoxMixin],
mixins: [autoResize],
data () {
return {
ref: `border-box-2-${(new Date()).getTime()}`
ref: 'border-box-2'
}
}
}
@ -32,8 +34,8 @@ export default {
<style lang="less">
.dv-border-box-2 {
position: relative;
box-sizing: border-box;
padding: 30px;
width: 100%;
height: 100%;
.dv-border-svg-container {
position: absolute;
@ -59,5 +61,11 @@ export default {
.dv-bb2-line2 {
stroke: fade(#fff, 60);
}
.border-box-content {
position: relative;
width: 100%;
height: 100%;
}
}
</style>

View File

@ -11,19 +11,21 @@
:points="`22, 22 ${width - 4}, 22 ${width - 4}, ${height - 4} 22, ${height - 4} 22, 22`" />
</svg>
<div class="border-box-content">
<slot></slot>
</div>
</div>
</template>
<script>
import borderBoxMixin from '../../mixins/borderBoxMixin.js'
import autoResize from '../../mixins/autoResize.js'
export default {
name: 'BorderBox3',
mixins: [borderBoxMixin],
mixins: [autoResize],
data () {
return {
ref: `border-box-3-${(new Date()).getTime()}`
ref: 'border-box-3'
}
}
}
@ -32,8 +34,8 @@ export default {
<style lang="less">
.dv-border-box-3 {
position: relative;
box-sizing: border-box;
padding: 30px;
width: 100%;
height: 100%;
.dv-border-svg-container {
position: absolute;
@ -55,5 +57,11 @@ export default {
.dv-bb3-line2 {
stroke-width: 1;
}
.border-box-content {
position: relative;
width: 100%;
height: 100%;
}
}
</style>

View File

@ -15,30 +15,37 @@
<polyline class="dv-bb4-line-10" :points="`385, 17 ${width - 10}, 17`" />
</svg>
<div class="border-box-content">
<slot></slot>
</div>
</div>
</template>
<script>
import borderBoxMixin from '../../mixins/borderBoxMixin.js'
import autoResize from '../../mixins/autoResize.js'
export default {
name: 'BorderBox4',
mixins: [borderBoxMixin],
mixins: [autoResize],
data () {
return {
ref: `border-box-4-${(new Date()).getTime()}`
ref: 'border-box-4'
}
},
props: ['reverse']
props: {
reverse: {
type: Boolean,
default: false
}
}
}
</script>
<style lang="less" scoped>
.dv-border-box-4 {
position: relative;
box-sizing: border-box;
padding: 30px;
width: 100%;
height: 100%;
.dv-reverse {
transform: rotate(180deg);
@ -124,5 +131,11 @@ export default {
.sw1;
stroke-dasharray: 80 270;
}
.border-box-content {
position: relative;
width: 100%;
height: 100%;
}
}
</style>

View File

@ -11,30 +11,37 @@
<polyline class="dv-bb5-line-6" :points="`15, ${height - 13} ${width - 110}, ${height - 13}`" />
</svg>
<div class="border-box-content">
<slot></slot>
</div>
</div>
</template>
<script>
import borderBoxMixin from '../../mixins/borderBoxMixin.js'
import autoResize from '../../mixins/autoResize.js'
export default {
name: 'BorderBox5',
mixins: [borderBoxMixin],
mixins: [autoResize],
data () {
return {
ref: `border-box-5-${(new Date()).getTime()}`
ref: 'border-box-5'
}
},
props: ['reverse']
props: {
reverse: {
type: Boolean,
default: false
}
}
}
</script>
<style lang="less">
.dv-border-box-5 {
position: relative;
box-sizing: border-box;
padding: 20px;
width: 100%;
height: 100%;
.dv-reverse {
transform: rotate(180deg);
@ -71,5 +78,10 @@ export default {
stroke: fade(#fff, 15);
}
.border-box-content {
position: relative;
width: 100%;
height: 100%;
}
}
</style>

View File

@ -19,19 +19,21 @@
<polyline :points="`${width - 7}, ${height - 30} ${width - 7}, ${height - 80}`" />
</svg>
<div class="border-box-content">
<slot></slot>
</div>
</div>
</template>
<script>
import borderBoxMixin from '../../mixins/borderBoxMixin.js'
import autoResize from '../../mixins/autoResize.js'
export default {
name: 'BorderBox6',
mixins: [borderBoxMixin],
mixins: [autoResize],
data () {
return {
ref: `border-box-6-${(new Date()).getTime()}`
ref: 'border-box-6'
}
}
}
@ -40,8 +42,8 @@ export default {
<style lang="less">
.dv-border-box-6 {
position: relative;
box-sizing: border-box;
padding: 10px;
width: 100%;
height: 100%;
.dv-svg-container {
position: absolute;
@ -60,5 +62,11 @@ export default {
stroke: fade(#fff, 35);
}
}
.border-box-content {
position: relative;
width: 100%;
height: 100%;
}
}
</style>

View File

@ -12,19 +12,21 @@
<polyline class="dv-bb7-line-width-5" :points="`0, ${height - 10} 0, ${height} 10, ${height}`" />
</svg>
<div class="border-box-content">
<slot></slot>
</div>
</div>
</template>
<script>
import borderBoxMixin from '../../mixins/borderBoxMixin.js'
import autoResize from '../../mixins/autoResize.js'
export default {
name: 'BorderBox7',
mixins: [borderBoxMixin],
mixins: [autoResize],
data () {
return {
ref: `border-box-7-${(new Date()).getTime()}`
ref: 'border-box-7'
}
}
}
@ -35,10 +37,10 @@ export default {
.dv-border-box-7 {
position: relative;
width: 100%;
height: 100%;
box-shadow: inset 0 0 40px fade(@color, 30);
box-sizing: border-box;
border: 1px solid @color;
padding: 10px;
.dv-svg-container {
position: absolute;
@ -62,5 +64,11 @@ export default {
stroke: fade(gray, 50);
stroke-width: 5;
}
.border-box-content {
position: relative;
width: 100%;
height: 100%;
}
}
</style>

View File

@ -0,0 +1,108 @@
<template>
<div class="dv-border-box-8" :ref="ref">
<svg class="dv-svg-container">
<defs>
<path
:id="path"
:d="`M2.5, 2.5 L${width - 2.5}, 2.5 L${width - 2.5}, ${height - 2.5} L2.5, ${height - 2.5} L2.5, 2.5`"
fill="transparent"
/>
<radialGradient
:id="gradient"
cx="50%" cy="50%" r="50%"
>
<stop
offset="0%" stop-color="#fff"
stop-opacity="1"
/>
<stop
offset="100%" stop-color="#fff"
stop-opacity="0"
/>
</radialGradient>
<mask :id="mask">
<circle cx="0" cy="0" r="150" :fill="`url(#${gradient})`">
<animateMotion
dur="3s"
:path="`M2.5, 2.5 L${width - 2.5}, 2.5 L${width - 2.5}, ${height - 2.5} L2.5, ${height - 2.5} L2.5, 2.5`"
rotate="auto"
repeatCount="indefinite"
/>
</circle>
</mask>
</defs>
<use
stroke="#235fa7"
stroke-width="1"
:xlink:href="`#${path}`"
/>
<use
stroke="#4fd2dd"
stroke-width="3"
:xlink:href="`#${path}`"
:mask="`url(#${mask})`"
>
<animate
attributeName="stroke-dasharray"
:from="`0, ${length}`"
:to="`${length}, 0`"
dur="3s"
repeatCount="indefinite"
/>
</use>
</svg>
<div class="border-box-content">
<slot></slot>
</div>
</div>
</template>
<script>
import autoResize from '../../mixins/autoResize.js'
export default {
name: 'BorderBox8',
mixins: [autoResize],
data () {
return {
ref: 'border-box-8',
path: `border-box-8-path-${(new Date()).getTime()}`,
gradient: `border-box-8-gradient-${(new Date()).getTime()}`,
mask: `border-box-8-mask-${(new Date()).getTime()}`
}
},
computed: {
length () {
const { width, height } = this
return (width + height - 5) * 2
}
}
}
</script>
<style lang="less">
.dv-border-box-8 {
position: relative;
width: 100%;
height: 100%;
svg {
position: absolute;
width: 100%;
height: 100%;
left: 0px;
top: 0px;
}
.border-box-content {
position: relative;
width: 100%;
height: 100%;
}
}
</style>

View File

@ -1,167 +0,0 @@
<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,87 @@
<template>
<div class="dv-charts-container" :ref="ref">
<div class="charts" :ref="chartRef" />
<div class="charts-slot-content">
<slot></slot>
</div>
</div>
</template>
<script>
import autoResize from '../../mixins/autoResize.js'
import Charts from '@jiaminghi/charts'
export default {
name: 'Charts',
mixins: [autoResize],
props: {
option: {
type: Object,
default: {}
}
},
data () {
return {
ref: `charts-container-${(new Date()).getTime()}`,
chartRef: `chart-${(new Date()).getTime()}`,
chart: null
}
},
watch: {
option () {
let { chart, option } = this
if (!option) option = {}
chart.setOption(option)
}
},
methods: {
afterAutoResizeMixinInit () {
const { initChart } = this
initChart()
},
initChart () {
const { $refs, chartRef, option } = this
const chart = this.chart = new Charts($refs[chartRef])
if (!option) return
chart.setOption(option)
},
onResize () {
const { chart } = this
if (!chart) return
chart.resize()
}
}
}
</script>
<style lang="less">
.dv-charts-container {
position: relative;
width: 100%;
height: 100%;
.charts {
width: 100%;
height: 100%;
}
.charts-slot-content {
position: absolute;
left: 0px;
top: 0px;
width: 100%;
height: 100%;
}
}
</style>

View File

@ -1,620 +0,0 @@
<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

@ -1,179 +0,0 @@
<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.

Before

Width:  |  Height:  |  Size: 48 KiB

View File

@ -1,20 +1,177 @@
<template>
<div class="decoration-1">
<img src="./img/decoration.gif" />
<div class="dv-decoration-1" :ref="ref">
<svg :width="`${svgWH[0]}px`" :height="`${svgWH[1]}px`" :style="`transform:scale(${svgScale[0]},${svgScale[1]});`">
<template
v-for="(point, i) in points"
>
<rect
v-if="Math.random() > 0.6"
:key="i"
fill="#fff"
:x="point[0] - halfPointSideLength"
:y="point[1] - halfPointSideLength"
:width="pointSideLength"
:height="pointSideLength"
>
<animate
v-if="Math.random() > 0.6"
attributeName="fill"
values="#fff;transparent"
dur="1s"
:begin="Math.random() * 2"
repeatCount="indefinite"
/>
</rect>
</template>
<rect
v-if="rects[0]"
fill="#0de7c2"
:x="rects[0][0] - pointSideLength"
:y="rects[0][1] - pointSideLength"
:width="pointSideLength * 2"
:height="pointSideLength * 2"
>
<animate
attributeName="width"
:values="`0;${pointSideLength * 2}`"
dur="2s"
repeatCount="indefinite"
/>
<animate
attributeName="height"
:values="`0;${pointSideLength * 2}`"
dur="2s"
repeatCount="indefinite"
/>
<animate
attributeName="x"
:values="`${rects[0][0]};${rects[0][0] - pointSideLength}`"
dur="2s"
repeatCount="indefinite"
/>
<animate
attributeName="y"
:values="`${rects[0][1]};${rects[0][1] - pointSideLength}`"
dur="2s"
repeatCount="indefinite"
/>
</rect>
<rect
v-if="rects[1]"
fill="#0de7c2"
:x="rects[1][0] - 40"
:y="rects[1][1] - pointSideLength"
:width="40"
:height="pointSideLength * 2"
>
<animate
attributeName="width"
values="0;40;0"
dur="2s"
repeatCount="indefinite"
/>
<animate
attributeName="x"
:values="`${rects[1][0]};${rects[1][0] - 40};${rects[1][0]}`"
dur="2s"
repeatCount="indefinite"
/>
</rect>
</svg>
</div>
</template>
<script>
import autoResize from '../../mixins/autoResize.js'
export default {
name: 'Decoration1'
name: 'Decoration1',
mixins: [autoResize],
data () {
const pointSideLength = 2.5
return {
ref: 'decoration-1',
svgWH: [200, 50],
svgScale: [1, 1],
rowNum: 4,
rowPoints: 20,
pointSideLength,
halfPointSideLength: pointSideLength / 2,
points: [],
rects: []
}
},
methods: {
afterAutoResizeMixinInit () {
const { calcSVGData } = this
calcSVGData()
},
calcSVGData () {
const { calcPointsPosition, calcRectsPosition, calcScale } = this
calcPointsPosition()
calcRectsPosition()
calcScale()
},
calcPointsPosition () {
const { svgWH, rowNum, rowPoints } = this
const [w, h] = svgWH
const horizontalGap = w / (rowPoints + 1)
const verticalGap = h / (rowNum + 1)
let points = new Array(rowNum).fill(0).map((foo, i) =>
new Array(rowPoints).fill(0).map((foo, j) => [
horizontalGap * (j + 1), verticalGap * (i + 1)
]))
this.points = points.reduce((all, item) => [...all, ...item], [])
},
calcRectsPosition () {
const { points, rowPoints } = this
const rect1 = points[rowPoints * 2 - 1]
const rect2 = points[rowPoints * 2 - 3]
this.rects = [rect1, rect2]
},
calcScale () {
const { width, height, svgWH } = this
const [w, h] = svgWH
this.svgScale = [width / w, height / h]
},
onResize () {
const { calcSVGData } = this
calcSVGData()
}
}
}
</script>
<style lang="less">
.decoration-1 {
img {
.dv-decoration-1 {
width: 100%;
height: 100%;
svg {
transform-origin: left top;
}
}
</style>

View File

@ -1,77 +1,101 @@
<template>
<div class="decoration-2" :ref="ref">
<div :class="reverse ? 'reverse' : 'normal'" />
<div class="dv-decoration-2" :ref="ref">
<svg :width="`${width}px`" :height="`${height}px`">
<rect :x="x" :y="y" :width="w" :height="h" fill="#3faacb">
<animate
:attributeName="reverse ? 'height' : 'width'"
from="0"
:to="reverse ? height : width"
dur="6s"
calcMode="spline"
keyTimes="0;1"
keySplines=".42,0,.58,1"
repeatCount="indefinite"
/>
</rect>
<rect :x="x" :y="y" width="1" height="1" fill="#fff">
<animate
:attributeName="reverse ? 'y' : 'x'"
from="0"
:to="reverse ? height : width"
dur="6s"
calcMode="spline"
keyTimes="0;1"
keySplines=".42,0,.58,1"
repeatCount="indefinite"
/>
</rect>
</svg>
</div>
</template>
<script>
import autoResize from '../../mixins/autoResize.js'
export default {
name: 'Decoration2',
mixins: [autoResize],
props: {
reverse: {
type: Boolean,
default: false
}
},
data () {
return {
ref: `decoration-2-${(new Date()).getTime()}`,
width: 0,
height: 0
ref: 'decoration-2',
x: 0,
y: 0,
w: 0,
h: 0
}
},
watch: {
reverse () {
const { calcSVGData } = this
calcSVGData()
}
},
props: ['reverse'],
methods: {
init () {
const { $nextTick, $refs, ref } = this
afterAutoResizeMixinInit () {
const { calcSVGData } = this
$nextTick(e => {
this.width = $refs[ref].clientWidth
this.height = $refs[ref].clientHeight
})
calcSVGData()
},
calcSVGData () {
const { reverse, width, height } = this
if (reverse) {
this.w = 1
this.h = height
this.x = width / 2
this.y = 0
} else {
this.w = width
this.h = 1
this.x = 0
this.y = height / 2
}
},
mounted () {
const { init } = this
onResize () {
const { calcSVGData } = this
init()
calcSVGData()
}
}
}
</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% {
<style lang="less">
.dv-decoration-2 {
display: flex;
width: 100%;
}
100% {
width: 100%;
}
}
height: 100%;
justify-content: center;
align-items: center;
}
</style>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

View File

@ -1,20 +1,108 @@
<template>
<div class="decoration-3">
<img src="./img/decoration.gif" />
<div class="dv-decoration-3" :ref="ref">
<svg :width="`${svgWH[0]}px`" :height="`${svgWH[1]}px`" :style="`transform:scale(${svgScale[0]},${svgScale[1]});`">
<template
v-for="(point, i) in points"
>
<rect
:key="i"
fill="#7acaec"
:x="point[0] - halfPointSideLength"
:y="point[1] - halfPointSideLength"
:width="pointSideLength"
:height="pointSideLength"
>
<animate
v-if="Math.random() > 0.6"
attributeName="fill"
values="#7acaec;transparent"
:dur="Math.random() + 1 + 's'"
:begin="Math.random() * 2"
repeatCount="indefinite"
/>
</rect>
</template>
</svg>
</div>
</template>
<script>
import autoResize from '../../mixins/autoResize.js'
export default {
name: 'Decoration3'
name: 'Decoration3',
mixins: [autoResize],
data () {
const pointSideLength = 7
return {
ref: 'decoration-3',
svgWH: [300, 35],
svgScale: [1, 1],
rowNum: 2,
rowPoints: 25,
pointSideLength,
halfPointSideLength: pointSideLength / 2,
points: []
}
},
methods: {
afterAutoResizeMixinInit () {
const { calcSVGData } = this
calcSVGData()
},
calcSVGData () {
const { calcPointsPosition, calcScale } = this
calcPointsPosition()
calcScale()
},
calcPointsPosition () {
const { svgWH, rowNum, rowPoints } = this
const [w, h] = svgWH
const horizontalGap = w / (rowPoints + 1)
const verticalGap = h / (rowNum + 1)
let points = new Array(rowNum).fill(0).map((foo, i) =>
new Array(rowPoints).fill(0).map((foo, j) => [
horizontalGap * (j + 1), verticalGap * (i + 1)
]))
this.points = points.reduce((all, item) => [...all, ...item], [])
},
calcScale () {
const { width, height, svgWH } = this
const [w, h] = svgWH
this.svgScale = [width / w, height / h]
},
onResize () {
const { calcSVGData } = this
calcSVGData()
}
}
}
</script>
<style lang="less">
.decoration-3 {
img {
.dv-decoration-3 {
width: 100%;
height: 100%;
svg {
transform-origin: left top;
}
}
</style>

View File

@ -1,92 +1,66 @@
<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>
<div class="dv-decoration-4" :ref="ref">
<div
:class="`container ${reverse ? 'reverse' : 'normal'}`"
:style="reverse ? `width:${width}px;height:5px` : `width:5px;height:${height}px;`"
>
<svg :width="reverse ? width : 5" :height="reverse ? 5 : height">
<polyline
stroke="rgba(255, 255, 255, 0.3)"
:points="reverse ? `0, 2.5 ${width}, 2.5` : `2.5, 0 2.5, ${height}`"
/>
<polyline
class="bold-line"
stroke="rgba(255, 255, 255, 0.3)"
stroke-width="3"
stroke-dasharray="20, 80"
stroke-dashoffset="-30"
:points="reverse ? `0, 2.5 ${width}, 2.5` : `2.5, 0 2.5, ${height}`"
/>
</svg>
</div>
</div>
</template>
<script>
import autoResize from '../../mixins/autoResize.js'
export default {
name: 'Decoration4',
mixins: [autoResize],
props: ['reverse'],
data () {
return {
ref: `decoration-4-${(new Date()).getTime()}`,
width: 0,
height: 0
ref: 'decoration-4'
}
},
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 {
.dv-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%;
.container {
display: flex;
overflow: hidden;
position: absolute;
}
.normal {
height: 0% !important;
animation: ani-height 3s ease-in-out infinite;
left: 50%;
margin-left: -2px;
}
.reverse {
width: 0% !important;
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;
top: 50%;
margin-top: -2px;
}
@keyframes ani-height {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

View File

@ -1,22 +1,110 @@
<template>
<div class="dv-decoration-5">
<img src="./img/decoration.gif" />
<div class="dv-decoration-5" :ref="ref">
<svg :width="width" :height="height">
<polyline
fill="transparent"
stroke="#3f96a5"
stroke-width="3"
:points="line1Points"
>
<animate
attributeName="stroke-dasharray"
attributeType="XML"
:from="`0, ${line1Length / 2}, 0, ${line1Length / 2}`"
:to="`0, 0, ${line1Length}, 0`"
dur="1.2s"
begin="0s"
calcMode="spline"
keyTimes="0;1"
keySplines=".4,1,.49,.98"
repeatCount="indefinite"
/>
</polyline>
<polyline
fill="transparent"
stroke="#3f96a5"
stroke-width="2"
:points="line2Points"
>
<animate
attributeName="stroke-dasharray"
attributeType="XML"
:from="`0, ${line2Length / 2}, 0, ${line2Length / 2}`"
:to="`0, 0, ${line2Length}, 0`"
dur="1.2s"
begin="0s"
calcMode="spline"
keyTimes="0;1"
keySplines=".4,1,.49,.98"
repeatCount="indefinite"
/>
</polyline>
</svg>
</div>
</template>
<script>
import autoResize from '../../mixins/autoResize.js'
import { getPolylineLength } from '@jiaminghi/charts/lib/util'
export default {
name: 'Decoration5'
name: 'Decoration5',
mixins: [autoResize],
data () {
return {
ref: 'decoration-5',
line1Points: '',
line2Points: '',
line1Length: 0,
line2Length: 0,
}
},
methods: {
afterAutoResizeMixinInit () {
const { calcSVGData } = this
calcSVGData()
},
calcSVGData () {
const { width, height } = this
let line1Points = [
[0, height * 0.2], [width * 0.18, height * 0.2], [width * 0.2, height * 0.4], [width * 0.25, height * 0.4],
[width * 0.27, height * 0.6], [width * 0.72, height * 0.6], [width * 0.75, height * 0.4],
[width * 0.8, height * 0.4], [width * 0.82, height * 0.2], [width, height * 0.2]
]
let line2Points = [
[width * 0.3, height * 0.8], [width * 0.7, height * 0.8]
]
const line1Length = getPolylineLength(line1Points)
const line2Length = getPolylineLength(line2Points)
line1Points = line1Points.map(point => point.join(',')).join(' ')
line2Points = line2Points.map(point => point.join(',')).join(' ')
this.line1Points = line1Points
this.line2Points = line2Points
this.line1Length = line1Length
this.line2Length = line2Length
},
onResize () {
const { calcSVGData } = this
calcSVGData()
}
}
}
</script>
<style lang="less">
.dv-decoration-5 {
img {
width: 100%;
margin-top: -21%;
margin-bottom: -25%;
}
height: 100%;
}
</style>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 242 KiB

View File

@ -1,20 +1,133 @@
<template>
<div class="decoration-6">
<img src="./img/decoration.gif" />
<div class="dv-decoration-6" :ref="ref">
<svg :width="`${svgWH[0]}px`" :height="`${svgWH[1]}px`" :style="`transform:scale(${svgScale[0]},${svgScale[1]});`">
<template
v-for="(point, i) in points"
>
<rect
:key="i"
fill="#7acaec"
:x="point[0] - halfRectWidth"
:y="point[1] - heights[i] / 2"
:width="rectWidth"
:height="heights[i]"
>
<animate
attributeName="y"
:values="`${point[1] - minHeights[i] / 2};${point[1] - heights[i] / 2};${point[1] - minHeights[i] / 2}`"
:dur="`${randoms[i]}s`"
keyTimes="0;.5;1"
calcMode="spline"
keySplines=".42,0,.58,1;.42,0,.58,1"
begin="0s"
repeatCount="indefinite"
/>
<animate
attributeName="height"
:values="`${minHeights[i]};${heights[i]};${minHeights[i]}`"
:dur="`${randoms[i]}s`"
keyTimes="0;.5;1"
calcMode="spline"
keySplines=".42,0,.58,1;.42,0,.58,1"
begin="0s"
repeatCount="indefinite"
/>
</rect>
</template>
</svg>
</div>
</template>
<script>
import autoResize from '../../mixins/autoResize.js'
import { randomExtend } from '../../util'
export default {
name: 'Decoration6'
name: 'Decoration6',
mixins: [autoResize],
data () {
const rectWidth = 7
return {
ref: 'decoration-6',
svgWH: [300, 35],
svgScale: [1, 1],
rowNum: 1,
rowPoints: 40,
rectWidth,
halfRectWidth: rectWidth / 2,
points: [],
heights: [],
minHeights: [],
randoms: []
}
},
methods: {
afterAutoResizeMixinInit () {
const { calcSVGData } = this
calcSVGData()
},
calcSVGData () {
const { calcPointsPosition, calcScale } = this
calcPointsPosition()
calcScale()
},
calcPointsPosition () {
const { svgWH, rowNum, rowPoints } = this
const [w, h] = svgWH
const horizontalGap = w / (rowPoints + 1)
const verticalGap = h / (rowNum + 1)
let points = new Array(rowNum).fill(0).map((foo, i) =>
new Array(rowPoints).fill(0).map((foo, j) => [
horizontalGap * (j + 1), verticalGap * (i + 1)
]))
this.points = points.reduce((all, item) => [...all, ...item], [])
const heights = this.heights = new Array(rowNum * rowPoints)
.fill(0).map(foo =>
Math.random() > 0.8 ? randomExtend(0.7 * h, h) : randomExtend(0.2 * h, 0.5 * h))
this.minHeights = new Array(rowNum * rowPoints)
.fill(0).map((foo, i) => heights[i] * Math.random())
this.randoms = new Array(rowNum * rowPoints)
.fill(0).map(foo => Math.random() + 1.5)
},
calcScale () {
const { width, height, svgWH } = this
const [w, h] = svgWH
this.svgScale = [width / w, height / h]
},
onResize () {
const { calcSVGData } = this
calcSVGData()
}
}
}
</script>
<style lang="less">
.decoration-6 {
img {
.dv-decoration-6 {
width: 100%;
height: 100%;
svg {
transform-origin: left top;
}
}
</style>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 496 B

View File

@ -1,8 +1,34 @@
<template>
<div class="decoration-7">
<img src="./img/decoration.png" />
<div class="dv-decoration-7">
<svg width="21px" height="20px">
<polyline
stroke-width="4"
fill="transparent"
stroke="#1dc1f5"
points="10, 0 19, 10 10, 20"
/>
<polyline
stroke-width="2"
fill="transparent"
stroke="#1dc1f5"
points="2, 0 11, 10 2, 20"
/>
</svg>
<slot></slot>
<img class="reverse" src="./img/decoration.png" />
<svg width="21px" height="20px">
<polyline
stroke-width="4"
fill="transparent"
stroke="#1dc1f5"
points="11, 0 2, 10 11, 20"
/>
<polyline
stroke-width="2"
fill="transparent"
stroke="#1dc1f5"
points="19, 0 10, 10 19, 20"
/>
</svg>
</div>
</template>
@ -13,14 +39,11 @@ export default {
</script>
<style lang="less">
.decoration-7 {
.dv-decoration-7 {
display: flex;
flex-direction: row;
width: 100%;
height: 100%;
justify-content: center;
align-items: center;
.reverse {
transform: rotate(180deg);
}
}
</style>

View File

@ -0,0 +1,63 @@
<template>
<div class="dv-decoration-8" :ref="ref">
<svg>
<polyline
stroke="#3f96a5"
stroke-width="2"
fill="transparent"
:points="`${xPos(0)}, 0 ${xPos(30)}, ${height / 2}`"
/>
<polyline
stroke="#3f96a5"
stroke-width="2"
fill="transparent"
:points="`${xPos(20)}, 0 ${xPos(50)}, ${height / 2} ${xPos(width)}, ${height / 2}`"
/>
<polyline
stroke="#3f96a5"
fill="transparent"
stroke-width="3"
:points="`${xPos(0)}, ${height - 3}, ${xPos(200)}, ${height - 3}`"
/>
</svg>
</div>
</template>
<script>
import autoResize from '../../mixins/autoResize.js'
export default {
name: 'Decoration8',
mixins: [autoResize],
props: {
reverse: {
type: Boolean,
default: false
}
},
data () {
return {
ref: 'decoration-8',
}
},
methods: {
xPos (pos) {
const { reverse, width } = this
if (!reverse) return pos
return width - pos
}
}
}
</script>
<style lang="less">
.dv-decoration-8 {
display: flex;
width: 100%;
height: 100%;
}
</style>

View File

@ -0,0 +1,194 @@
<template>
<div class="dv-digital-flop">
<canvas ref="digital-flop" />
</div>
</template>
<script>
import CRender from '@jiaminghi/c-render'
import '@jiaminghi/charts/lib/extend/index'
import { deepMerge } from '@jiaminghi/charts/lib/util/index'
import { deepClone } from '@jiaminghi/c-render/lib/plugin/util'
export default {
name: 'DigitalFlop',
props: {
config: {
type: Object,
default: () => ({})
}
},
data () {
return {
render: null,
defaultConfig: {
/**
* @description Number for digital flop
* @type {Array<Number>}
* @default number = []
* @example number = [10]
*/
number: [],
/**
* @description Content formatter
* @type {String}
* @default content = ''
* @example content = '{nt}个'
*/
content: '',
/**
* @description Number toFixed
* @type {Number}
* @default toFixed = 0
*/
toFixed: 0,
/**
* @description Text align
* @type {String}
* @default textAlign = 'center'
* @example textAlign = 'center' | 'left' | 'right'
*/
textAlign: 'center',
/**
* @description Text style configuration
* @type {Object} {CRender Class Style}
*/
style: {
fontSize: 30,
fill: '#3de7c9'
},
/**
* @description CRender animationCurve
* @type {String}
* @default animationCurve = 'easeOutCubic'
*/
animationCurve: 'easeOutCubic',
/**
* @description CRender animationFrame
* @type {String}
* @default animationFrame = 50
*/
animationFrame: 50
},
mergedConfig: null,
graph: null
}
},
watch: {
config () {
const { update } = this
update()
}
},
methods: {
init () {
const { initRender, mergeConfig, initGraph } = this
initRender()
mergeConfig()
initGraph()
},
initRender () {
const { $refs } = this
this.render = new CRender($refs['digital-flop'])
},
mergeConfig () {
const { defaultConfig, config } = this
this.mergedConfig = deepMerge(deepClone(defaultConfig, true), config || {})
},
initGraph () {
const { getShape, getStyle, render, mergedConfig } = this
const { animationCurve, animationFrame } = mergedConfig
const shape = getShape()
const style = getStyle()
this.graph = render.add({
name: 'numberText',
animationCurve,
animationFrame,
shape,
style
})
},
getShape () {
const { number, content, toFixed, textAlign } = this.mergedConfig
const [w, h] = this.render.area
const position = [w / 2, h / 2]
if (textAlign === 'left') position[0] = 0
if (textAlign === 'right') position[0] = w
return {
number,
content,
toFixed,
position
}
},
getStyle () {
const { style, textAlign } = this.mergedConfig
return deepMerge(style, {
textAlign,
textBaseline: 'middle'
})
},
update () {
const { mergeConfig, mergeShape, getShape, getStyle, graph, mergedConfig } = this
mergeConfig()
if (!graph) return
const { animationCurve, animationFrame } = mergedConfig
const shape = getShape()
const style = getStyle()
mergeShape(graph, shape)
graph.animationCurve = animationCurve
graph.animationFrame = animationFrame
graph.animation('style', style, true)
graph.animation('shape', shape)
},
mergeShape (graph, shape) {
const cacheNum = graph.shape.number.length
const shapeNum = shape.number.length
if (cacheNum !== shapeNum) graph.shape.number = shape.number
}
},
mounted () {
const { init } = this
init()
}
}
</script>
<style lang="less">
.dv-digital-flop {
canvas {
width: 100%;
height: 100%;
}
}
</style>

View File

@ -0,0 +1,509 @@
<template>
<div
class="dv-flyline-chart"
ref="dv-flyline-chart"
:style="`background-image: url(${mergedConfig ? mergedConfig.bgImgUrl : ''})`"
@click="consoleClickPos"
>
<svg v-if="mergedConfig" :width="width" :height="height">
<defs>
<radialGradient
:id="gradientId"
cx="50%" cy="50%" r="50%"
>
<stop
offset="0%" stop-color="#fff"
stop-opacity="1"
/>
<stop
offset="100%" stop-color="#fff"
stop-opacity="0"
/>
</radialGradient>
<radialGradient
:id="gradient2Id"
cx="50%" cy="50%" r="50%"
>
<stop
offset="0%" stop-color="#fff"
stop-opacity="0"
/>
<stop
offset="100%" stop-color="#fff"
stop-opacity="1"
/>
</radialGradient>
<circle
v-if="paths[0]"
:id="`circle${paths[0].toString()}`"
:cx="paths[0][2][0]"
:cy="paths[0][2][1]"
>
<animate
attributeName="r"
:values="`1;${mergedConfig.halo.radius}`"
:dur="mergedConfig.halo.duration / 10 + 's'"
repeatCount="indefinite"
/>
<animate
attributeName="opacity"
values="1;0"
:dur="mergedConfig.halo.duration / 10 + 's'"
repeatCount="indefinite"
/>
</circle>
</defs>
<image
v-if="paths[0]"
:xlink:href="mergedConfig.centerPointImg.url"
:width="mergedConfig.centerPointImg.width"
:height="mergedConfig.centerPointImg.height"
:x="paths[0][2][0] - mergedConfig.centerPointImg.width / 2"
:y="paths[0][2][1] - mergedConfig.centerPointImg.height / 2"
/>
<mask :id="`maskhalo${paths[0].toString()}`">
<use
v-if="paths[0]"
:xlink:href="`#circle${paths[0].toString()}`"
:fill="`url(#${gradient2Id})`"
/>
</mask>
<use
v-if="paths[0] && mergedConfig.halo.show"
:xlink:href="`#circle${paths[0].toString()}`"
:fill="mergedConfig.halo.color"
:mask="`url(#maskhalo${paths[0].toString()})`"
/>
<g
v-for="(path, i) in paths"
:key="i"
>
<defs>
<path
:id="`path${path.toString()}`"
:ref="`path${i}`"
:d="`M${path[0].toString()} Q${path[1].toString()} ${path[2].toString()}`"
fill="transparent"
/>
</defs>
<use
:xlink:href="`#path${path.toString()}`"
:stroke-width="mergedConfig.lineWidth"
:stroke="mergedConfig.orbitColor"
/>
<use
v-if="lengths[i]"
:xlink:href="`#path${path.toString()}`"
:stroke-width="mergedConfig.lineWidth"
:stroke="mergedConfig.flylineColor"
:mask="`url(#mask${unique}${path.toString()})`"
>
<animate
attributeName="stroke-dasharray"
:from="`0, ${lengths[i]}`"
:to="`${lengths[i]}, 0`"
:dur="times[i] || 0"
repeatCount="indefinite"
/>
</use>
<mask :id="`mask${unique}${path.toString()}`">
<circle cx="0" cy="0" :r="mergedConfig.flylineRadius" :fill="`url(#${gradientId})`">
<animateMotion
:dur="times[i] || 0"
:path="`M${path[0].toString()} Q${path[1].toString()} ${path[2].toString()}`"
rotate="auto"
repeatCount="indefinite"
/>
</circle>
</mask>
<image
:xlink:href="mergedConfig.pointsImg.url"
:width="mergedConfig.pointsImg.width"
:height="mergedConfig.pointsImg.height"
:x="path[0][0] - mergedConfig.pointsImg.width / 2"
:y="path[0][1] - mergedConfig.pointsImg.height / 2"
/>
<text
:style="`fontSize:${mergedConfig.text.fontSize}px;`"
:fill="mergedConfig.text.color"
:x="path[0][0] + mergedConfig.text.offset[0]"
:y="path[0][1] + mergedConfig.text.offset[1]"
>
{{ texts[i] }}
</text>
</g>
</svg>
</div>
</template>
<script>
import { deepMerge } from '@jiaminghi/charts/lib/util/index'
import { deepClone } from '@jiaminghi/c-render/lib/plugin/util'
import { randomExtend } from '../../util/index'
import autoResize from '../../mixins/autoResize.js'
export default {
name: 'PercentPond',
mixins: [autoResize],
props: {
config: {
type: Object,
default: {}
},
dev: {
type: Boolean,
default: false
}
},
data () {
return {
ref: 'dv-flyline-chart',
unique: Math.random(),
maskId: `flyline-mask-id-${(new Date()).getTime()}`,
maskCircleId: `mask-circle-id-${(new Date()).getTime()}`,
gradientId: `gradient-id-${(new Date()).getTime()}`,
gradient2Id: `gradient2-id-${(new Date()).getTime()}`,
defaultConfig: {
/**
* @description Flyline chart center point
* @type {Array<Number>}
* @default centerPoint = [0, 0]
*/
centerPoint: [0, 0],
/**
* @description Flyline start points
* @type {Array<Array<Number>>}
* @default points = []
* @example points = [[10, 10], [100, 100]]
*/
points: [],
/**
* @description Flyline width
* @type {Number}
* @default lineWidth = 1
*/
lineWidth: 1,
/**
* @description Orbit color
* @type {String}
* @default orbitColor = 'rgba(103, 224, 227, .2)'
*/
orbitColor: 'rgba(103, 224, 227, .2)',
/**
* @description Flyline color
* @type {String}
* @default orbitColor = '#ffde93'
*/
flylineColor: '#ffde93',
/**
* @description K value
* @type {Number}
* @default k = -0.5
* @example k = -1 ~ 1
*/
k: -0.5,
/**
* @description Flyline curvature
* @type {Number}
* @default curvature = 5
*/
curvature: 5,
/**
* @description Flyline radius
* @type {Number}
* @default flylineRadius = 100
*/
flylineRadius: 100,
/**
* @description Flyline animation duration
* @type {Array<Number>}
* @default duration = [20, 30]
*/
duration: [20, 30],
/**
* @description Relative points position
* @type {Boolean}
* @default relative = true
*/
relative: true,
/**
* @description Back ground image url
* @type {String}
* @default bgImgUrl = ''
* @example bgImgUrl = './img/bg.jpg'
*/
bgImgUrl: '',
/**
* @description Text configuration
* @type {Object}
*/
text: {
/**
* @description Text offset
* @type {Array<Number>}
* @default offset = [0, 15]
*/
offset: [0, 15],
/**
* @description Text color
* @type {String}
* @default color = '#ffdb5c'
*/
color: '#ffdb5c',
/**
* @description Text font size
* @type {Number}
* @default fontSize = 12
*/
fontSize: 12
},
/**
* @description Halo configuration
* @type {Object}
*/
halo: {
/**
* @description Weather to show halo
* @type {Boolean}
* @default show = true
* @example show = true | false
*/
show: true,
/**
* @description Halo animation duration (10 = 1s)
* @type {Number}
* @default duration = 30
*/
duration: 30,
/**
* @description Halo color
* @type {String}
* @default color = '#fb7293'
*/
color: '#fb7293',
/**
* @description Halo max radius
* @type {Number}
* @default radius = 120
*/
radius: 120
},
/**
* @description Center point img configuration
* @type {Object}
*/
centerPointImg: {
/**
* @description Center point img width
* @type {Number}
* @default width = 40
*/
width: 40,
/**
* @description Center point img height
* @type {Number}
* @default height = 40
*/
height: 40,
/**
* @description Center point img url
* @type {String}
* @default url = ''
*/
url: ''
},
/**
* @description Points img configuration
* @type {Object}
* @default radius = 120
*/
pointsImg: {
/**
* @description Points img width
* @type {Number}
* @default width = 15
*/
width: 15,
/**
* @description Points img height
* @type {Number}
* @default height = 15
*/
height: 15,
/**
* @description Points img url
* @type {String}
* @default url = ''
*/
url: ''
}
},
mergedConfig: null,
paths: [],
lengths: [],
times: [],
texts: []
}
},
watch: {
config () {
const { calcData } = this
calcData()
}
},
methods: {
afterAutoResizeMixinInit () {
const { calcData } = this
calcData()
},
onResize () {
const { calcData } = this
calcData()
},
async calcData () {
const { mergeConfig, createFlylinePaths, calcLineLengths } = this
mergeConfig()
createFlylinePaths()
await calcLineLengths()
const { calcTimes, calcTexts } = this
calcTimes()
calcTexts()
},
mergeConfig () {
let { config, defaultConfig } = this
const mergedConfig = deepMerge(deepClone(defaultConfig, true), config || {})
const { points } = mergedConfig
mergedConfig.points = points.map(item => {
if (item instanceof Array) {
return { position: item, text: '' }
}
return item
})
this.mergedConfig = mergedConfig
},
createFlylinePaths () {
const { getPath, mergedConfig, width, height } = this
let { centerPoint, points, relative } = mergedConfig
points = points.map(({ position }) => position)
if (relative) {
centerPoint = [width * centerPoint[0], height * centerPoint[1]]
points = points.map(([x, y]) => [width * x, height * y])
}
this.paths = points.map(point => getPath(centerPoint, point))
},
getPath (center, point) {
const { getControlPoint } = this
const controlPoint = getControlPoint(center, point)
return [point, controlPoint, center]
},
getControlPoint ([sx, sy], [ex, ey]) {
const { getPointDistance, getKLinePointByx, mergedConfig } = this
const { curvature, k } = mergedConfig
const [mx, my] = [(sx + ex) / 2, (sy + ey) / 2]
const distance = getPointDistance([sx, sy], [ex, ey])
const targetLength = distance / curvature
const disDived = targetLength / 2
let [dx, dy] = [mx, my]
do {
dx += disDived
dy = getKLinePointByx(k, [mx, my], dx)[1]
} while (getPointDistance([mx, my], [dx, dy]) < targetLength)
return [dx, dy]
},
getKLinePointByx (k, [lx, ly], x) {
const y = ly - k * lx + k * x
return [x, y]
},
async calcLineLengths () {
const { $nextTick, paths, $refs } = this
await $nextTick()
this.lengths = paths.map((foo, i) => $refs[`path${i}`][0].getTotalLength())
},
calcTimes () {
const { duration, points } = this.mergedConfig
this.times = points.map(foo => randomExtend(...duration) / 10)
},
calcTexts () {
const { points } = this.mergedConfig
this.texts = points.map(({ text }) => text)
},
consoleClickPos ({ offsetX, offsetY }) {
const { width, height, dev } = this
if (!dev) return
const relativeX = (offsetX / width).toFixed(2)
const relativeY = (offsetY / height).toFixed(2)
console.warn(`dv-flyline-chart DEV: \n Click Position is [${offsetX}, ${offsetY}] \n Relative Position is [${relativeX}, ${relativeY}]`)
}
}
}
</script>
<style lang="less">
.dv-flyline-chart {
display: flex;
flex-direction: column;
background-size: 100% 100%;
polyline {
transition: all 0.3s;
}
text {
text-anchor: middle;
dominant-baseline: middle;
}
}
</style>

View File

@ -1,21 +0,0 @@
<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

@ -1,3 +1,7 @@
import fullScreenContainer from './fullScreenContainer'
import loading from './loading/index.vue'
// border box
import borderBox1 from './borderBox1/index'
import borderBox2 from './borderBox2/index'
import borderBox3 from './borderBox3/index'
@ -5,7 +9,9 @@ import borderBox4 from './borderBox4/index'
import borderBox5 from './borderBox5/index'
import borderBox6 from './borderBox6/index'
import borderBox7 from './borderBox7/index'
import borderBox8 from './borderBox8/index'
// decoration
import decoration1 from './decoration1/index'
import decoration2 from './decoration2/index'
import decoration3 from './decoration3/index'
@ -13,61 +19,51 @@ 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 decoration8 from './decoration8/index'
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'
// charts
import charts from './charts/index.vue'
import numberShow from './numberShow/index.vue'
import percentPond from './percentPond/index.vue'
import percentArc from './percentArc/index.vue'
import activeRingChart from './activeRingChart'
import waterLevelPond from './waterLevelPond/index.vue'
import percentPond from './percentPond/index.vue'
import flylineChart from './flylineChart'
import digitalFlop from './digitalFlop'
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('dvFullScreenContainer', fullScreenContainer)
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('dvLoading', 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)
// border box
Vue.component('dvBorderBox1', borderBox1)
Vue.component('dvBorderBox2', borderBox2)
Vue.component('dvBorderBox3', borderBox3)
Vue.component('dvBorderBox4', borderBox4)
Vue.component('dvBorderBox5', borderBox5)
Vue.component('dvBorderBox6', borderBox6)
Vue.component('dvBorderBox7', borderBox7)
Vue.component('dvBorderBox8', borderBox8)
Vue.component('numberShow', numberShow)
Vue.component('percentPond', percentPond)
Vue.component('percentArc', percentArc)
Vue.component('waterLevelPond', waterLevelPond)
Vue.component('scrollBoard', scrollBoard)
// decoration
Vue.component('dvDecoration1', decoration1)
Vue.component('dvDecoration2', decoration2)
Vue.component('dvDecoration3', decoration3)
Vue.component('dvDecoration4', decoration4)
Vue.component('dvDecoration5', decoration5)
Vue.component('dvDecoration6', decoration6)
Vue.component('dvDecoration7', decoration7)
Vue.component('dvDecoration8', decoration8)
Vue.component('fullScreenContainer', fullScreenContainer)
Vue.component('labelLine', labelLine)
Vue.component('forSlot', forSlot)
// charts
Vue.component('dvCharts', charts)
Vue.component('dvActiveRingChart', activeRingChart)
Vue.component('dvWaterLevelPond', waterLevelPond)
Vue.component('dvPercentPond', percentPond)
Vue.component('dvFlylineChart', flylineChart)
Vue.component('dvDigitalFlop', digitalFlop)
// Vue.component('dvScrollBoard', scrollBoard)
}

View File

@ -1,96 +0,0 @@
<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.

Before

Width:  |  Height:  |  Size: 6.1 KiB

View File

@ -1,6 +1,59 @@
<template>
<div class="loading">
<img class="loading-img" src="./img/loading.png">
<div class="dv-loading">
<svg width="50px" height="50px">
<circle
cx="25"
cy="25"
r="20"
fill="transparent"
stroke-width="3"
stroke-dasharray="31.415, 31.415"
stroke="#02bcfe"
stroke-linecap="round"
>
<animateTransform
attributeName="transform"
type="rotate"
values="0, 25 25;360, 25 25"
dur="1.5s"
repeatCount="indefinite"
/>
<animate
attributeName="stroke"
values="#02bcfe;#3be6cb;#02bcfe"
dur="3s"
repeatCount="indefinite"
/>
</circle>
<circle
cx="25"
cy="25"
r="10"
fill="transparent"
stroke-width="3"
stroke-dasharray="15.7, 15.7"
stroke="#3be6cb"
stroke-linecap="round"
>
<animateTransform
attributeName="transform"
type="rotate"
values="360, 25 25;0, 25 25"
dur="1.5s"
repeatCount="indefinite"
/>
<animate
attributeName="stroke"
values="#3be6cb;#02bcfe;#3be6cb"
dur="3s"
repeatCount="indefinite"
/>
</circle>
</svg>
<div class="loading-tip">
<slot />
</div>
</div>
</template>
@ -11,29 +64,17 @@ export default {
</script>
<style lang="less">
.loading {
position: absolute;
top: 0px;
left: 0px;
.dv-loading {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
.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)
}
.loading-tip {
font-size: 15px;
color: #fff;
}
}
</style>

View File

@ -1,33 +0,0 @@
<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

@ -1,222 +0,0 @@
<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

@ -1,76 +1,246 @@
<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">
<div class="dv-percent-pond" ref="percent-pond">
<svg>
<defs>
<linearGradient id="linear">
<linearGradient :id="gradientId1" x1="0%" y1="0%" x2="100%" y2="0%">
<stop v-for="lc in linearGradient" :key="lc[0]"
:offset="lc[0]"
:stop-color="lc[1]" />
</linearGradient>
<linearGradient :id="gradientId2" x1="0%" y1="0%" :x2="gradient2XPos" y2="0%">
<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}`" />
<rect
:x="mergedConfig ? mergedConfig.borderWidth / 2 : '0'"
:y="mergedConfig ? mergedConfig.borderWidth / 2 : '0'"
:rx="mergedConfig ? mergedConfig.borderRadius : '0'"
:ry="mergedConfig ? mergedConfig.borderRadius : '0'"
fill="transparent"
:stroke-width="mergedConfig ? mergedConfig.borderWidth : '0'"
:stroke="`url(#${gradientId1})`"
:width="rectWidth"
:height="rectHeight"
/>
<polyline
:stroke-width="polylineWidth"
:stroke-dasharray="mergedConfig ? mergedConfig.lineDash.join(',') : '0'"
:stroke="`url(#${polylineGradient})`"
:points="points"
/>
<text
:stroke="mergedConfig ? mergedConfig.textColor : '#fff'"
:fill="mergedConfig ? mergedConfig.textColor : '#fff'"
:x="width / 2"
:y="height / 2"
>
{{ details }}
</text>
</svg>
</div>
<div class="p-decoration-box" />
</div>
</div>
</template>
<script>
import { deepMerge } from '@jiaminghi/charts/lib/util/index'
import { deepClone } from '@jiaminghi/c-render/lib/plugin/util'
export default {
name: 'PercentPond',
props: ['percent', 'colors'],
props: {
config: {
type: Object,
default: () => ({})
}
},
data () {
return {
ref: `percent-pond-${(new Date()).getTime()}`,
gradientId1: `percent-pond-gradientId1-${(new Date()).getTime()}`,
gradientId2: `percent-pond-gradientId2-${(new Date()).getTime()}`,
width: 0,
height: 0,
defaultColor: ['#00BAFF', '#3DE7C9'],
defaultConfig: {
/**
* @description Value
* @type {Number}
* @default value = 0
*/
value: 0,
/**
* @description Colors (Hex|rgb|rgba)
* @type {Array<String>}
* @default colors = ['#00BAFF', '#3DE7C9']
*/
colors: ['#3DE7C9', '#00BAFF'],
/**
* @description Border width
* @type {Number}
* @default borderWidth = 3
*/
borderWidth: 3,
/**
* @description Gap between border and pond
* @type {Number}
* @default borderGap = 3
*/
borderGap: 3,
/**
* @description Line dash
* @type {Array<Number>}
* @default lineDash = [5, 1]
*/
lineDash: [5, 1],
/**
* @description Text color
* @type {String}
* @default textColor = '#fff'
*/
textColor: '#fff',
/**
* @description Border radius
* @type {Number}
* @default borderRadius = 5
*/
borderRadius: 5,
/**
* @description Local Gradient
* @type {Boolean}
* @default localGradient = false
* @example localGradient = false | true
*/
localGradient: false,
/**
* @description Formatter
* @type {String}
* @default formatter = '{value}%'
*/
formatter: '{value}%'
},
linearGradient: []
mergedConfig: null
}
},
watch: {
colors () {
const { calcLinearColor } = this
computed: {
rectWidth () {
const { mergedConfig, width } = this
calcLinearColor()
}
if (!mergedConfig) return 0
const { borderWidth } = mergedConfig
return width - borderWidth
},
methods: {
init () {
const { $nextTick, $refs, ref, calcLinearColor } = this
rectHeight () {
const { mergedConfig, height } = this
$nextTick(e => {
this.width = $refs[ref].clientWidth
this.height = $refs[ref].clientHeight
})
if (!mergedConfig) return 0
calcLinearColor()
const { borderWidth } = mergedConfig
return height - borderWidth
},
calcLinearColor () {
const { colors, defaultColor } = this
points () {
const { mergedConfig, width, height } = this
let trueColor = colors || defaultColor
const halfHeight = height / 2
typeof trueColor === 'string' && (trueColor = [trueColor, trueColor])
if (!mergedConfig) return `0, ${halfHeight} 0, ${halfHeight}`
const colorNum = trueColor.length
const { borderWidth, borderGap, value } = mergedConfig
const polylineLength = (width - (borderWidth + borderGap) * 2) / 100 * value
return `
${borderWidth + borderGap}, ${halfHeight}
${borderWidth + borderGap + polylineLength}, ${halfHeight + 0.001}
`
},
polylineWidth () {
const { mergedConfig, height } = this
if (!mergedConfig) return 0
const { borderWidth, borderGap } = mergedConfig
return height - (borderWidth + borderGap) * 2
},
linearGradient () {
const { mergedConfig } = this
if (!mergedConfig) return []
const { colors } = mergedConfig
const colorNum = colors.length
const colorOffsetGap = 100 / (colorNum - 1)
this.linearGradient = trueColor.map((c, i) => [colorOffsetGap * i, c])
return colors.map((c, i) => [colorOffsetGap * i, c])
},
polylineGradient () {
const { gradientId1, gradientId2, mergedConfig } = this
if (!mergedConfig) return gradientId2
if (mergedConfig.localGradient) return gradientId1
return gradientId2
},
gradient2XPos () {
const { mergedConfig } = this
if (!mergedConfig) return '100%'
const { value } = mergedConfig
return `${200 - value}%`
},
details () {
const { mergedConfig } = this
if (!mergedConfig) return ''
const { value, formatter } = mergedConfig
return formatter.replace('{value}', value)
}
},
watch: {
config () {
const { mergeConfig } = this
mergeConfig()
}
},
methods: {
async init () {
const { initWH, config, mergeConfig } = this
await initWH()
if (!config) return
mergeConfig()
},
async initWH () {
const { $nextTick, $refs } = this
await $nextTick()
const dom = $refs['percent-pond']
this.width = dom.clientWidth
this.height = dom.clientHeight
},
mergeConfig () {
let { config, defaultConfig } = this
this.mergedConfig = deepMerge(deepClone(defaultConfig, true), config || {})
}
},
mounted () {
@ -82,58 +252,19 @@ export default {
</script>
<style lang="less">
.percent-pond {
.dv-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;
}
transition: all 0.3s;
}
text {
font-size: 25px;
font-weight: bold;
text-anchor: middle;
dominant-baseline: middle;
}
}
</style>

View File

@ -1,175 +0,0 @@
<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

@ -1,281 +0,0 @@
<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

@ -1,558 +0,0 @@
<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

@ -1,434 +0,0 @@
<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

@ -1,272 +1,306 @@
<template>
<div class="water-level-pond">
<loading v-if="!status" />
<svg class="svg-container">
<div class="dv-water-pond-level">
<svg v-if="render">
<defs>
<linearGradient :id="id" x1="0%" y1="100%" x2="0%" y2="0%">
<stop v-for="lc in linearGradient" :key="lc[0]"
<linearGradient :id="gradientId" x1="0%" y1="0%" x2="0%" y2="100%">
<stop v-for="lc in svgBorderGradient" :key="lc[0]"
:offset="lc[0]"
:stop-color="lc[1]" />
</linearGradient>
</defs>
<text :stroke="`url(#${id})`"
:fill="`url(#${id})`"
:x="centerPos[0] + 8"
:y="centerPos[1] + 8">
{{ (level && Math.max(...level)) || 0 }}%
<text
v-if="render"
:stroke="`url(#${gradientId})`"
:fill="`url(#${gradientId})`"
:x="render.area[0] / 2 + 8"
:y="render.area[1] / 2 + 8"
>
{{ details }}
</text>
<ellipse v-if="!type || type === 'circle'"
:cx="centerPos[0] + 8"
:cy="centerPos[1] + 8"
:rx="canvasWH[0] / 2 + 5"
:ry="canvasWH[1] / 2 + 5"
:stroke="`url(#${id})`" />
<ellipse v-if="!shape || shape === 'round'"
:cx="render.area[0] / 2 + 8"
:cy="render.area[1] / 2 + 8"
:rx="render.area[0] / 2 + 5"
:ry="render.area[1] / 2 + 5"
:stroke="`url(#${gradientId})`" />
<rect v-else
x="2" y="2"
:rx="type === 'roundRect' && 10" :ry="type === 'roundRect' && 10"
:width="canvasWH[0] + 12"
:height="canvasWH[1] + 12"
:stroke="`url(#${id})`" />
:rx="shape === 'roundRect' ? 10 : 0"
:ry="shape === 'roundRect' ? 10 : 0"
:width="render.area[0] + 12"
:height="render.area[1] + 12"
:stroke="`url(#${gradientId})`" />
</svg>
<canvas :ref="ref" :style="`border-radius: ${radius};`" />
<canvas ref="water-pond-level" :style="`border-radius: ${radius};`" />
</div>
</template>
<script>
import canvasMixin from '../../mixins/canvasMixin.js'
import { deepMerge } from '@jiaminghi/charts/lib/util/index'
import { deepClone } from '@jiaminghi/c-render/lib/plugin/util'
import CRender from '@jiaminghi/c-render'
export default {
name: 'WaterLevelPond',
mixins: [canvasMixin],
name: 'waterLevelPond',
props: {
config: Object,
default: () => ({})
},
data () {
return {
ref: `water-level-pond-${(new Date()).getTime()}`,
gradientId: `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: ''
}
defaultConfig: {
/**
* @description Data
* @type {Array<Number>}
* @default data = []
* @example data = [60, 40]
*/
data: [],
/**
* @description Shape of wanter level pond
* @type {String}
* @default shape = 'rect'
* @example shape = 'rect' | 'roundRect' | 'round'
*/
shape: 'rect',
/**
* @description Water wave number
* @type {Number}
* @default waveNum = 3
*/
waveNum: 3,
/**
* @description Water wave height (px)
* @type {Number}
* @default waveHeight = 40
*/
waveHeight: 40,
/**
* @description Wave opacity
* @type {Number}
* @default waveOpacity = 0.4
*/
waveOpacity: 0.4,
/**
* @description Colors (Hex|rgb|rgba)
* @type {Array<String>}
* @default colors = ['#00BAFF', '#3DE7C9']
*/
colors: ['#3DE7C9', '#00BAFF'],
/**
* @description Formatter
* @type {String}
* @default formatter = '{value}%'
*/
formatter: '{value}%'
},
props: ['level', 'type', 'colors', 'waveNum', 'waveHeight', 'borderColor', 'noGradient'],
watch: {
level () {
const { checkData, draw } = this
checkData() && draw()
mergedConfig: {},
render: null,
svgBorderGradient: [],
details: '',
waves: [],
animation: false
}
},
computed: {
radius () {
const { type } = this
const { shape } = this.mergedConfig
if (type === 'circle') return '50%'
if (shape === 'round') return '50%'
if (type === 'rect') return '0'
if (shape === 'rect') return '0'
if (type === 'roundRect') return '10px'
if (shape === 'roundRect') return '10px'
return '50%'
return '0'
},
shape () {
const { shape } = this.mergedConfig
if (!shape) return 'rect'
return shape
}
},
watch: {
config () {
const { calcData, render } = this
render.delAllGraph()
this.waves = []
setTimeout(calcData, 0)
}
},
methods: {
async init () {
const { initCanvas, checkData, draw } = this
init () {
const { initRender, config, calcData } = this
await initCanvas()
initRender()
checkData() && draw()
if (!config) return
calcData()
},
checkData () {
const { level } = this
initRender () {
const { $refs } = this
this.status = false
if (!level || !level.length) return false
this.status = true
return true
this.render = new CRender($refs['water-pond-level'])
},
draw () {
const { stopAnimation, clearCanvas } = this
calcData () {
const { mergeConfig, calcSvgBorderGradient, calcDetails } = this
stopAnimation()
mergeConfig()
clearCanvas()
calcSvgBorderGradient()
const { initColor, calcBorderLinearColor, calcWaveData } = this
calcDetails()
initColor()
const { addWave, animationWave } = this
calcBorderLinearColor()
addWave()
calcWaveData()
const { calcBottomPoints, calcOverXPos, drawWaveAnimation } = this
calcBottomPoints()
calcOverXPos()
drawWaveAnimation()
animationWave()
},
initColor () {
const { colors, defaultColor } = this
mergeConfig () {
const { config, defaultConfig } = this
this.drawColor = colors || defaultColor
this.mergedConfig = deepMerge(deepClone(defaultConfig, true), config)
},
calcBorderLinearColor () {
const { colors, defaultColor, borderColor } = this
calcSvgBorderGradient () {
const { colors } = this.mergedConfig
let trueColor = borderColor || colors || defaultColor
typeof trueColor === 'string' && (trueColor = [trueColor, trueColor])
const colorNum = trueColor.length
const colorNum = colors.length
const colorOffsetGap = 100 / (colorNum - 1)
this.linearGradient = trueColor.map((c, i) => [colorOffsetGap * i, c])
this.svgBorderGradient = colors.map((c, i) => [colorOffsetGap * i, c])
},
calcWaveData () {
const { waveNum, waveHeight, defaultWaveNum, defaultWaveHeight, canvasWH } = this
calcDetails () {
const { data, formatter } = this.mergedConfig
const waveTrueNum = this.waveTrueNum = waveNum || defaultWaveNum
if (!data.length) {
this.details = ''
const waveTrueHeight = this.waveTrueHeight = (waveHeight || defaultWaveHeight) * canvasWH[1]
return
}
const waveWidth = this.waveTrueWidth = canvasWH[0] / waveTrueNum
const maxValue = Math.max(...data)
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()
this.details = formatter.replace('{value}', maxValue)
},
addWavePoint () {
const { wavePoints, waveTrueWidth } = this
addWave () {
const { render, getWaveShapes, getWaveStyle, drawed } = this
const addPoint = [wavePoints[1][0] - waveTrueWidth, wavePoints[1][1]]
const shapes = getWaveShapes()
const style = getWaveStyle()
return wavePoints.unshift(addPoint)
this.waves = shapes.map(shape => render.add({
name: 'smoothline',
animationFrame: 300,
shape,
style,
drawed
}))
},
calcBottomPoints () {
const { canvasWH } = this
getWaveShapes () {
const { mergedConfig, render, mergeOffset } = this
this.bottomPoints = [
[...canvasWH],
[0, canvasWH[1]]
]
const { waveNum, waveHeight, data } = mergedConfig
const [w, h] = render.area
const pointsNum = waveNum * 4 + 4
const pointXGap = w / waveNum / 2
return data.map(v => {
let points = new Array(pointsNum).fill(0).map((foo, j) => {
const x = w - pointXGap * j
const startY = (1 - v / 100) * h
const y = j % 2 === 0 ? startY : startY - waveHeight
return [x, y]
})
points = points.map(p => mergeOffset(p, [pointXGap * 2, 0]))
return { points }
})
},
calcOverXPos () {
const { canvasWH: [width], waveTrueWidth } = this
this.overXPos = width + waveTrueWidth
mergeOffset ([x, y], [ox, oy]) {
return [x + ox, y + oy]
},
drawWaveAnimation () {
const { clearCanvas, drawWaveAnimation } = this
getWaveStyle () {
const { render, mergedConfig } = this
clearCanvas()
const h = render.area[1]
const { getCurrentPoints, drawCurrentWave, calcNextFramePoints } = this
getCurrentPoints()
drawCurrentWave()
calcNextFramePoints()
this.animationHandler = requestAnimationFrame(drawWaveAnimation)
return {
gradientColor: mergedConfig.colors,
gradientType: 'linear',
gradientParams: [0, 0, 0, h],
gradientWith: 'fill',
opacity: mergedConfig.waveOpacity,
translate: [0, 0]
}
},
getCurrentPoints () {
const { level, wavePoints, canvasWH: [, height] } = this
drawed ({ shape: { points } }, { ctx, area }) {
const firstPoint = points[0]
const lastPoint = points.slice(-1)[0]
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 [w, h] = area
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.lineTo(lastPoint[0], h)
ctx.lineTo(firstPoint[0], h)
ctx.closePath()
multipleColor && noGradient && (ctx.fillStyle = drawColor[i % colorNum])
ctx.fill()
},
async animationWave (repeat = 1) {
const { waves, render, animation } = this
if (animation) return
this.animation = true
const w = render.area[0]
waves.forEach(graph => {
const reset = repeat % 2 === 0
graph.attr('style', { translate: [0, 0] })
graph.animation('style', {
translate: [w, 0]
}, true)
})
},
calcNextFramePoints () {
const { wavePoints, waveAdded, addWavePoint, overXPos } = this
const addedWavePoints = wavePoints.map(([x, y]) => [x + waveAdded, y])
await render.launchAnimation()
const lastPointIndex = addedWavePoints.length - 1
this.animation = false
let addStatus = false
if (!render.graphs.length) return
addedWavePoints[lastPointIndex][0] > overXPos &&
addedWavePoints.pop() && (addStatus = true)
this.wavePoints = addedWavePoints
addStatus && addWavePoint()
},
stopAnimation () {
const { animationHandler } = this
animationHandler && cancelAnimationFrame(animationHandler)
this.animationWave(repeat + 1)
}
},
mounted () {
@ -274,27 +308,21 @@ export default {
init()
},
destroyed () {
const { stopAnimation } = this
beforeDestroy () {
const { calcData, render } = this
stopAnimation()
render.delAllGraph()
this.waves = []
}
}
</script>
<style lang="less">
.water-level-pond {
.dv-water-pond-level {
position: relative;
.percent-text {
position: absolute;
left: 50%;
top: 50%;
font-weight: bold;
transform: translate(-50%, -50%);
}
.svg-container {
svg {
position: absolute;
width: 100%;
height: 100%;
@ -320,7 +348,6 @@ export default {
width: calc(~"100% - 16px");
height: calc(~"100% - 16px");
box-sizing: border-box;
border-radius: 50%;
}
}
</style>

View File

@ -1 +0,0 @@
export default {}

View File

@ -1,8 +1,5 @@
import components from './components/index'
import plugins from './plugins'
export default function (Vue) {
components(Vue)
plugins(Vue)
}

View File

@ -12,17 +12,19 @@ export default {
}
},
methods: {
async init () {
const { initWH, getDebounceInitWHFun, bindDomResizeCallback } = this
async autoResizeMixinInit () {
const { initWH, getDebounceInitWHFun, bindDomResizeCallback, afterAutoResizeMixinInit } = this
await initWH()
getDebounceInitWHFun()
bindDomResizeCallback()
if (typeof afterAutoResizeMixinInit === 'function') afterAutoResizeMixinInit()
},
initWH () {
const { $nextTick, $refs, ref } = this
const { $nextTick, $refs, ref, onResize } = this
return new Promise(resolve => {
$nextTick(e => {
@ -31,6 +33,8 @@ export default {
this.width = dom.clientWidth
this.height = dom.clientHeight
if (typeof onResize === 'function') onResize()
resolve()
})
})
@ -58,9 +62,9 @@ export default {
}
},
mounted () {
const { init } = this
const { autoResizeMixinInit } = this
init()
autoResizeMixinInit()
},
beforeDestroyed () {
const { unbindDomResizeCallback } = this

View File

@ -1,672 +0,0 @@
export default {
data () {
return {
// config able
defaultAxisFontSize: 10,
defaultAxisFontFamily: 'Arial',
defaultAxisLineColor: '#666',
defaultGridLineColor: '#666',
defaultAxisTagColor: '#666',
defaultAxisUnitColor: '#666',
defaultGridLineDash: [2, 2],
defaultNoAxisLine: false,
defaultNoAxisTag: false,
defaultXAxisOffset: 20,
defaultAxisLineTagGap: 5,
// after merge
axisFontSize: 0,
axisFontFamily: '',
axisLineColor: '',
gridLineColor: '',
axisTagColor: '',
axisUnitColor: '',
gridLineDash: [],
noAxisLine: '',
noAxisTag: '',
// calc data
valueMaxMin: [],
agValueMaxMin: [],
valueAxisMaxMin: [],
agValueAxisMaxMin: [],
valueAxisTag: [],
agValueAxisTag: [],
labelAxisTag: [],
agLabelAxisTag: [],
xAxisTagBA: ['', ''],
agXAxisTagBA: ['', ''],
yAxisTagBA: ['', ''],
agYAxisTagBA: ['', ''],
addBAValueAxisTag: [],
addBAAGValueAxisTag: [],
addBALabelAxisTag: [],
addBAAGLabelAxisTag: [],
axisUnit: [],
axisOffset: [],
axisOriginPos: [],
axisWH: [],
axisAnglePos: {},
valueAxisTagPos: [],
agValueAxisTagPos: [],
labelAxisTagPos: [],
agLabelAxisTagPos: [],
tagAlign: {}
}
},
methods: {
initAxis () {
const { calcValuesMaxMin, calcValueAxisData, calcLabelAxisData } = this
calcValuesMaxMin()
calcValueAxisData()
calcLabelAxisData()
const { calcTagBA, calcAddBATag, calcAxisUnit } = this
calcTagBA()
calcAddBATag()
calcAxisUnit()
const { calcAxisFontData, calcAxisColor, calcGridLineDash } = this
calcAxisFontData()
calcAxisColor()
calcGridLineDash()
const { calcAxisLineTagStatus, calcAxisOffset, calcAxisAreaData } = this
calcAxisLineTagStatus()
calcAxisOffset()
calcAxisAreaData()
const { calcAxisAnglePos, calcValueAxisTagPos, calcLabelAxisTagPos } = this
calcAxisAnglePos()
calcValueAxisTagPos()
calcLabelAxisTagPos()
const { calcTagAlign } = this
calcTagAlign()
},
calcValuesMaxMin () {
const { data: { series }, calcValueMaxMin } = this
const valueSeries = series.filter(({ againstAxis }) => !againstAxis)
if (valueSeries.length) this.valueMaxMin = calcValueMaxMin(valueSeries)
const agValueSeries = series.filter(({ againstAxis }) => againstAxis)
if (agValueSeries.length) this.agValueMaxMin = calcValueMaxMin(agValueSeries)
},
calcValueMaxMin (series) {
const { mulValueAdd, calcMulValueAdd, getArrayMax, getArrayMin } = this
let valueSeries = series.map(({ value }) => value)
const min = getArrayMin(valueSeries)
mulValueAdd && (valueSeries = calcMulValueAdd(valueSeries))
const max = getArrayMax(valueSeries)
return [max, min]
},
calcMulValueAdd (values) {
const { multipleSum, filterNull } = this
return values.map(series =>
filterNull(series).map(n =>
n instanceof Array ? multipleSum(...filterNull(n)) : n))
},
calcValueAxisData () {
const { horizon, data: { x, ax, y, ay }, calcValueAxisTag } = this
const { valueMaxMin, agValueMaxMin, getValueAxisMaxMin } = this
const valueAxis = horizon ? [x, ax] : [y, ay]
if (valueMaxMin.length) {
const valueAxisTag = this.valueAxisTag = calcValueAxisTag(valueMaxMin, valueAxis[0])
this.valueAxisMaxMin = getValueAxisMaxMin(valueAxisTag)
}
if (agValueMaxMin.length) {
const agValueAxisTag = this.agValueAxisTag = calcValueAxisTag(agValueMaxMin, valueAxis[1])
this.agValueAxisMaxMin = getValueAxisMaxMin(agValueAxisTag)
}
},
calcValueAxisTag ([vmax, vmin], { max, min, num, fixed, tags } = {}) {
if (tags) return tags
let [trueMax, trueMin] = [max, min]
if (vmax === 0 && vmin === 0) vmax = 8
const thirdValueMinus = parseInt((vmax - vmin) / 3)
!max && (max !== 0) && (trueMax = vmax + thirdValueMinus)
!min && (min !== 0) && (trueMin = vmin - thirdValueMinus)
const trueMinus = trueMax - trueMin
!num && trueMinus < 9 && (num = Math.ceil(trueMinus) + 1)
!num && (num = 10)
const valueGap = trueMinus / (num - 1)
return Array(num).fill(0).map((t, i) =>
(trueMin + i * valueGap).toFixed(fixed))
},
getValueAxisMaxMin (valueTag) {
const lastIndex = valueTag.length - 1
return [
parseFloat(valueTag[lastIndex]),
parseFloat(valueTag[0])
]
},
calcLabelAxisData () {
const { horizon, data: { x, ax, y, ay } } = this
const labelAxis = horizon ? [y, ay] : [x, ax]
if (labelAxis[0] && labelAxis[0].tags) this.labelAxisTag = labelAxis[0].tags
if (labelAxis[1] && labelAxis[1].tags) this.agLabelAxisTag = labelAxis[1].tags
},
calcTagBA () {
const { data: { x, ax, y, ay } } = this
if (x && x.tagBefore) this.xAxisTagBA[0] = x.tagBefore
if (ax && ax.tagBefore) this.agXAxisTagBA[0] = ax.tagBefore
if (y && y.tagBefore) this.yAxisTagBA[0] = y.tagBefore
if (ay && ay.tagBefore) this.agYAxisTagBA[0] = ay.tagBefore
if (x && x.tagAfter) this.xAxisTagBA[1] = x.tagAfter
if (ax && ax.tagAfter) this.agXAxisTagBA[1] = ax.tagAfter
if (y && y.tagAfter) this.yAxisTagBA[1] = y.tagAfter
if (ay && ay.tagAfter) this.agYAxisTagBA[1] = ay.tagAfter
},
calcAddBATag () {
const { xAxisTagBA, agXAxisTagBA, yAxisTagBA, agYAxisTagBA } = this
const { valueAxisTag, agValueAxisTag, labelAxisTag, agLabelAxisTag } = this
const { horizon, addBATag } = this
const valueTagBA = horizon ? [xAxisTagBA, agXAxisTagBA] : [yAxisTagBA, agYAxisTagBA]
const labelTagBA = horizon ? [yAxisTagBA, agYAxisTagBA] : [xAxisTagBA, agXAxisTagBA]
if (valueAxisTag.length) this.addBAValueAxisTag = addBATag(valueAxisTag, valueTagBA[0])
if (agValueAxisTag.length) this.addBAAGValueAxisTag = addBATag(agValueAxisTag, valueTagBA[1])
if (labelAxisTag.length) this.addBALabelAxisTag = addBATag(labelAxisTag, labelTagBA[0])
if (agLabelAxisTag.length) this.addBAAGLabelAxisTag = addBATag(agLabelAxisTag, labelTagBA[1])
},
addBATag (tags, ba) {
return tags.map(tag => tag ? `${ba[0]}${tag}${ba[1]}` : tag)
},
calcAxisUnit () {
const { data: { x, ax, y, ay } } = this
if (x && x.unit) this.axisUnit[0] = x.unit
if (ax && ax.unit) this.axisUnit[1] = ax.unit
if (y && y.unit) this.axisUnit[2] = y.unit
if (ay && ay.unit) this.axisUnit[3] = ay.unit
},
calcAxisFontData () {
const { defaultAxisFontSize, defaultAxisFontFamily, data } = this
const { fontSize, fontFamily, axisFontSize, axisFontFamily }= data
this.axisFontSize = axisFontSize || fontSize || defaultAxisFontSize
this.axisFontFamily = axisFontFamily || fontFamily || defaultAxisFontFamily
},
calcAxisColor () {
const { data, defaultAxisTagColor, defaultAxisLineColor, defaultGridLineColor } = this
const { axisTagColor, axisLineColor, gridLineColor } = data
this.axisTagColor = axisTagColor || defaultAxisTagColor
this.axisLineColor = axisLineColor || defaultAxisLineColor
this.gridLineColor = gridLineColor || defaultGridLineColor
},
calcGridLineDash () {
const { data, defaultGridLineDash } = this
const { gridLineDash } = data
if (gridLineDash instanceof Array) {
this.gridLineDash = gridLineDash
} else if (gridLineDash) {
this.gridLineDash = defaultGridLineDash
} else {
this.gridLineDash = [10, 0]
}
},
calcAxisLineTagStatus () {
const { defaultNoAxisLine, defaultNoAxisTag, data } = this
const { noAxisLine, noAxisTag } = data
this.noAxisLine = noAxisLine || defaultNoAxisLine
this.noAxisTag = noAxisTag || defaultNoAxisTag
},
calcAxisOffset () {
const { horizon, axisUnit, defaultXAxisOffset } = this
const { addBAValueAxisTag, addBAAGValueAxisTag, addBALabelAxisTag, addBAAGLabelAxisTag } = this
const { axisFontSize, axisFontFamily, defaultAxisLineTagGap } = this
const { data: { x, ax, y, ay } } = this
const { ctx, canvas: { getTextsWidth }, boundaryGap } = this
ctx.font = `${axisFontSize}px ${axisFontFamily}`
this.axisOffset[0] = (ax && ax.offset) || defaultXAxisOffset
this.axisOffset[2] = (x && x.offset) || defaultXAxisOffset
const horizonAxisTags = horizon
? [addBALabelAxisTag, addBAAGLabelAxisTag]
: [addBAValueAxisTag, addBAAGValueAxisTag]
this.axisOffset[3] = (y && y.offset) ||
Math.max(...getTextsWidth(ctx, [axisUnit[2] || '']),
...getTextsWidth(ctx, horizonAxisTags[0].length ? horizonAxisTags[0] : 0)) + defaultAxisLineTagGap
// axis offset 1
const xAxisTags = horizon ? addBAValueAxisTag : addBALabelAxisTag
let xAxisTagsHalfWidth = 0
xAxisTags.length && (xAxisTagsHalfWidth = ctx.measureText(xAxisTags.length - 1).width / 2)
let rightOffset = Math.max(...getTextsWidth(ctx, [axisUnit[3] || '']),
...getTextsWidth(ctx, [axisUnit[0] || '']),
...getTextsWidth(ctx, horizonAxisTags[1].length ? horizonAxisTags[1] : [''])) + defaultAxisLineTagGap
;(!boundaryGap || horizon) && (rightOffset += (xAxisTagsHalfWidth + 5))
this.axisOffset[1] = (ay && ay.offset) || rightOffset
if (y && y.noTag) this.axisOffset[3] = 1
if (ay && ay.noTag) this.axisOffset[1] = 1
},
calcAxisAreaData () {
const { canvasWH, axisOffset, axisWH, axisOriginPos } = this
axisWH[0] = canvasWH[0] - axisOffset[1] - axisOffset[3]
axisWH[1] = canvasWH[1] - axisOffset[0] - axisOffset[2]
axisOriginPos[0] = axisOffset[3]
axisOriginPos[1] = axisWH[1] + axisOffset[0]
},
calcAxisAnglePos () {
const { axisWH, axisOriginPos, axisAnglePos } = this
axisAnglePos.leftTop = [axisOriginPos[0], axisOriginPos[1] - axisWH[1]]
axisAnglePos.rightTop = [axisOriginPos[0] + axisWH[0], axisOriginPos[1] - axisWH[1]]
axisAnglePos.leftBottom = axisOriginPos
axisAnglePos.rightBottom = [axisOriginPos[0] + axisWH[0], axisOriginPos[1]]
},
calcValueAxisTagPos () {
const { horizon, axisOriginPos, axisAnglePos } = this
const { valueAxisTag, agValueAxisTag, getValueAxisTagPos } = this
if (valueAxisTag) this.valueAxisTagPos = getValueAxisTagPos(valueAxisTag, axisOriginPos)
const basePoint = horizon ? axisAnglePos.leftTop : axisAnglePos.rightBottom
if (agValueAxisTag) this.agValueAxisTagPos = getValueAxisTagPos(agValueAxisTag, basePoint)
},
getValueAxisTagPos (tags, [x, y]) {
const { horizon, axisWH } = this
const tagsNum = tags.length
const areaLength = horizon ? axisWH[0] : axisWH[1]
const tagGap = areaLength / (tagsNum - 1)
return new Array(tagsNum).fill(0).map((t, i) =>
horizon ? [x + tagGap * i, y] : [x, y - tagGap * i])
},
calcLabelAxisTagPos () {
const { horizon, getLabelAxisTagPos, axisAnglePos } = this
const { labelAxisTag, agLabelAxisTag, axisOriginPos } = this
if (labelAxisTag.length) this.labelAxisTagPos = getLabelAxisTagPos(labelAxisTag, axisOriginPos)
const basePoint = horizon ? axisAnglePos.rightBottom : axisAnglePos.leftTop
if (agLabelAxisTag.length) this.agLabelAxisTagPos = getLabelAxisTagPos(agLabelAxisTag, basePoint)
},
getLabelAxisTagPos (tags, [x, y]) {
const { horizon, axisWH, boundaryGap } = this
const tagsNum = tags.length
const areaLength = horizon ? axisWH[1] : axisWH[0]
let gapColumnNum = boundaryGap ? tagsNum : tagsNum - 1
gapColumnNum === 0 && (gapColumnNum = 1)
const tagGap = areaLength / gapColumnNum
const halfGap = tagGap / 2
const tempPos = new Array(tagsNum).fill(0)
if (boundaryGap) {
return tempPos.map((t, i) =>
horizon ? [x, y - (tagGap * i) - halfGap] : [x + (tagGap * i) + halfGap, y])
}
if (!boundaryGap) {
return tempPos.map((t, i) =>
horizon ? [x, y + tagGap * i] : [x + tagGap * i, y])
}
},
calcTagAlign () {
const { tagAlign } = this
tagAlign.x = ['center', 'top']
tagAlign.y = ['right', 'middle']
tagAlign.ax = ['center', 'bottom']
tagAlign.ay = ['left', 'middle']
},
drawAxis () {
const { drawAxisLine, drawAxisTag, drawAxisUnit } = this
drawAxisLine()
drawAxisTag()
drawAxisUnit()
const { drawAxisGrid } = this
drawAxisGrid()
},
drawAxisLine () {
const { noAxisLine } = this
if (noAxisLine) return
const { ctx, horizon, axisOriginPos, axisAnglePos } = this
const { axisLineColor, agValueAxisTag, agLabelAxisTag } = this
const { data: { x, ax, y, ay } } = this
ctx.lineWidth = 1
if (!x || !x.noAxisLine) {
ctx.strokeStyle = (x && x.axisLineColor) || axisLineColor
ctx.beginPath()
ctx.moveTo(...axisOriginPos)
ctx.lineTo(...axisAnglePos.rightBottom)
ctx.stroke()
}
if (!y || !y.noAxisLine) {
ctx.strokeStyle = (y && y.axisLineColor) || axisLineColor
ctx.beginPath()
ctx.moveTo(...axisOriginPos)
ctx.lineTo(...axisAnglePos.leftTop)
ctx.stroke()
}
const agValueAxis = horizon ? ay : ax
if (agValueAxisTag.length && (!agValueAxis || !agValueAxis.noAxisLine)) {
ctx.strokeStyle = (agValueAxis && agValueAxis.axisLineColor) || axisLineColor
ctx.beginPath()
ctx.moveTo(...(horizon ? axisAnglePos.leftTop : axisAnglePos.rightTop))
ctx.lineTo(...(horizon ? axisAnglePos.rightTop : axisAnglePos.rightBottom))
ctx.stroke()
}
const agLebalAxis = horizon ? ax : ay
if (agLabelAxisTag.length && (!agLebalAxis || !agLebalAxis.noAxisLine)) {
ctx.strokeStyle = (agLebalAxis && agLebalAxis.axisLineColor) || axisLineColor
ctx.beginPath()
ctx.moveTo(...(horizon ? axisAnglePos.rightTop : axisAnglePos.leftTop))
ctx.lineTo(...(horizon ? axisAnglePos.rightBottom : axisAnglePos.rightTop))
ctx.stroke()
}
},
drawAxisTag () {
const { noAxisTag } = this
if (noAxisTag) return
const { horizon, tagAlign, defaultAxisLineTagGap: offset } = this
const { data: { x, ax, y, ay }, drawAxisSeriesTag } = this
const xAxis = horizon ? ['addBAValueAxisTag', 'valueAxisTagPos'] : ['addBALabelAxisTag', 'labelAxisTagPos']
const yAxis = horizon ? ['addBALabelAxisTag', 'labelAxisTagPos'] : ['addBAValueAxisTag', 'valueAxisTagPos']
const agXAxis = horizon ? ['addBAAGValueAxisTag', 'agValueAxisTagPos'] : ['addBAAGLabelAxisTag', 'agLabelAxisTagPos']
const agYAxis = horizon ? ['addBAAGLabelAxisTag', 'agLabelAxisTagPos'] : ['addBAAGValueAxisTag', 'agValueAxisTagPos']
if (!x || !x.noAxisTag) drawAxisSeriesTag(...xAxis.map(td => this[td]), x, tagAlign.x, [0, offset], x && x.rotate)
if (!y || !y.noAxisTag) drawAxisSeriesTag(...yAxis.map(td => this[td]), y, tagAlign.y, [-offset, 0])
if (!ax || !ax.noAxisTag) drawAxisSeriesTag(...agXAxis.map(td => this[td]), ax, tagAlign.ax, [0, -offset])
if (!ay || !ay.noAxisTag) drawAxisSeriesTag(...agYAxis.map(td => this[td]), ay, tagAlign.ay, [offset, 0])
},
drawAxisSeriesTag (tags, tagPos, { fontSize, fontFamily, tagColor } = {}, align, offset, rotate = false) {
const { ctx, axisFontSize, axisFontFamily, axisTagColor, drawColors } = this
let color = tagColor || axisTagColor
color === 'colors' && (color = drawColors)
const colorNum = color.length
const mulColor = color instanceof Array
ctx.font = `${fontSize || axisFontSize}px ${fontFamily || axisFontFamily}`
!mulColor && (ctx.fillStyle = color)
ctx.textAlign = align[0]
ctx.textBaseline = align[1]
tags.forEach((tag, i) => {
if (!tag && tag !== 0) return
const currentPos = [tagPos[i][0] + offset[0], tagPos[i][1] + offset[1]]
mulColor && (ctx.fillStyle = color[i % colorNum])
if (rotate) {
ctx.textAlign = 'left'
ctx.textBaseline = 'top'
ctx.save()
ctx.translate(...currentPos)
ctx.rotate(rotate * Math.PI / 180)
}
ctx.fillText(tag, ...(rotate ? [0, 0] : currentPos))
if (rotate) ctx.restore()
})
},
drawAxisUnit () {
const { noAxisTag } = this
if (noAxisTag) return
const { axisOriginPos, canvasWH, drawUnit, defaultAxisLineTagGap } = this
const { data: { x, ax, y, ay }, axisAnglePos } = this
if (x) {
const pos = [canvasWH[0], axisOriginPos[1] + defaultAxisLineTagGap]
drawUnit(x, pos, ['right', 'top'])
}
if (ax) {
const pos = [canvasWH[0], axisAnglePos.rightTop[1] - defaultAxisLineTagGap]
drawUnit(ax, pos, ['right', 'bottom'])
}
if (y) {
const pos = [axisOriginPos[0] - defaultAxisLineTagGap, 0]
drawUnit(y, pos, ['right', 'top'])
}
if (ay) {
const pos = [axisAnglePos.rightTop[0] + defaultAxisLineTagGap, 0]
drawUnit(ay, pos, ['left', 'top'])
}
},
drawUnit ({ unit, unitColor, fontSize, fontFamily }, pos, align) {
const { axisTagColor, axisFontSize, axisFontFamily } = this
const { ctx } = this
if (!unit) return
ctx.font = `${fontSize || axisFontSize}px ${fontFamily || axisFontFamily}`
ctx.fillStyle = unitColor || axisTagColor
ctx.textAlign = align[0]
ctx.textBaseline = align[1]
ctx.fillText(unit, ...pos)
},
drawAxisGrid () {
const { valueAxisTagPos, agValueAxisTagPos, labelAxisTagPos, agLabelAxisTagPos } = this
const { valueAxisTag, agValueAxisTag, labelAxisTag, agLabelAxisTag } = this
const { data: { x, ax, y, ay }, horizon, drawGrid, boundaryGap } = this
const xAxis = horizon ? [valueAxisTag, valueAxisTagPos] : [labelAxisTag, labelAxisTagPos]
let xBELineStatus = [false, false]
if (horizon) {
xBELineStatus = [true, false]
} else {
xBELineStatus = boundaryGap ? [false, false] : [true, true]
}
if (xAxis[0].length) drawGrid(x, ...xAxis, false, false, true, ...xBELineStatus)
const yAxis = horizon ? [labelAxisTag, labelAxisTagPos] : [valueAxisTag, valueAxisTagPos]
if (yAxis[0].length) drawGrid(y, ...yAxis, true, true, false, !horizon)
const agXAxis = horizon ? [agValueAxisTag, agValueAxisTagPos] : [agLabelAxisTag, agLabelAxisTagPos]
if (agXAxis[0].length) drawGrid(ax, ...agXAxis, false, false, false, ...(boundaryGap ? [false, false] : [true, true]))
const agYAxis = horizon ? [agLabelAxisTag, agLabelAxisTagPos] : [agValueAxisTag, agValueAxisTagPos]
if (agYAxis[0].length) drawGrid(ay, ...agYAxis, true, false, false, true)
},
drawGrid (axis = {}, gridTag, gridPos, horizon = true, right = true, top = true, noFirst = false, noLast = false) {
const { grid, gridLineColor: cGLC, gridLineDash: cGLD } = axis
if (!grid) return
const { gridLineColor, gridLineDash, defaultGridLineDash } = this
const { ctx, drawColors, axisWH } = this
let trueGridLineDash = gridLineDash
if (cGLD instanceof Array) {
trueGridLineDash = cGLD
} else if (cGLD === true) {
trueGridLineDash = defaultGridLineDash
}
ctx.setLineDash(trueGridLineDash)
let color = cGLC || gridLineColor
color === 'colors' && (color = drawColors)
const mulColor = color instanceof Array
const colorNum = color.length
!mulColor && (ctx.strokeStyle = color)
ctx.lineWidth = 1
const gridLastIndex = gridPos.length - 1
gridPos.forEach((pos, i) => {
if (!gridTag[i] && gridTag[i] !== 0) return
if (i === 0 && noFirst) return
if (i === gridLastIndex && noLast) return
ctx.beginPath()
mulColor && (ctx.strokeStyle = color[i % colorNum])
ctx.moveTo(...pos)
ctx.lineTo(...(horizon
? [right ? pos[0] + axisWH[0] : pos[0] - axisWH[0], pos[1]]
: [pos[0], top ? pos[1] - axisWH[1] : pos[1] + axisWH[1]]))
ctx.stroke()
})
}
}
}

View File

@ -1,43 +0,0 @@
export default {
data () {
return {
canvasDom: '',
canvasWH: [0, 0],
ctx: '',
centerPos: [0, 0]
}
},
methods: {
initCanvas () {
const { $nextTick } = this
return new Promise(resolve => {
$nextTick(e => {
const { $refs, ref, labelRef, canvasWH, centerPos } = this
const canvas = this.canvasDom = $refs[ref]
this.labelDom = $refs[labelRef]
canvasWH[0] = canvas.clientWidth
canvasWH[1] = canvas.clientHeight
canvas.setAttribute('width', canvasWH[0])
canvas.setAttribute('height', canvasWH[1])
this.ctx = canvas.getContext('2d')
centerPos[0] = canvasWH[0] / 2
centerPos[1] = canvasWH[1] / 2
resolve()
})
})
},
clearCanvas () {
const { ctx, canvasWH } = this
ctx.clearRect(0, 0, ...canvasWH)
}
}
}

View File

@ -1,23 +0,0 @@
export default {
data () {
return {
defaultColors: [
'#9cf4a7', '#66d7ee', '#eee966',
'#a866ee', '#ee8f66', '#ee66aa'
],
drawColors: '',
drawColorsMul: false
}
},
methods: {
initColors () {
const { colors, defaultColors } = this
const trueDrawColors = this.drawColors = colors || defaultColors
this.drawColorsMul = trueDrawColors instanceof Array
}
}
}

View File

@ -1,195 +0,0 @@
import { filterNull } from './methodsExtend'
export function drawLine (ctx, lineBegin, lineEnd, lineWidth = 2, lineColor = '#000', dashArray = [10, 0]) {
if (!ctx || !lineBegin || !lineEnd) return
ctx.beginPath()
ctx.moveTo(...lineBegin)
ctx.lineTo(...lineEnd)
ctx.closePath()
ctx.lineWidth = lineWidth
ctx.strokeStyle = lineColor
ctx.setLineDash(dashArray)
ctx.stroke()
}
export function drawPolylinePath (ctx, points, close = false, newPath = false) {
if (!ctx || !points.length) return
newPath && ctx.beginPath()
points.forEach((point, i) =>
point && (i === 0 ? ctx.moveTo(...point) : ctx.lineTo(...point)))
close && ctx.lineTo(...points[0])
}
export function drawPolyline (ctx, points, lineWidth = 2, lineColor = '#000', close = false, dashArray = [10, 0], newPath = false) {
if (!ctx || !points.length) return
drawPolylinePath(ctx, points, close, newPath)
ctx.lineWidth = lineWidth
ctx.strokeStyle = lineColor
ctx.setLineDash(dashArray)
ctx.stroke()
}
export function drawSmoothlinePath (ctx, points, close = false, newPath = false, moveTo = false) {
const canDrawPoints = filterNull(points)
if (!ctx || canDrawPoints.length < 2) return
close && canDrawPoints.push(canDrawPoints[0])
newPath && ctx.beginPath()
if (canDrawPoints.length === 2) {
ctx.moveTo(...canDrawPoints[0])
ctx.lineTo(...canDrawPoints[1])
return
}
const lastPointIndex = canDrawPoints.length - 1
moveTo && ctx.moveTo(...canDrawPoints[0])
canDrawPoints.forEach((t, i) =>
(i !== lastPointIndex) && drawBezierCurveLinePath(ctx,
...getBezierCurveLineControlPoints(canDrawPoints, i, false),
canDrawPoints[i + 1]))
}
export function getBezierCurveLineControlPoints (points, index, close = false, offsetA = 0.25, offsetB = 0.25) {
const pointNum = points.length
if (pointNum < 3 || index >= pointNum) return
let beforePointIndex = index - 1
beforePointIndex < 0 && (beforePointIndex = (close ? pointNum + beforePointIndex : 0))
let afterPointIndex = index + 1
afterPointIndex >= pointNum && (afterPointIndex = (close ? afterPointIndex - pointNum : pointNum - 1))
let afterNextPointIndex = index + 2
afterNextPointIndex >= pointNum && (afterNextPointIndex = (close ? afterNextPointIndex - pointNum : pointNum - 1))
const pointBefore = points[beforePointIndex]
const pointMiddle = points[index]
const pointAfter = points[afterPointIndex]
const pointAfterNext = points[afterNextPointIndex]
return [
[
pointMiddle[0] + offsetA * (pointAfter[0] - pointBefore[0]),
pointMiddle[1] + offsetA * (pointAfter[1] - pointBefore[1])
],
[
pointAfter[0] - offsetB * (pointAfterNext[0] - pointMiddle[0]),
pointAfter[1] - offsetB * (pointAfterNext[1] - pointMiddle[1])
]
]
}
export function drawSmoothline (ctx, points, lineWidth = 2, lineColor = '#000', close = false, dashArray = [10, 0], newPath = false, moveTo = false) {
if (!ctx || points.length < 3) return
drawSmoothlinePath(ctx, points, close, newPath, moveTo)
ctx.lineWidth = lineWidth
ctx.strokeStyle = lineColor
ctx.setLineDash(dashArray)
ctx.stroke()
}
export function drawBezierCurveLinePath (ctx, ctlBefore, ctlAfter, end, newPath = false) {
if (!ctx || !ctlBefore || !ctlAfter || !end) return
newPath && ctx.beginPath()
ctx.bezierCurveTo(...ctlBefore, ...ctlAfter, ...end)
}
export function drawPoints (ctx, points, radius = 10, color = '#000') {
points.forEach(point => {
if (!point) return
ctx.beginPath()
ctx.arc(...point, radius, 0, Math.PI * 2)
ctx.fillStyle = color
ctx.fill()
})
}
export function getLinearGradientColor (ctx, begin, end, color) {
if (!ctx || !begin || !end || !color.length) return
let colors = color
typeof colors === 'string' && (colors = [color, color])
const linearGradientColor = ctx.createLinearGradient(...begin, ...end)
const colorGap = 1 / (colors.length - 1)
colors.forEach((c, i) => linearGradientColor.addColorStop(colorGap * i, c))
return linearGradientColor
}
export function getRadialGradientColor (ctx, origin, begin = 0, end = 100, color) {
if (!ctx || !origin || !color.length) return
let colors = color
typeof colors === 'string' && (colors = [color, color])
const radialGradientColor = ctx.createRadialGradient(...origin, begin, ...origin, end)
const colorGap = 1 / (colors.length - 1)
colors.forEach((c, i) => radialGradientColor.addColorStop(colorGap * i, c))
return radialGradientColor
}
export function getCircleRadianPoint (x, y, radius, radian) {
const { sin, cos } = Math
return [x + cos(radian) * radius, y + sin(radian) * radius]
}
export function getTextsWidth (ctx, texts) {
if (!ctx || !texts) return
return texts.map(text => ctx.measureText(text).width)
}
const canvas = {
drawLine,
drawPolylinePath,
drawPolyline,
getBezierCurveLineControlPoints,
drawSmoothlinePath,
drawSmoothline,
drawBezierCurveLinePath,
drawPoints,
getLinearGradientColor,
getRadialGradientColor,
getCircleRadianPoint,
getTextsWidth
}
export default function (Vue) {
Vue.prototype.canvas = canvas
}

View File

@ -1,53 +0,0 @@
const hexReg = /^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/
const rgbReg = /^(rgb|rgba|RGB|RGBA)/
/**
* @description hex color to rgb / rgba color
* @param {string} rgba opacity
* @return {string} rgb / rgba color
*/
export function hexToRgb (hex, opacity) {
if (!hex || !hexReg.test(hex)) return false
hex = hex.toLowerCase().replace('#', '')
// deal 3bit hex color to 6bit hex color
hex.length === 3 && (hex = Array.from(hex).map(hexNum => hexNum + hexNum).join(''))
let rgb = []
for (let i = 0; i < 6; i += 2) {
rgb.push(parseInt(`0x${hex.slice(i, i + 2)}`))
}
rgb = rgb.join(',')
if (opacity) {
return `rgba(${rgb}, ${opacity})`
} else {
return `rgb(${rgb})`
}
}
/**
* @description rgb / rgba color to hex color
* @return {string} hex color
*/
export function rgbToHex (rgb) {
if (!rgb || !rgbReg.test(rgb)) return false
rgb = rgb.toLowerCase().replace(/rgb\(|rgba\(|\)/g, '').split(',').slice(0, 3)
const hex = rgb.map((rgbNum) => Number(rgbNum).toString(16)).map((rgbNum) => rgbNum === '0' ? rgbNum + rgbNum : rgbNum).join('')
return `#${hex}`
}
const color = {
hexToRgb,
rgbToHex
}
export default function (Vue) {
Vue.prototype.color = color
}

View File

@ -1,11 +0,0 @@
import methodsExtend from './methodsExtend'
import canvasExtend from './canvasExtend'
import colorExtend from './colorExtend'
export default function (Vue) {
methodsExtend(Vue)
canvasExtend(Vue)
colorExtend(Vue)
}

View File

@ -1,155 +0,0 @@
const { parse, stringify } = JSON
export function deepClone (object) {
return parse(stringify(object))
}
export function deleteArrayAllItems (arrays) {
arrays.forEach(element => element.splice(0, element.length))
}
export function debounce (delay, callback) {
let lastTime
return function () {
clearTimeout(lastTime)
const [that, args] = [this, arguments]
lastTime = setTimeout(() => {
callback.apply(that, args)
}, delay)
}
}
export function randomExtend (minNum, maxNum) {
if (arguments.length === 1) {
return parseInt(Math.random() * minNum + 1, 10)
} else {
return parseInt(Math.random() * (maxNum - minNum + 1) + minNum, 10)
}
}
export function multipleSum (...num) {
let sum = 0
num.forEach(n => (sum += n))
return sum
}
export function filterNull (arr) {
const tmpArr = []
arr.forEach(v => ((v || v === 0) && tmpArr.push(v)))
return tmpArr
}
export function getPointDistance (pointOne, pointTwo) {
const minusX = Math.abs(pointOne[0] - pointTwo[0])
const minusY = Math.abs(pointOne[1] - pointTwo[1])
return Math.sqrt(minusX * minusX + minusY * minusY)
}
export function getPointToLineDistance (point, linePointOne, linePointTwo) {
const a = getPointDistance(point, linePointOne)
const b = getPointDistance(point, linePointTwo)
const c = getPointDistance(linePointOne, linePointTwo)
return 0.5 * Math.sqrt((a + b + c) * (a + b - c) * (a + c - b) * (b + c - a)) / c
}
export function getArrayMaxMin (array) {
if (!array) return false
return [getArrayMax(array), getArrayMin(array)]
}
export function getArrayMax (array) {
if (!array) return false
return Math.max(...filterNull(array).map(n =>
n instanceof Array ? getArrayMax(n) : n))
}
export function getArrayMin (array) {
if (!array) return false
return Math.min(...filterNull(array).map(n =>
n instanceof Array ? getArrayMin(n) : n))
}
export function getAxisPointsPos ([max, min], values, axisOriginPos, axisWH, tagPos, horizon) {
let minus = max - min
minus === 0 && (minus = 1)
return values.map((value, i) => {
if (!value && value !== 0) return false
if (value instanceof Array) {
return value.map(v =>
getAxisPointPos([max, min], v, axisOriginPos, axisWH, tagPos[i], horizon))
}
const percent = (value - min) / minus
const length = percent * (horizon ? axisWH[0] : axisWH[1])
return horizon ? [
axisOriginPos[0] + length,
tagPos[i][1]
] : [
tagPos[i][0],
axisOriginPos[1] - length
]
})
}
export function getAxisPointPos ([max, min], value, axisOriginPos, axisWH, tagPos, horizon) {
if (!value && value !== 0) return false
const minus = max - min
const percent = (value - min) / minus
const length = percent * (horizon ? axisWH[0] : axisWH[1])
return horizon ? [
axisOriginPos[0] + length,
tagPos[1]
] : [
tagPos[0],
axisOriginPos[1] - length
]
}
export function observerDomResize (dom, callback) {
const MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver
const observer = new MutationObserver(callback)
observer.observe(dom, { attributes: true, attributeFilter: ['style'], attributeOldValue: true })
return observer
}
export default function (Vue) {
Vue.prototype.deepClone = deepClone
Vue.prototype.deleteArrayAllItems = deleteArrayAllItems
Vue.prototype.debounce = debounce
Vue.prototype.multipleSum = multipleSum
Vue.prototype.randomExtend = randomExtend
Vue.prototype.filterNull = filterNull
Vue.prototype.getPointDistance = getPointDistance
Vue.prototype.getPointToLineDistance = getPointToLineDistance
Vue.prototype.getArrayMaxMin = getArrayMaxMin
Vue.prototype.getArrayMax = getArrayMax
Vue.prototype.getArrayMin = getArrayMin
Vue.prototype.getAxisPointPos = getAxisPointPos
Vue.prototype.getAxisPointsPos = getAxisPointsPos
Vue.prototype.observerDomResize = observerDomResize
}

7
util/index.js Normal file
View File

@ -0,0 +1,7 @@
export function randomExtend (minNum, maxNum) {
if (arguments.length === 1) {
return parseInt(Math.random() * minNum + 1, 10)
} else {
return parseInt(Math.random() * (maxNum - minNum + 1) + minNum, 10)
}
}