update for v 2.0.0
							
								
								
									
										26
									
								
								CHANGELOG.md
									
									
									
									
									
										Normal 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
									
								
							
							
						
						| @@ -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. | ||||
							
								
								
									
										25
									
								
								README.md
									
									
									
									
									
								
							
							
						
						| @@ -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). | ||||
|   | ||||
							
								
								
									
										302
									
								
								components/activeRingChart/index.vue
									
									
									
									
									
										Normal 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> | ||||
| @@ -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> | ||||
| Before Width: | Height: | Size: 93 KiB | 
| @@ -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"> | ||||
|     <slot></slot> | ||||
|   <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> | ||||
|   | ||||
| @@ -11,19 +11,21 @@ | ||||
|       <circle cx="11" :cy="height - 11" r="1" /> | ||||
|     </svg> | ||||
|  | ||||
|     <slot></slot> | ||||
|     <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> | ||||
|   | ||||
| @@ -11,19 +11,21 @@ | ||||
|         :points="`22, 22 ${width - 4}, 22 ${width - 4}, ${height - 4} 22, ${height - 4} 22, 22`" /> | ||||
|     </svg> | ||||
|  | ||||
|     <slot></slot> | ||||
|     <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> | ||||
|   | ||||
| @@ -15,30 +15,37 @@ | ||||
|       <polyline class="dv-bb4-line-10" :points="`385, 17 ${width - 10}, 17`" /> | ||||
|     </svg> | ||||
|  | ||||
|     <slot></slot> | ||||
|     <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> | ||||
|   | ||||
| @@ -11,30 +11,37 @@ | ||||
|       <polyline class="dv-bb5-line-6" :points="`15, ${height - 13} ${width - 110}, ${height - 13}`" /> | ||||
|     </svg> | ||||
|  | ||||
|     <slot></slot> | ||||
|     <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> | ||||
|   | ||||
| @@ -19,19 +19,21 @@ | ||||
|       <polyline :points="`${width - 7}, ${height - 30} ${width - 7}, ${height - 80}`" /> | ||||
|     </svg> | ||||
|  | ||||
|     <slot></slot> | ||||
|     <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> | ||||
|   | ||||
| @@ -12,19 +12,21 @@ | ||||
|       <polyline class="dv-bb7-line-width-5" :points="`0, ${height - 10} 0, ${height} 10, ${height}`" /> | ||||
|     </svg> | ||||
|  | ||||
|     <slot></slot> | ||||
|     <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> | ||||
|   | ||||
							
								
								
									
										108
									
								
								components/borderBox8/index.vue
									
									
									
									
									
										Normal 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> | ||||
| @@ -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> </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> | ||||
							
								
								
									
										87
									
								
								components/charts/index.vue
									
									
									
									
									
										Normal 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> | ||||
