DemuMesDataV/src/components/columnChart/index.vue

582 lines
16 KiB
Vue
Raw Normal View History

2018-12-20 18:25:19 +08:00
<template>
<div class="column-chart">
<loading v-if="!data" />
<div class="canvas-container">
<canvas :ref="ref" />
</div>
<label-line :label="data.labelLine" :colors="drawColors" />
2018-12-24 18:24:52 +08:00
<for-slot><slot></slot></for-slot>
2018-12-20 18:25:19 +08:00
</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],
2018-12-24 18:24:52 +08:00
props: ['data', 'colors'],
2018-12-20 18:25:19 +08:00
data () {
return {
ref: `radar-chart-${(new Date()).getTime()}`,
2018-12-23 19:06:42 +08:00
// axis base config
boundaryGap: true,
mulValueAdd: true,
horizon: false,
2018-12-23 22:33:45 +08:00
echelonOffset: 10,
2018-12-24 14:09:25 +08:00
defaultColumnBGColor: 'rgba(100, 100, 100, 0.2)',
defaultValueFontSize: 10,
defaultValueColor: '#999',
2018-12-23 22:33:45 +08:00
2018-12-23 19:06:42 +08:00
columnData: [],
columnItemSeriesNum: 0,
columnItemAllWidth: 0,
columnItemWidth: 0,
2018-12-24 14:09:25 +08:00
columnBGWidth: 0,
2018-12-23 19:06:42 +08:00
columnItemOffset: [],
2018-12-24 14:09:25 +08:00
valueTextOffset: [],
2018-12-23 19:06:42 +08:00
valuePointPos: []
2018-12-20 18:25:19 +08:00
}
},
2018-12-24 18:24:52 +08:00
watch: {
data (d) {
const { draw } = this
d && draw()
}
},
2018-12-20 18:25:19 +08:00
methods: {
async init () {
2018-12-23 19:06:42 +08:00
const { initCanvas, initColors } = this
2018-12-20 18:25:19 +08:00
await initCanvas()
2018-12-23 19:06:42 +08:00
initColors()
const { data, draw } = this
2018-12-20 18:25:19 +08:00
data && draw()
},
draw () {
2018-12-23 19:06:42 +08:00
const { clearCanvas } = this
2018-12-20 18:25:19 +08:00
clearCanvas()
2018-12-23 19:06:42 +08:00
const { calcHorizon, initAxis, drawAxis } = this
calcHorizon()
2018-12-20 18:25:19 +08:00
initAxis()
drawAxis()
2018-12-20 22:36:07 +08:00
2018-12-23 19:06:42 +08:00
const { switchNormalOrCenterOriginType } = this
2018-12-20 22:36:07 +08:00
2018-12-23 19:06:42 +08:00
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
2018-12-20 22:36:07 +08:00
calcColumnConfig()
2018-12-23 19:06:42 +08:00
calcColumnItemOffset()
calcValuePointPos()
2018-12-20 22:36:07 +08:00
2018-12-24 14:09:25 +08:00
const { drawColumnBG, drawFigure, drawValueText } = this
drawColumnBG()
2018-12-20 22:36:07 +08:00
2018-12-23 19:06:42 +08:00
drawFigure()
2018-12-24 14:09:25 +08:00
drawValueText()
2018-12-20 22:36:07 +08:00
},
calcColumnConfig () {
2018-12-24 14:09:25 +08:00
const { data: { data, spaceBetween }, labelAxisTagPos, axisOriginPos, horizon } = this
2018-12-20 22:36:07 +08:00
2018-12-23 19:06:42 +08:00
const columnData = this.columnData = data.filter(({ type }) =>
!(type === 'polyline' || type === 'smoothline'))
2018-12-20 22:36:07 +08:00
2018-12-23 19:06:42 +08:00
const columnItemSeriesNum = this.columnItemSeriesNum = columnData.length
2018-12-20 22:36:07 +08:00
2018-12-23 19:06:42 +08:00
const columnItemAllWidth = this.columnItemAllWidth = (horizon
? axisOriginPos[1] - labelAxisTagPos[0][1]
: labelAxisTagPos[0][0] - axisOriginPos[0]) * 2
2018-12-20 22:36:07 +08:00
2018-12-24 14:09:25 +08:00
const columnItemWidth = this.columnItemWidth = columnItemAllWidth / (columnItemSeriesNum + 1)
const spaceGap = columnItemWidth / (columnItemSeriesNum + 1)
let columnBGWidth = columnItemWidth * columnItemSeriesNum
spaceBetween && (columnBGWidth += spaceGap * (columnItemSeriesNum - 1))
this.columnBGWidth = columnBGWidth
2018-12-23 19:06:42 +08:00
},
calcColumnItemOffset () {
const { columnItemSeriesNum, columnItemAllWidth, columnItemWidth } = this
2018-12-20 22:36:07 +08:00
2018-12-24 14:09:25 +08:00
const { data: { spaceBetween, data } } = this
2018-12-20 22:36:07 +08:00
2018-12-23 19:06:42 +08:00
const halfColumnWidth = columnItemWidth / 2
const halfColumnItemAllWidth = columnItemAllWidth / 2
let columnItemOffset = new Array(columnItemSeriesNum).fill(0)
if (spaceBetween) {
const spaceGap = columnItemWidth / (columnItemSeriesNum + 1)
2018-12-24 14:09:25 +08:00
columnItemOffset = columnItemOffset.map((t, i) =>
2018-12-23 19:06:42 +08:00
spaceGap * (i + 1) + columnItemWidth * i + halfColumnWidth - halfColumnItemAllWidth)
}
if (!spaceBetween) {
2018-12-24 14:09:25 +08:00
columnItemOffset = columnItemOffset.map((t, i) =>
2018-12-23 19:06:42 +08:00
columnItemWidth * (i + 1) - halfColumnItemAllWidth)
}
2018-12-24 14:09:25 +08:00
this.columnItemOffset = data.map(({ type }) =>
(type === 'polyline' || type === 'smoothline')
? 0
: columnItemOffset.shift())
2018-12-20 22:36:07 +08:00
},
2018-12-23 19:06:42 +08:00
calcValuePointPos () {
const { getAxisPointsPos, valueAxisMaxMin, agValueAxisMaxMin } = this
const { labelAxisTagPos, deepClone, filterNull, multipleSum } = this
2018-12-20 22:36:07 +08:00
2018-12-23 19:06:42 +08:00
const { data: { data }, axisOriginPos, axisWH, horizon } = this
2018-12-20 22:36:07 +08:00
2018-12-23 19:06:42 +08:00
const dealAfterData = deepClone(data).map(({ data, againstAxis }) => {
if (!(data[0] instanceof Array)) return { data, againstAxis }
2018-12-20 22:36:07 +08:00
2018-12-23 19:06:42 +08:00
const td = data.map(series => series.map((v, i) => {
if (!v && v !== 0) return false
2018-12-20 22:36:07 +08:00
2018-12-23 19:06:42 +08:00
return multipleSum(...filterNull(series.slice(0, i + 1)))
}))
2018-12-20 22:36:07 +08:00
2018-12-23 19:06:42 +08:00
return { data: td, againstAxis }
})
2018-12-20 22:36:07 +08:00
2018-12-23 19:06:42 +08:00
this.valuePointPos = dealAfterData.map(({ data, againstAxis }) =>
getAxisPointsPos(
againstAxis ? agValueAxisMaxMin : valueAxisMaxMin,
data,
axisOriginPos,
axisWH,
labelAxisTagPos,
horizon
))
2018-12-20 22:36:07 +08:00
},
2018-12-24 14:09:25 +08:00
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 } = this
ctx.lineCap = roundColumn ? 'round' : 'butt'
labelAxisTagPos.forEach(([x, y]) => {
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()
})
},
2018-12-23 19:06:42 +08:00
drawFigure () {
2018-12-23 22:33:45 +08:00
const { data: { data }, valuePointPos } = this
2018-12-23 19:06:42 +08:00
2018-12-23 22:33:45 +08:00
const { drawColumn, drawEchelon, drawline } = this
2018-12-23 19:06:42 +08:00
data.forEach((series, i) => {
switch (series.type) {
2018-12-23 22:33:45 +08:00
case 'leftEchelon':
case 'rightEchelon': drawEchelon(series, valuePointPos[i], i)
2018-12-23 19:06:42 +08:00
break
2018-12-23 22:33:45 +08:00
case 'polyline':
case 'smoothline': drawline(series, valuePointPos[i], i)
2018-12-23 19:06:42 +08:00
break
2018-12-23 22:33:45 +08:00
default: drawColumn(series, valuePointPos[i], i)
2018-12-23 19:06:42 +08:00
break
}
})
},
getGradientColor (value, colors) {
const { data: { localGradient }, axisAnglePos, horizon } = this
2018-12-20 22:36:07 +08:00
2018-12-23 19:06:42 +08:00
const { ctx, canvas: { getLinearGradientColor } } = this
2018-12-20 22:36:07 +08:00
2018-12-23 19:06:42 +08:00
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) {
2018-12-23 22:33:45 +08:00
const { ctx, columnItemWidth, drawColors } = this
2018-12-20 22:36:07 +08:00
2018-12-23 19:06:42 +08:00
ctx.setLineDash([10, 0])
ctx.lineWidth = columnItemWidth
2018-12-20 22:36:07 +08:00
2018-12-23 22:33:45 +08:00
const color = fillColor || drawColors[i]
const colorNum = color.length
const drawColorNum = drawColors.length
const { columnItemOffset, labelAxisTagPos, getOffsetPoint } = this
2018-12-20 22:36:07 +08:00
2018-12-24 14:09:25 +08:00
const currentOffset = columnItemOffset[i]
2018-12-23 22:33:45 +08:00
const offsetTagPos = labelAxisTagPos.map(p => getOffsetPoint(p, currentOffset))
2018-12-20 22:36:07 +08:00
2018-12-23 22:33:45 +08:00
const { getGradientColor, getRoundColumnPoint, data: { roundColumn } } = this
2018-12-20 22:36:07 +08:00
2018-12-23 22:33:45 +08:00
ctx.lineCap = roundColumn ? 'round' : 'butt'
2018-12-20 22:36:07 +08:00
2018-12-23 22:33:45 +08:00
const seriesColumn = points[0][0] instanceof Array
2018-12-20 22:36:07 +08:00
2018-12-24 14:09:25 +08:00
seriesColumn && points.forEach((series, j) => {
let lastEnd = offsetTagPos[j]
series.forEach((item, k) => {
if (!item && item !== 0) return
2018-12-20 22:36:07 +08:00
2018-12-23 22:33:45 +08:00
const currentPoint = getOffsetPoint(item, currentOffset)
2018-12-20 22:36:07 +08:00
2018-12-23 22:33:45 +08:00
let columnPoint = [lastEnd, currentPoint]
roundColumn && (columnPoint = getRoundColumnPoint(columnPoint))
if (typeof color === 'string') {
2018-12-24 14:09:25 +08:00
ctx.strokeStyle = drawColors[(i + k) % drawColorNum]
2018-12-23 22:33:45 +08:00
} else {
2018-12-24 14:09:25 +08:00
ctx.strokeStyle = color[k % colorNum]
2018-12-23 22:33:45 +08:00
}
ctx.beginPath()
ctx.moveTo(...columnPoint[0])
ctx.lineTo(...columnPoint[1])
ctx.stroke()
lastEnd = currentPoint
})
})
!seriesColumn && points.forEach((point, i) => {
2018-12-24 14:09:25 +08:00
if (!point && point !== 0) return
2018-12-23 22:33:45 +08:00
let columnPoint = [offsetTagPos[i], getOffsetPoint(point, currentOffset)]
roundColumn && (columnPoint = getRoundColumnPoint(columnPoint))
2018-12-20 22:36:07 +08:00
ctx.beginPath()
2018-12-23 22:33:45 +08:00
ctx.strokeStyle = getGradientColor(point, color)
ctx.moveTo(...columnPoint[0])
ctx.lineTo(...columnPoint[1])
2018-12-20 22:36:07 +08:00
ctx.stroke()
})
},
2018-12-23 19:06:42 +08:00
getOffsetPoint ([x, y], offset) {
const { horizon } = this
2018-12-21 14:47:16 +08:00
2018-12-23 19:06:42 +08:00
return horizon
? [x, y + offset]
: [x + offset, y]
},
2018-12-24 14:09:25 +08:00
getOffsetPoints (points, offset) {
const { getOffsetPoint } = this
2018-12-23 22:33:45 +08:00
2018-12-24 18:24:52 +08:00
return points.map(point => point ? getOffsetPoint(point, offset) : false)
2018-12-24 14:09:25 +08:00
},
getRoundColumnPoint ([pa, pb], cw = false) {
const { horizon, columnItemWidth: dciw } = this
const columnWidth = cw || dciw
const radius = columnWidth / 2
2018-12-23 22:33:45 +08:00
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
2018-12-24 14:09:25 +08:00
const currentOffset = columnItemOffset[i]
2018-12-23 22:33:45 +08:00
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
},
2018-12-24 14:09:25 +08:00
drawValueText () {
const { data: { showValueText }, horizon, columnItemOffset, getOffsetPoints } = this
2018-12-21 14:47:16 +08:00
2018-12-24 14:09:25 +08:00
if (!showValueText) return
2018-12-21 14:47:16 +08:00
2018-12-24 14:09:25 +08:00
const { data: { valueTextFontSize, valueTextColor, valueTextOffset, data } } = this
2018-12-21 14:47:16 +08:00
2018-12-24 14:09:25 +08:00
const { ctx, defaultValueColor, defaultValueFontSize, valuePointPos, drawTexts } = this
2018-12-21 14:47:16 +08:00
2018-12-24 18:24:52 +08:00
const offset = horizon ? [5, 0] : [0, -5]
2018-12-21 14:47:16 +08:00
2018-12-24 14:09:25 +08:00
const trueOffset = valueTextOffset || offset
2018-12-21 14:47:16 +08:00
2018-12-24 14:09:25 +08:00
ctx.fillStyle = valueTextColor || defaultValueColor
2018-12-21 14:47:16 +08:00
2018-12-24 14:09:25 +08:00
ctx.font = `${valueTextFontSize || defaultValueFontSize}px Arial`
2018-12-21 14:47:16 +08:00
2018-12-24 14:09:25 +08:00
ctx.textAlign = horizon ? 'left' : 'center'
ctx.textBaseline = horizon ? 'middle' : 'bottom'
2018-12-21 14:47:16 +08:00
2018-12-24 14:09:25 +08:00
data.forEach(({ data }, i) => {
if (data[0] instanceof Array) {
data.forEach((td, j) =>
drawTexts(ctx,
td,
getOffsetPoints(valuePointPos[i][j], columnItemOffset[i]),
trueOffset))
2018-12-21 14:47:16 +08:00
2018-12-24 14:09:25 +08:00
return
}
2018-12-23 19:06:42 +08:00
2018-12-24 14:09:25 +08:00
drawTexts(ctx, data, getOffsetPoints(valuePointPos[i], columnItemOffset[i]), trueOffset)
})
},
drawTexts (ctx, values, points, [x, y] = [0, 0]) {
values.forEach((v, i) => {
if (!v && v !== 0) return
2018-12-23 19:06:42 +08:00
2018-12-24 14:09:25 +08:00
ctx.fillText(v, points[i][0] + x, points[i][1] + y)
})
},
drawCenterOriginTypeColumnChart () {}
2018-12-20 18:25:19 +08:00
},
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>