DemuMesDataV/src/components/radarChart/index.vue

548 lines
14 KiB
Vue
Raw Normal View History

2018-12-14 19:45:23 +08:00
<template>
<div class="radar-chart">
2018-12-16 17:57:09 +08:00
<loading v-if="!data" />
2018-12-14 19:45:23 +08:00
<div class="canvas-container">
<canvas :ref="ref" />
</div>
2018-12-20 18:25:49 +08:00
<label-line :label="data.labelLine" :colors="drawColors" />
2018-12-14 19:45:23 +08:00
</div>
</template>
<script>
2018-12-25 10:56:13 +08:00
import colorsMixin from '../../mixins/colorsMixin.js'
import canvasMixin from '../../mixins/canvasMixin.js'
2018-12-15 19:16:04 +08:00
2018-12-14 19:45:23 +08:00
export default {
name: 'RadarChart',
2018-12-25 10:56:13 +08:00
mixins: [canvasMixin, colorsMixin],
2018-12-14 19:45:23 +08:00
data () {
return {
ref: `radar-chart-${(new Date()).getTime()}`,
defaultRadius: 0.8,
2018-12-15 19:16:04 +08:00
defaultRingNum: 4,
defaultRingType: 'circle',
defaultRingLineType: 'dashed',
defaultRingLineColor: '#666',
defaultRingFillType: 'none',
defaultRayLineType: 'line',
2018-12-14 19:45:23 +08:00
defaultRayLineColor: '#666',
2018-12-15 19:16:04 +08:00
2018-12-14 19:45:23 +08:00
defaultRayLineOffset: Math.PI * -0.5,
defaultLabelColor: '#fff',
defaultLabelFS: 10,
2018-12-25 15:54:36 +08:00
defaultValueFontSize: 10,
defaultValueColor: '#999',
2018-12-15 19:16:04 +08:00
drawColors: '',
2018-12-14 19:45:23 +08:00
radius: '',
2018-12-15 19:16:04 +08:00
ringType: '',
2018-12-14 19:45:23 +08:00
rayLineRadianData: [],
2018-12-15 19:16:04 +08:00
ringRadiusData: [],
ringPolylineData: [],
ringLineDash: [],
ringlineMultipleColor: false,
ringLineColor: '',
ringFillType: '',
ringFillMultipleColor: false,
ringFillColor: '',
rayLineColor: '',
rayLineDash: '',
rayLineMultipleColor: false,
labelPosData: [],
labelColor: '',
labelFontSize: '',
labelMultipleColor: false,
2018-12-14 19:45:23 +08:00
valuePointData: []
}
},
props: ['data', 'colors'],
watch: {
data (d) {
const { reDraw } = this
reDraw(d)
},
color (d) {
const { reDraw } = this
reDraw(d)
}
},
methods: {
2018-12-25 10:56:13 +08:00
async init () {
const { initCanvas, data, draw } = this
2018-12-14 19:45:23 +08:00
2018-12-25 10:56:13 +08:00
await initCanvas()
2018-12-14 19:45:23 +08:00
2018-12-25 10:56:13 +08:00
data && draw()
2018-12-14 19:45:23 +08:00
},
draw () {
2018-12-15 19:16:04 +08:00
const { ctx, canvasWH } = this
ctx.clearRect(0, 0, ...canvasWH)
2018-12-25 10:56:13 +08:00
const { initColors, calcRadarRadius, calcRingType } = this
2018-12-15 19:16:04 +08:00
2018-12-25 10:56:13 +08:00
initColors()
2018-12-14 19:45:23 +08:00
calcRadarRadius()
2018-12-15 19:16:04 +08:00
calcRingType()
2018-12-16 17:57:09 +08:00
const { calcRayLineRadianData, calcRingRadiusData, calcRingPolylineData } = this
2018-12-15 19:16:04 +08:00
2018-12-16 17:57:09 +08:00
calcRayLineRadianData()
2018-12-15 19:16:04 +08:00
calcRingRadiusData()
calcRingPolylineData()
2018-12-16 17:57:09 +08:00
const { calcRingDrawConfig, calcRingFillConfig, fillRing } = this
2018-12-15 19:16:04 +08:00
2018-12-16 17:57:09 +08:00
calcRingDrawConfig()
2018-12-15 19:16:04 +08:00
calcRingFillConfig()
fillRing()
2018-12-16 17:57:09 +08:00
const { drawCircleRing, drawPolylineRing, calcRayLineConfig } = this
2018-12-15 19:16:04 +08:00
2018-12-16 17:57:09 +08:00
drawCircleRing()
2018-12-15 19:16:04 +08:00
drawPolylineRing()
calcRayLineConfig()
2018-12-14 19:45:23 +08:00
2018-12-16 17:57:09 +08:00
const { drawRayLine, calcLabelPosData, calcLabelConfig } = this
2018-12-14 19:45:23 +08:00
drawRayLine()
2018-12-15 19:16:04 +08:00
calcLabelPosData()
calcLabelConfig()
2018-12-16 17:57:09 +08:00
const { drawLable, caclValuePointData, fillRadar } = this
2018-12-14 19:45:23 +08:00
drawLable()
2018-12-16 17:57:09 +08:00
caclValuePointData()
2018-12-15 19:16:04 +08:00
2018-12-16 17:57:09 +08:00
fillRadar()
2018-12-25 15:54:36 +08:00
const { fillValueText } = this
fillValueText()
2018-12-15 19:16:04 +08:00
},
2018-12-14 19:45:23 +08:00
calcRadarRadius () {
const { canvasWH, data: { radius }, defaultRadius } = this
this.radius = Math.min(...canvasWH) * (radius || defaultRadius) * 0.5
},
2018-12-15 19:16:04 +08:00
calcRingType () {
const { data: { ringType }, defaultRingType } = this
2018-12-14 19:45:23 +08:00
2018-12-15 19:16:04 +08:00
this.ringType = ringType || defaultRingType
},
calcRayLineRadianData () {
2018-12-16 17:57:09 +08:00
const { data: { label, rayLineOffset }, defaultRayLineOffset } = this
2018-12-14 19:45:23 +08:00
2018-12-16 17:57:09 +08:00
const { data } = label
2018-12-14 19:45:23 +08:00
2018-12-15 19:16:04 +08:00
const fullRadian = Math.PI * 2
2018-12-14 19:45:23 +08:00
2018-12-15 19:16:04 +08:00
const radianGap = fullRadian / data.length
2018-12-14 19:45:23 +08:00
2018-12-15 19:16:04 +08:00
const radianOffset = rayLineOffset || defaultRayLineOffset
this.rayLineRadianData = data.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 () {
2018-12-25 10:56:13 +08:00
const { ringRadiusData, rayLineRadianData, centerPos } = this
2018-12-15 19:16:04 +08:00
const { canvas: { getCircleRadianPoint } } = this
this.ringPolylineData = ringRadiusData.map((r, i) =>
rayLineRadianData.map(radian =>
2018-12-25 10:56:13 +08:00
getCircleRadianPoint(...centerPos, r, radian)))
2018-12-15 19:16:04 +08:00
},
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 () {
2018-12-25 10:56:13 +08:00
const { ctx, centerPos, ringFillColor, ringType, radius, ringPolylineData } = this
2018-12-15 19:16:04 +08:00
const { canvas: { getRadialGradientColor, drawPolylinePath } } = this
2018-12-25 10:56:13 +08:00
const color = getRadialGradientColor(ctx, centerPos, 0, radius, ringFillColor)
2018-12-15 19:16:04 +08:00
ctx.beginPath()
2018-12-25 10:56:13 +08:00
ringType === 'circle' && ctx.arc(...centerPos, radius, 0, Math.PI * 2)
2018-12-15 19:16:04 +08:00
ringType === 'polyline' && drawPolylinePath(ctx, ringPolylineData[ringPolylineData.length - 1])
ctx.closePath()
ctx.fillStyle = color
ctx.fill()
},
fillMulCoverRing () {
2018-12-25 10:56:13 +08:00
const { ctx, ringType, ringFillColor, centerPos } = this
2018-12-15 19:16:04 +08:00
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()
2018-12-25 10:56:13 +08:00
ctx.arc(...centerPos, radius, 0, Math.PI * 2)
2018-12-15 19:16:04 +08:00
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
2018-12-25 10:56:13 +08:00
const { ringFillMultipleColor, centerPos, ringFillColor, ringPolylineData } = this
2018-12-15 19:16:04 +08:00
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()
2018-12-25 10:56:13 +08:00
ctx.arc(...centerPos, r - halfLineWidth, 0, Math.PI * 2)
2018-12-15 19:16:04 +08:00
ringFillMultipleColor && (ctx.strokeStyle = ringFillColor[i % colorNum])
ctx.stroke()
})
ctx.lineCap = 'round'
2018-12-25 10:56:13 +08:00
ctx.lineWidth = getPointToLineDistance(centerPos, ringPolylineData[0][0], ringPolylineData[0][1])
2018-12-15 19:16:04 +08:00
ringType === 'polyline' &&
ringRadiusData.map(r => r - halfLineWidth).map(r =>
rayLineRadianData.map(radian =>
2018-12-25 10:56:13 +08:00
getCircleRadianPoint(...centerPos, r, radian))).forEach((line, i) => {
2018-12-15 19:16:04 +08:00
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
2018-12-25 10:56:13 +08:00
const { ctx, ringRadiusData, centerPos, ringLineDash, ringlineMultipleColor, ringLineColor } = this
2018-12-15 19:16:04 +08:00
ctx.setLineDash(ringLineDash)
2018-12-14 19:45:23 +08:00
ctx.lineWidth = 1
2018-12-15 19:16:04 +08:00
!ringlineMultipleColor && (ctx.strokeStyle = ringLineColor)
const colorNum = ringLineColor.length
ringRadiusData.forEach((r, i) => {
2018-12-14 19:45:23 +08:00
ctx.beginPath()
2018-12-25 10:56:13 +08:00
ctx.arc(...centerPos, r, 0, Math.PI * 2)
2018-12-15 19:16:04 +08:00
ringlineMultipleColor && (ctx.strokeStyle = ringLineColor[i % colorNum])
2018-12-14 19:45:23 +08:00
ctx.stroke()
})
},
2018-12-15 19:16:04 +08:00
drawPolylineRing () {
const { data: { ringType }, defaultRingType } = this
2018-12-14 19:45:23 +08:00
2018-12-15 19:16:04 +08:00
if ((ringType && ringType !== 'polyline') || (!ringType && defaultRingType !== 'polyline')) return
2018-12-14 19:45:23 +08:00
2018-12-15 19:16:04 +08:00
const { ctx, ringPolylineData, ringLineDash, ringlineMultipleColor, ringLineColor } = this
2018-12-14 19:45:23 +08:00
2018-12-15 19:16:04 +08:00
const { canvas: { drawPolyline } } = this
2018-12-14 19:45:23 +08:00
2018-12-15 19:16:04 +08:00
const colorNum = ringLineColor.length
2018-12-14 19:45:23 +08:00
2018-12-15 19:16:04 +08:00
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
2018-12-14 19:45:23 +08:00
2018-12-15 19:16:04 +08:00
this.rayLineDash = (rayLineType || defaultRayLineType) === 'line' ? [10, 0] : [5, 5]
2018-12-14 19:45:23 +08:00
2018-12-16 17:57:09 +08:00
const trueRayLineColor = rayLineColor === 'colors' ? drawColors : (rayLineColor || defaultRayLineColor)
2018-12-14 19:45:23 +08:00
2018-12-16 17:57:09 +08:00
this.rayLineColor = trueRayLineColor
2018-12-14 19:45:23 +08:00
2018-12-16 17:57:09 +08:00
this.rayLineMultipleColor = typeof trueRayLineColor === 'object'
2018-12-15 19:16:04 +08:00
},
drawRayLine () {
2018-12-25 10:56:13 +08:00
const { ctx, rayLineColor, rayLineDash, ringPolylineData, centerPos, rayLineMultipleColor } = this
2018-12-15 19:16:04 +08:00
const lastRingLineIndex = ringPolylineData.length - 1
2018-12-14 19:45:23 +08:00
2018-12-15 19:16:04 +08:00
ctx.setLineDash(rayLineDash)
2018-12-14 19:45:23 +08:00
2018-12-15 19:16:04 +08:00
!rayLineMultipleColor && (ctx.strokeStyle = rayLineColor)
2018-12-14 19:45:23 +08:00
2018-12-15 19:16:04 +08:00
ctx.lineWidth = 1
const colorNum = rayLineColor.length
ringPolylineData[lastRingLineIndex].forEach((point, i) => {
2018-12-14 19:45:23 +08:00
ctx.beginPath()
2018-12-25 10:56:13 +08:00
ctx.moveTo(...centerPos)
2018-12-14 19:45:23 +08:00
2018-12-15 19:16:04 +08:00
ctx.lineTo(...point)
rayLineMultipleColor && (ctx.strokeStyle = rayLineColor[i % colorNum])
2018-12-14 19:45:23 +08:00
ctx.stroke()
})
},
2018-12-15 19:16:04 +08:00
calcLabelPosData () {
2018-12-25 10:56:13 +08:00
const { rayLineRadianData, radius, centerPos } = this
2018-12-15 19:16:04 +08:00
const { canvas: { getCircleRadianPoint } } = this
const labelRadius = radius + 10
this.labelPosData = rayLineRadianData.map(radian =>
2018-12-25 10:56:13 +08:00
getCircleRadianPoint(...centerPos, labelRadius, radian))
2018-12-15 19:16:04 +08:00
},
calcLabelConfig () {
const { defaultLabelColor, defaultLabelFS, drawColors } = this
const { data: { label: { color, fontSize } } } = this
2018-12-14 19:45:23 +08:00
2018-12-15 19:16:04 +08:00
const trueLabelColor = color === 'colors' ? drawColors : (color || defaultLabelColor)
2018-12-14 19:45:23 +08:00
2018-12-15 19:16:04 +08:00
this.labelMultipleColor = typeof trueLabelColor === 'object'
2018-12-14 19:45:23 +08:00
2018-12-15 19:16:04 +08:00
this.labelFontSize = fontSize || defaultLabelFS
2018-12-14 19:45:23 +08:00
2018-12-15 19:16:04 +08:00
this.labelColor = trueLabelColor
},
drawLable () {
2018-12-25 10:56:13 +08:00
const { ctx, centerPos: [x], labelPosData, labelColor, labelFontSize, labelMultipleColor } = this
2018-12-14 19:45:23 +08:00
2018-12-15 19:16:04 +08:00
const { data: { label: { data } } } = this
ctx.font = `${labelFontSize}px Arial`
!labelMultipleColor && (ctx.fillStyle = labelColor)
2018-12-14 19:45:23 +08:00
ctx.textBaseline = 'middle'
2018-12-15 19:16:04 +08:00
const colorNum = labelColor.length
2018-12-14 19:45:23 +08:00
2018-12-15 19:16:04 +08:00
labelPosData.forEach((pos, i) => {
2018-12-14 19:45:23 +08:00
ctx.textAlign = 'start'
2018-12-15 19:16:04 +08:00
pos[0] < x && (ctx.textAlign = 'end')
2018-12-14 19:45:23 +08:00
2018-12-15 19:16:04 +08:00
labelMultipleColor && (ctx.fillStyle = labelColor[i % colorNum])
2018-12-14 19:45:23 +08:00
2018-12-15 19:16:04 +08:00
ctx.fillText(data[i], ...pos)
2018-12-14 19:45:23 +08:00
})
},
caclValuePointData () {
2018-12-25 10:56:13 +08:00
const { data: { data, max }, centerPos, radius, rayLineRadianData } = this
2018-12-14 19:45:23 +08:00
2018-12-15 19:16:04 +08:00
const { canvas: { getCircleRadianPoint } } = this
2018-12-14 19:45:23 +08:00
2018-12-16 17:57:09 +08:00
const maxValue = max || Math.max(...data.map(({ data: td }) => Math.max(...td)))
2018-12-14 19:45:23 +08:00
const valueRadius = data.map(({ data: td }) =>
td.map(value =>
Number.isFinite(value)
? value / maxValue * radius : false))
this.valuePointData = valueRadius.map(td =>
td.map((r, i) =>
2018-12-25 10:56:13 +08:00
r ? getCircleRadianPoint(...centerPos, r, rayLineRadianData[i]) : false))
2018-12-14 19:45:23 +08:00
},
fillRadar () {
2018-12-16 17:57:09 +08:00
const { ctx, data: { data }, valuePointData, drawColors, filterNull } = this
2018-12-14 19:45:23 +08:00
2018-12-16 17:57:09 +08:00
const { canvas: { drawPolylinePath } } = this
2018-12-14 19:45:23 +08:00
2018-12-16 17:57:09 +08:00
const { color: { hexToRgb } } = this
2018-12-14 19:45:23 +08:00
2018-12-15 19:16:04 +08:00
const colorNum = drawColors.length
2018-12-14 19:45:23 +08:00
valuePointData.forEach((line, i) => {
2018-12-16 17:57:09 +08:00
const currentColor = drawColors[i % colorNum]
const lineColor = data[i].lineColor
const fillColor = data[i].fillColor
2018-12-14 19:45:23 +08:00
data[i].dashed ? ctx.setLineDash([5, 5]) : ctx.setLineDash([10, 0])
drawPolylinePath(ctx, filterNull(line), 1, true, true)
2018-12-16 17:57:09 +08:00
ctx.strokeStyle = lineColor || currentColor
2018-12-14 19:45:23 +08:00
ctx.stroke()
2018-12-16 17:57:09 +08:00
ctx.fillStyle = fillColor || hexToRgb(currentColor, 0.5)
2018-12-14 19:45:23 +08:00
ctx.fill()
})
},
2018-12-25 15:54:36 +08:00
fillValueText () {
const { data: { data, showValueText, valueTextFontSize } } = this
if (!showValueText) return
const { ctx, defaultValueFontSize } = this
ctx.font = `${valueTextFontSize || defaultValueFontSize}px Arial`
const { fillSeriesText } = this
data.forEach((item, i) => fillSeriesText(item, i))
},
fillSeriesText ({ valueTextColor, lineColor, fillColor, data }, 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, data, 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)
})
},
2018-12-14 19:45:23 +08:00
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>