| @@ -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> | ||||
| @@ -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> | ||||
| Before Width: | Height: | Size: 48 KiB | 
| @@ -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 { | ||||
| .dv-decoration-1 { | ||||
|   width: 100%; | ||||
|   height: 100%; | ||||
|  | ||||
|   img { | ||||
|     width: 100%; | ||||
|   svg { | ||||
|     transform-origin: left top; | ||||
|   } | ||||
| } | ||||
| </style> | ||||
|   | ||||
| @@ -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 | ||||
|       } | ||||
|     }, | ||||
|     onResize () { | ||||
|       const { calcSVGData } = this | ||||
|  | ||||
|       calcSVGData() | ||||
|     } | ||||
|   }, | ||||
|   mounted () { | ||||
|     const { init } = this | ||||
|  | ||||
|     init() | ||||
|   } | ||||
| } | ||||
| </script> | ||||
|  | ||||
| <style lang="less" scoped> | ||||
| .decoration-2 { | ||||
|  | ||||
|   .reverse, .normal { | ||||
|     background-color: #3faacb; | ||||
|   } | ||||
|  | ||||
|   .normal { | ||||
|     width: 0%; | ||||
|     height: 1px; | ||||
|     border-right: 1px solid #fff; | ||||
|     animation: normal-amt 6s ease-in-out infinite; | ||||
|   } | ||||
|  | ||||
|   .reverse { | ||||
|     width: 1px; | ||||
|     height: 0%; | ||||
|     border-bottom: 1px solid #fff; | ||||
|     animation: reverse-amt 6s ease-in-out infinite; | ||||
|   } | ||||
|  | ||||
|   @keyframes reverse-amt { | ||||
|     70% { | ||||
|       height: 100%; | ||||
|     } | ||||
|  | ||||
|     100% { | ||||
|       height: 100%; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   @keyframes normal-amt { | ||||
|     70% { | ||||
|       width: 100%; | ||||
|     } | ||||
|  | ||||
|     100% { | ||||
|       width: 100%; | ||||
|     } | ||||
|   } | ||||
| <style lang="less"> | ||||
| .dv-decoration-2 { | ||||
|   display: flex; | ||||
|   width: 100%; | ||||
|   height: 100%; | ||||
|   justify-content: center; | ||||
|   align-items: center; | ||||
| } | ||||
| </style> | ||||
|   | ||||
| Before Width: | Height: | Size: 86 KiB | 
| @@ -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 { | ||||
| .dv-decoration-3 { | ||||
|   width: 100%; | ||||
|   height: 100%; | ||||
|  | ||||
|   img { | ||||
|     width: 100%; | ||||
|   svg { | ||||
|     transform-origin: left top; | ||||
|   } | ||||
| } | ||||
| </style> | ||||
|   | ||||
| @@ -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> | ||||
|     </svg> | ||||
|   <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; | ||||
|   width: 100%; | ||||
|   height: 100%; | ||||
|  | ||||
|   &.normal { | ||||
|     width: 6px; | ||||
|   } | ||||
|  | ||||
|   &.reverse { | ||||
|     height: 6px; | ||||
|   } | ||||
|  | ||||
|   .svg-container { | ||||
|   .container { | ||||
|     display: flex; | ||||
|     overflow: hidden; | ||||
|     position: absolute; | ||||
|  | ||||
|     &.ani-height { | ||||
|       width: 100%; | ||||
|       height: 0%; | ||||
|       animation: ani-height 3s ease-in-out infinite; | ||||
|     } | ||||
|  | ||||
|     &.ani-width { | ||||
|       width: 0%; | ||||
|       height: 100%; | ||||
|       animation: ani-width 3s ease-in-out infinite; | ||||
|     } | ||||
|  | ||||
|     polyline { | ||||
|       fill: none; | ||||
|       stroke: fade(gray, 25); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   .lighter-line { | ||||
|     stroke-width: 1px; | ||||
|   .normal { | ||||
|     height: 0% !important; | ||||
|     animation: ani-height 3s ease-in-out infinite; | ||||
|     left: 50%; | ||||
|     margin-left: -2px; | ||||
|   } | ||||
|  | ||||
|   .bolder-line { | ||||
|     stroke-width: 3px; | ||||
|     stroke-dasharray: 20, 80; | ||||
|     stroke-dashoffset: -30; | ||||
|   .reverse { | ||||
|     width: 0% !important; | ||||
|     animation: ani-width 3s ease-in-out infinite; | ||||
|     top: 50%; | ||||
|     margin-top: -2px; | ||||
|   } | ||||
|  | ||||
|   @keyframes ani-height { | ||||
|   | ||||
| Before Width: | Height: | Size: 20 KiB | 
| @@ -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%; | ||||
|   } | ||||
|   width: 100%; | ||||
|   height: 100%; | ||||
| } | ||||
| </style> | ||||
|   | ||||
| Before Width: | Height: | Size: 242 KiB | 
| @@ -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 { | ||||
| .dv-decoration-6 { | ||||
|   width: 100%; | ||||
|   height: 100%; | ||||
|  | ||||
|   img { | ||||
|     width: 100%; | ||||
|   svg { | ||||
|     transform-origin: left top; | ||||
|   } | ||||
| } | ||||
| </style> | ||||
|   | ||||
| Before Width: | Height: | Size: 496 B | 
| @@ -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> | ||||
|   | ||||
							
								
								
									
										63
									
								
								components/decoration8/index.vue
									
									
									
									
									
										Normal 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> | ||||
							
								
								
									
										194
									
								
								components/digitalFlop/index.vue
									
									
									
									
									
										Normal 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> | ||||
							
								
								
									
										509
									
								
								components/flylineChart/index.vue
									
									
									
									
									
										Normal 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> | ||||
| @@ -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> | ||||
| @@ -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) | ||||
| } | ||||
|   | ||||
| @@ -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> | ||||
| Before Width: | Height: | Size: 6.1 KiB | 
| @@ -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> | ||||
|   | ||||
| @@ -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> | ||||
| @@ -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> | ||||
| @@ -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="dv-percent-pond" ref="percent-pond"> | ||||
|     <svg> | ||||
|       <defs> | ||||
|         <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> | ||||
|  | ||||
|     <div class="percent-container"> | ||||
|       <div class="p-decoration-box" /> | ||||
|       <div class="p-svg-container" :ref="ref"> | ||||
|         <svg :width="width" :height="height"> | ||||
|           <defs> | ||||
|             <linearGradient id="linear"> | ||||
|               <stop v-for="lc in linearGradient" :key="lc[0]" | ||||
|                 :offset="lc[0]" | ||||
|                 :stop-color="lc[1]" /> | ||||
|             </linearGradient> | ||||
|           </defs> | ||||
|  | ||||
|           <polyline :stroke-width="height - 1" | ||||
|             stroke="url(#linear)" | ||||
|             :points="`1, ${height * 0.5} ${percent * (width - 2) / 100}, ${height * 0.5 + 0.0001}`" /> | ||||
|         </svg> | ||||
|       </div> | ||||
|       <div class="p-decoration-box" /> | ||||
|     </div> | ||||
|         <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> | ||||
|       <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> | ||||
| </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() | ||||
|     } | ||||
|   }, | ||||
|   methods: { | ||||
|     init () { | ||||
|       const { $nextTick, $refs, ref, calcLinearColor } = this | ||||
|       if (!mergedConfig) return 0 | ||||
|  | ||||
|       $nextTick(e => { | ||||
|         this.width = $refs[ref].clientWidth | ||||
|         this.height = $refs[ref].clientHeight | ||||
|       }) | ||||
|       const { borderWidth } = mergedConfig | ||||
|  | ||||
|       calcLinearColor() | ||||
|       return width - borderWidth | ||||
|     }, | ||||
|     calcLinearColor () { | ||||
|       const { colors, defaultColor } = this | ||||
|     rectHeight () { | ||||
|       const { mergedConfig, height } = this | ||||
|  | ||||
|       let trueColor = colors || defaultColor | ||||
|       if (!mergedConfig) return 0 | ||||
|  | ||||
|       typeof trueColor === 'string' && (trueColor = [trueColor, trueColor]) | ||||
|       const { borderWidth } = mergedConfig | ||||
|  | ||||
|       const colorNum = trueColor.length | ||||
|       return height - borderWidth | ||||
|     }, | ||||
|     points () { | ||||
|       const { mergedConfig, width, height } = this | ||||
|  | ||||
|       const halfHeight = height / 2 | ||||
|  | ||||
|       if (!mergedConfig) return `0, ${halfHeight} 0, ${halfHeight}` | ||||
|  | ||||
|       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; | ||||
|       } | ||||
|     } | ||||
|   polyline { | ||||
|     transition: all 0.3s; | ||||
|   } | ||||
|  | ||||
|   .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; | ||||
|       } | ||||
|     } | ||||
|   text { | ||||
|     font-size: 25px; | ||||
|     font-weight: bold; | ||||
|     text-anchor: middle; | ||||
|     dominant-baseline: middle; | ||||
|   } | ||||
| } | ||||
| </style> | ||||
|   | ||||
| @@ -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> | ||||
| @@ -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> | ||||
| @@ -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> | ||||
| @@ -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> | ||||
| @@ -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, | ||||
|       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}%' | ||||
|       }, | ||||
|  | ||||
|       id: `water-level-pond-${(new Date()).getTime()}`, | ||||
|       mergedConfig: {}, | ||||
|  | ||||
|       defaultColor: ['#00BAFF', '#3DE7C9'], | ||||
|       render: null, | ||||
|  | ||||
|       defaultWaveNum: 3, | ||||
|       defaultWaveHeight: 0.2, | ||||
|       defaultWaveOffset: -0.5, | ||||
|       svgBorderGradient: [], | ||||
|  | ||||
|       waveAdded: 0.7, | ||||
|       details: '', | ||||
|  | ||||
|       drawColor: '', | ||||
|       linearGradient: [], | ||||
|       waveTrueNum: '', | ||||
|       waveTrueHeight: '', | ||||
|       waveTrueWidth: '', | ||||
|       wavePoints: [], | ||||
|       bottomPoints: [], | ||||
|       overXPos: 0, | ||||
|       currentPoints: [], | ||||
|       animationHandler: '' | ||||
|     } | ||||
|   }, | ||||
|   props: ['level', 'type', 'colors', 'waveNum', 'waveHeight', 'borderColor', 'noGradient'], | ||||
|   watch: { | ||||
|     level () { | ||||
|       const { checkData, draw } = this | ||||
|       waves: [], | ||||
|  | ||||
|       checkData() && draw() | ||||
|       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]] | ||||
|       ] | ||||
|     }, | ||||
|     calcOverXPos () { | ||||
|       const { canvasWH: [width], waveTrueWidth } = this | ||||
|       const { waveNum, waveHeight, data } = mergedConfig | ||||
|  | ||||
|       this.overXPos = width + waveTrueWidth | ||||
|     }, | ||||
|     drawWaveAnimation () { | ||||
|       const { clearCanvas, drawWaveAnimation } = this | ||||
|       const [w, h] = render.area | ||||
|  | ||||
|       clearCanvas() | ||||
|       const pointsNum = waveNum * 4 + 4 | ||||
|  | ||||
|       const { getCurrentPoints, drawCurrentWave, calcNextFramePoints } = this | ||||
|       const pointXGap = w / waveNum / 2 | ||||
|  | ||||
|       getCurrentPoints() | ||||
|       return data.map(v => { | ||||
|         let points = new Array(pointsNum).fill(0).map((foo, j) => { | ||||
|           const x = w - pointXGap * j | ||||
|  | ||||
|       drawCurrentWave() | ||||
|           const startY = (1 - v / 100) * h | ||||
|  | ||||
|       calcNextFramePoints() | ||||
|           const y = j % 2 === 0 ? startY : startY - waveHeight | ||||
|  | ||||
|       this.animationHandler = requestAnimationFrame(drawWaveAnimation) | ||||
|     }, | ||||
|     getCurrentPoints () { | ||||
|       const { level, wavePoints, canvasWH: [, height] } = this | ||||
|           return [x, y] | ||||
|         }) | ||||
|  | ||||
|       this.currentPoints = level.map(l => | ||||
|         wavePoints.map(([x, y]) => | ||||
|           [x, y - (l / 100 * height)])) | ||||
|     }, | ||||
|     drawCurrentWave () { | ||||
|       const { currentPoints, ctx, bottomPoints, drawColor, canvasWH: [, y], noGradient } = this | ||||
|         points = points.map(p => mergeOffset(p, [pointXGap * 2, 0])) | ||||
|  | ||||
|       const { canvas: { drawSmoothlinePath, getLinearGradientColor } } = this | ||||
|  | ||||
|       const { color: { hexToRgb } } = this | ||||
|  | ||||
|       const multipleColor = typeof drawColor === 'object' | ||||
|  | ||||
|       !multipleColor && (ctx.fillStyle = drawColor) | ||||
|  | ||||
|       multipleColor && | ||||
|         !noGradient && | ||||
|           (ctx.fillStyle = getLinearGradientColor(ctx, [0, y], [0, 0], drawColor.map(c => hexToRgb(c, 0.5)))) | ||||
|  | ||||
|       const colorNum = drawColor.length | ||||
|  | ||||
|       currentPoints.forEach((line, i) => { | ||||
|         drawSmoothlinePath(ctx, line, false, true, true) | ||||
|  | ||||
|         ctx.lineTo(...bottomPoints[0]) | ||||
|         ctx.lineTo(...bottomPoints[1]) | ||||
|  | ||||
|         ctx.closePath() | ||||
|  | ||||
|         multipleColor && noGradient && (ctx.fillStyle = drawColor[i % colorNum]) | ||||
|  | ||||
|         ctx.fill() | ||||
|         return { points } | ||||
|       }) | ||||
|     }, | ||||
|     calcNextFramePoints () { | ||||
|       const { wavePoints, waveAdded, addWavePoint, overXPos } = this | ||||
|  | ||||
|       const addedWavePoints = wavePoints.map(([x, y]) => [x + waveAdded, y]) | ||||
|  | ||||
|       const lastPointIndex = addedWavePoints.length - 1 | ||||
|  | ||||
|       let addStatus = false | ||||
|  | ||||
|       addedWavePoints[lastPointIndex][0] > overXPos && | ||||
|         addedWavePoints.pop() && (addStatus = true) | ||||
|  | ||||
|       this.wavePoints = addedWavePoints | ||||
|  | ||||
|       addStatus && addWavePoint() | ||||
|     mergeOffset ([x, y], [ox, oy]) { | ||||
|       return [x + ox, y + oy] | ||||
|     }, | ||||
|     stopAnimation () { | ||||
|       const { animationHandler } = this | ||||
|     getWaveStyle () { | ||||
|       const { render, mergedConfig } = this | ||||
|  | ||||
|       animationHandler && cancelAnimationFrame(animationHandler) | ||||
|       const h = render.area[1] | ||||
|  | ||||
|       return { | ||||
|         gradientColor: mergedConfig.colors, | ||||
|         gradientType: 'linear', | ||||
|         gradientParams: [0, 0, 0, h], | ||||
|         gradientWith: 'fill', | ||||
|         opacity: mergedConfig.waveOpacity, | ||||
|         translate: [0, 0] | ||||
|       } | ||||
|     }, | ||||
|     drawed ({ shape: { points } }, { ctx, area }) { | ||||
|       const firstPoint = points[0] | ||||
|       const lastPoint = points.slice(-1)[0] | ||||
|  | ||||
|       const [w, h] = area | ||||
|  | ||||
|       ctx.lineTo(lastPoint[0], h) | ||||
|       ctx.lineTo(firstPoint[0], h) | ||||
|  | ||||
|       ctx.closePath() | ||||
|  | ||||
|       ctx.fill() | ||||
|     }, | ||||
|     async animationWave (repeat = 1) { | ||||
|       const { waves, render, animation } = this | ||||
|  | ||||
|       if (animation) return | ||||
|  | ||||
|       this.animation = true | ||||
|  | ||||
|       const w = render.area[0] | ||||
|  | ||||
|       waves.forEach(graph => { | ||||
|         const reset = repeat % 2 === 0 | ||||
|  | ||||
|         graph.attr('style', { translate: [0, 0] }) | ||||
|  | ||||
|         graph.animation('style', { | ||||
|           translate: [w, 0] | ||||
|         }, true) | ||||
|       }) | ||||
|  | ||||
|       await render.launchAnimation() | ||||
|  | ||||
|       this.animation = false | ||||
|  | ||||
|       if (!render.graphs.length) return | ||||
|  | ||||
|       this.animationWave(repeat + 1) | ||||
|     } | ||||
|   }, | ||||
|   mounted () { | ||||
| @@ -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> | ||||
|   | ||||
| @@ -1 +0,0 @@ | ||||
| export default {} | ||||
							
								
								
									
										3
									
								
								index.js
									
									
									
									
									
								
							
							
						
						| @@ -1,8 +1,5 @@ | ||||
| import components from './components/index' | ||||
|  | ||||
| import plugins from './plugins' | ||||
|  | ||||
| export default function (Vue) { | ||||
|   components(Vue) | ||||
|   plugins(Vue) | ||||
| } | ||||
|   | ||||
| @@ -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 | ||||
| @@ -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() | ||||
|       }) | ||||
|     } | ||||
|   } | ||||
| } | ||||
| @@ -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) | ||||
|     } | ||||
|   } | ||||
| } | ||||
| @@ -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 | ||||
|     } | ||||
|   } | ||||
| } | ||||
| @@ -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 | ||||
| } | ||||
| @@ -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 | ||||
| } | ||||
| @@ -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) | ||||
| } | ||||
| @@ -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
									
								
							
							
						
						| @@ -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) | ||||
|   } | ||||
| } | ||||
 jiaming743
					jiaming743