From f8c2ada86938cf17bdbfa9f7bb9be38a8f2e07d3 Mon Sep 17 00:00:00 2001 From: jiaming <743192023@qq.com> Date: Thu, 20 Dec 2018 18:25:49 +0800 Subject: [PATCH] abtract label line component --- src/components/radarChart/index.vue | 31 +-- src/mixins/axisMixin.js | 305 ++++++++++++++++++++++++++++ src/mixins/canvasMixin.js | 43 ++++ src/mixins/colorsMixin.js | 18 ++ src/plugins/canvasExtend.js | 9 +- src/plugins/methodsExtend.js | 23 +++ src/views/demo/chart.vue | 46 +++++ 7 files changed, 444 insertions(+), 31 deletions(-) create mode 100644 src/mixins/axisMixin.js create mode 100644 src/mixins/canvasMixin.js create mode 100644 src/mixins/colorsMixin.js diff --git a/src/components/radarChart/index.vue b/src/components/radarChart/index.vue index 92b08c2..56bb58c 100644 --- a/src/components/radarChart/index.vue +++ b/src/components/radarChart/index.vue @@ -7,14 +7,7 @@ -
-
-
-
{{ label }}
-
-
+ @@ -539,27 +532,5 @@ export default { width: 100%; height: 100%; } - - .label-line { - display: flex; - flex-direction: row; - flex-wrap: wrap; - justify-content: center; - font-size: 10px; - - .label-item { - height: 20px; - display: flex; - flex-direction: row; - align-items: center; - margin: 0px 5px 5px 5px; - - :nth-child(1) { - width: 10px; - height: 10px; - margin-right: 5px; - } - } - } } diff --git a/src/mixins/axisMixin.js b/src/mixins/axisMixin.js new file mode 100644 index 0000000..025b46e --- /dev/null +++ b/src/mixins/axisMixin.js @@ -0,0 +1,305 @@ +export default { + data () { + return { + defaultHorizon: false, + defaultXAxisFS: 10, + defaultYAxisFS: 10, + defaultXOffset: 30, + defaultRotateAngel: 30, + + defaultXAxisLineColor: 'rgba(255, 255, 255, 0.3)', + defaultYAxisLineColor: 'rgba(255, 255, 255, 0.3)', + + horizon: false, + // user data's max and min value + valueMaxMin: [], + // axis's max and min value + axisMaxMin: [], + // label's max width where x and y Axis + labelXYMaxWidth: [], + valueAxisTag: [], + labelAxisTag: [], + xyAxisFS: [], + xyAxisUnitWidth: [], + axisMargin: [], + axisOriginPos: [], + axisWH: [], + valueTagPos: [], + labelTagPos: [], + valueTagGap: 0, + labelTagGap: 0, + xTagColor: '', + yTagColor: '', + xTagColorMul: false, + yTagColorMul: false + } + }, + methods: { + initAxis () { + const { calcMaxMinValue, calcValueAxisTag, calcLabelAxisTag, calcXYAxisFS } = this + + calcMaxMinValue() + + calcValueAxisTag() + + calcLabelAxisTag() + + calcXYAxisFS() + + const { calcXYLabelMaxWidth, calcAxisMargin, calcAxisOriginPos } = this + + calcXYLabelMaxWidth() + + calcAxisMargin() + + calcAxisOriginPos() + + const { calcAxisWH, calcValueTagPos, calcLabelTagPos, calcTagGap, calcTagColor } = this + + calcAxisWH() + + calcValueTagPos() + + calcLabelTagPos() + + calcTagGap() + + calcTagColor() + }, + calcMaxMinValue () { + const { data: { data }, multipleSum, filterNull, getArrayMaxMin } = this + + const trueValue = data.map(item => + item.map(v => + v ? (typeof v === 'number' ? v : multipleSum(...filterNull(v))) : false)) + + this.valueMaxMin = getArrayMaxMin(trueValue) + }, + calcValueAxisTag () { + const { valueMaxMin: [ valueMax, valueMin ], data, defaultHorizon } = this + + const { horizon } = data + + this.horizon = horizon || defaultHorizon + + let { max, min, num, fixed } = data[horizon ? 'x' : 'y'] + + let [trueMax, trueMin] = [max, min] + + const thirdValueMinus = parseInt((valueMax - valueMin) / 3) + + !max && (max !== 0) && (trueMax = valueMax + thirdValueMinus) + !min && (min !== 0) && (trueMin = valueMin - thirdValueMinus) + + const trueMinus = trueMax - trueMin + + !num && trueMinus < 9 && (num = trueMinus + 1) + !num && (num = 10) + + const valueGap = trueMinus / (num - 1) + + const valueAxisTag = this.valueAxisTag = Array(num).fill(0).map((t, i) => + (trueMin + i * valueGap).toFixed(fixed)) + + const lastValueAxisTagIndex = valueAxisTag.length - 1 + + this.axisMaxMin = [parseFloat(valueAxisTag[lastValueAxisTagIndex]), parseFloat(valueAxisTag[0])] + }, + calcLabelAxisTag () { + const { data, horizon } = this + + this.labelAxisTag = data[horizon ? 'y' : 'x'].data + }, + calcXYAxisFS () { + const { defaultXAxisFS, defaultYAxisFS } = this + + const { data: { x: { fontSize: xfs }, y: { fontSize: yfs } } } = this + + this.xyAxisFS = [xfs || defaultXAxisFS, yfs || defaultYAxisFS] + }, + calcXYLabelMaxWidth () { + const { ctx, valueAxisTag, labelAxisTag, horizon, xyAxisFS } = this + + const { canvas: { getTextsWidth } } = this + + const { data: { x: { unit: xUN }, y: { unit: yUN } } } = this + + ctx.font = `${xyAxisFS[0]}px Arial` + + this.labelXYMaxWidth[0] = Math.max(...getTextsWidth(ctx, horizon ? labelAxisTag : valueAxisTag)) + + this.xyAxisUnitWidth[0] = ctx.measureText(xUN || '').width + + ctx.font = `${xyAxisFS[1]}px Arial` + + this.labelXYMaxWidth[1] = Math.max(...getTextsWidth(ctx, horizon ? valueAxisTag : labelAxisTag)) + + this.xyAxisUnitWidth[1] = ctx.measureText(yUN || '').width + }, + calcAxisMargin () { + const { defaultXOffset, labelXYMaxWidth, data, xyAxisUnitWidth } = this + + const { offset: xOF, unitWidth: xUW } = data.x + + const { offset: yOF, unitHeight: yUH } = data.y + + this.axisMargin[0] = yUH || defaultXOffset + this.axisMargin[1] = xUW || xyAxisUnitWidth[0] + 10 + this.axisMargin[2] = xOF || defaultXOffset + this.axisMargin[3] = yOF || labelXYMaxWidth[1] + 10 + }, + calcAxisOriginPos () { + const { axisMargin, canvasWH } = this + + this.axisOriginPos[0] = axisMargin[3] + this.axisOriginPos[1] = canvasWH[1] - axisMargin[2] + + this.ctx.arc(...this.axisOriginPos, 3, 0, Math.PI * 2) + }, + calcAxisWH () { + const { axisMargin, canvasWH } = this + + this.axisWH[0] = canvasWH[0] - axisMargin[1] - axisMargin[3] + this.axisWH[1] = canvasWH[1] - axisMargin[0] - axisMargin[2] + }, + calcValueTagPos () { + const { axisWH, valueAxisTag, horizon, axisOriginPos: [x, y] } = this + + const valueTagNum = valueAxisTag.length + + const gapWidth = (horizon ? axisWH[0] : axisWH[1]) / (valueTagNum - 1) + + this.valueTagPos = new Array(valueTagNum).fill(0).map((t, i) => + horizon ? [x + gapWidth * i, y + 5] : [x - 5, y - gapWidth * i]) + }, + calcLabelTagPos () { + const { axisWH, labelAxisTag, horizon, axisOriginPos: [x, y], data, axisType } = this + + const { boundaryGap } = data + + const labelNum = labelAxisTag.length + + const gapAllWidth = horizon ? axisWH[1] : axisWH[0] + + const tempArray = new Array(labelNum).fill(0) + + if (axisType === 'column' || (axisType === 'line' && boundaryGap)) { + const gapWidth = gapAllWidth / labelNum + + const halfGapWidth = gapWidth / 2 + + this.labelTagPos = tempArray.map((t, i) => + horizon ? [x - 5, y - gapWidth * i - halfGapWidth] : [x + gapWidth * i + halfGapWidth, y + 5]) + } + + if (axisType === 'line' && !boundaryGap) { + const gapWidth = gapAllWidth / (labelNum - 1) + + this.labelTagPos = tempArray.map((t, i) => + horizon ? [x - 5, y - gapWidth] : [x + gapWidth * i, y + 5]) + } + }, + calcTagGap () { + const { horizon, valueTagPos, labelTagPos } = this + + const v = horizon ? '0' : '1' + + this.valueTagGap = Math.abs(valueTagPos[0][v] - valueTagPos[1][v]) + + const l = horizon ? '1' : '0' + + this.labelTagGap = Math.abs(labelTagPos[0][l] - labelTagPos[1][l]) + }, + calcTagColor () { + const { defaultXAxisLineColor, defaultYAxisLineColor, drawColors, data } = this + + const { x: { color: xc }, y: { color: yc } } = data + + let xTagColor = xc || defaultXAxisLineColor + + xTagColor === 'colors' && (xTagColor = drawColors) + + let yTagColor = yc || defaultYAxisLineColor + + yTagColor === 'colors' && (yTagColor = drawColors) + + this.xTagColor = xTagColor + + this.xTagColorMul = xTagColor instanceof Array + + this.yTagColor = yTagColor + + this.yTagColorMul = yTagColor instanceof Array + }, + drawAxis () { + const { drawAxisLine, drawAxisTag } = this + + drawAxisLine() + + drawAxisTag() + }, + drawAxisLine () { + const { ctx, defaultXAxisLineColor, defaultYAxisLineColor, axisOriginPos, axisWH, data } = this + + const { x: { lineColor: xlc }, y: { lineColor: ylc } } = data + + ctx.lineWidth = 1 + + ctx.strokeStyle = xlc || defaultXAxisLineColor + ctx.beginPath() + ctx.moveTo(...axisOriginPos) + ctx.lineTo(axisOriginPos[0] + axisWH[0], axisOriginPos[1]) + ctx.stroke() + + ctx.strokeStyle = ylc || defaultYAxisLineColor + ctx.beginPath() + ctx.moveTo(...axisOriginPos) + ctx.lineTo(axisOriginPos[0], axisOriginPos[1] - axisWH[1]) + + ctx.stroke() + }, + drawAxisTag () { + const { ctx, horizon, valueTagPos, labelTagPos, valueAxisTag, labelAxisTag } = this + + const { xTagColor, xTagColorMul, yTagColor, yTagColorMul, xyAxisFS } = this + + const xAxisData = horizon ? valueTagPos : labelTagPos + const yAxisData = horizon ? labelTagPos : valueTagPos + + const xTagData = horizon ? valueAxisTag : labelAxisTag + const yTagData = horizon ? labelAxisTag : valueAxisTag + + !xTagColorMul && (ctx.fillStyle = xTagColor) + + const xTagColorNum = xTagColor.length + + ctx.font = `${xyAxisFS[0]}px Arial` + + ctx.textAlign = 'center' + ctx.textBaseline = 'top' + + xAxisData.forEach((pos, i) => { + xTagColorMul && (ctx.fillStyle = xTagColor[i % xTagColorNum]) + + ctx.fillText(xTagData[i], ...pos) + }) + + !yTagColorMul && (ctx.fillStyle = yTagColor) + + const yTagColorNum = yTagColor.length + + ctx.font = `${xyAxisFS[1]}px Arial` + + ctx.textAlign = 'right' + ctx.textBaseline = 'middle' + + yAxisData.forEach((pos, i) => { + xTagColorMul && (ctx.fillStyle = yTagColor[i % yTagColorNum]) + + ctx.fillText(yTagData[i], ...pos) + }) + + this.ctx.fill() + } + } +} diff --git a/src/mixins/canvasMixin.js b/src/mixins/canvasMixin.js new file mode 100644 index 0000000..c52720c --- /dev/null +++ b/src/mixins/canvasMixin.js @@ -0,0 +1,43 @@ +export default { + data () { + return { + canvasDom: '', + canvasWH: [0, 0], + ctx: '', + centerPos: [] + } + }, + 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) + } + } +} diff --git a/src/mixins/colorsMixin.js b/src/mixins/colorsMixin.js new file mode 100644 index 0000000..b2684f5 --- /dev/null +++ b/src/mixins/colorsMixin.js @@ -0,0 +1,18 @@ +import defaultColors from '../config/color.js' + +export default { + data () { + return { + defaultColors, + + drawColors: '' + } + }, + methods: { + initColors () { + const { colors, defaultColors } = this + + this.drawColors = colors || defaultColors + } + } +} diff --git a/src/plugins/canvasExtend.js b/src/plugins/canvasExtend.js index 4f27fad..771075c 100644 --- a/src/plugins/canvasExtend.js +++ b/src/plugins/canvasExtend.js @@ -169,6 +169,12 @@ export function getCircleRadianPoint (x, y, radius, radian) { 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, @@ -180,7 +186,8 @@ const canvas = { drawPoints, getLinearGradientColor, getRadialGradientColor, - getCircleRadianPoint + getCircleRadianPoint, + getTextsWidth } export default function (Vue) { diff --git a/src/plugins/methodsExtend.js b/src/plugins/methodsExtend.js index 07843be..a84ec21 100644 --- a/src/plugins/methodsExtend.js +++ b/src/plugins/methodsExtend.js @@ -62,6 +62,26 @@ export function getPointToLineDistance (point, 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 default function (Vue) { Vue.prototype.deepClone = deepClone Vue.prototype.deleteArrayAllItems = deleteArrayAllItems @@ -71,4 +91,7 @@ export default function (Vue) { Vue.prototype.filterNull = filterNull Vue.prototype.getPointDistance = getPointDistance Vue.prototype.getPointToLineDistance = getPointToLineDistance + Vue.prototype.getArrayMaxMin = getArrayMaxMin + Vue.prototype.getArrayMax = getArrayMax + Vue.prototype.getArrayMin = getArrayMin } diff --git a/src/views/demo/chart.vue b/src/views/demo/chart.vue index 2038dfc..9f9aa24 100644 --- a/src/views/demo/chart.vue +++ b/src/views/demo/chart.vue @@ -36,6 +36,23 @@ colors: ['#9cf4a7', '#66d7ee', '#eee966', '#a866ee', '#ee8f66', '#ee66aa'] + + + +
+
Column-Chart
+ +<column-chart :data="data" :colors="colors" /> + + + +data: { + data: [] +} + +
+
+ @@ -483,12 +500,41 @@ export default { title: 'Attention', target: 'attention' }, + { + title: 'Column-Chart', + target: 'column-chart' + }, { title: 'Radar-Chart', target: 'radar-chart' } ], + columnChartData1: { + data: [ + [180, 290, 420, 200, 350, 219], + [ + [45, 32, 66], + [122, 49, 218], + [40, 129, 216], + [45, 66, 45], + [110, 120, 201], + [23, 40, 12] + ] + ], + x: { + data: ['西峡', '周口', '南阳', '驻马店', '郑州', '洛阳'] + }, + y: { + grid: true, + unit: '次', + min: 0, + max: 600 + }, + columnBG: 'rgba(250, 250, 250, 0.2)', + roundColumn: true + }, + radarChartData1: { data: [ {