From 76f42548b5fc51dfe7ffeb371cd6c1eb47447c64 Mon Sep 17 00:00:00 2001 From: Jin-Hee Park <53548023+jhee564@users.noreply.github.com> Date: Tue, 15 Jun 2021 13:37:26 +0900 Subject: [PATCH] =?UTF-8?q?[#823]=20[3.0]=20Stack=EC=B0=A8=ED=8A=B8=20?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=84=B0=20null=EB=8C=80=EC=9D=91=20(#830)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [#823] [3.0] Stack Chart 데이터 배열 속 null 값 예외처리 - data 배열 속 null이 존재 할 경우 canvas의 lineTo 메소드에 null값이 그대로 들어가 이상현상 발생하여 0으로 치환하는 로직 넣음 - Tooltip에서 StackChart의 경우 original데이터를 참조하는데 첫번째 시리즈만 original데이터를 넘기지 않아 추가함 * [#823] [3.0] Stack Chart 데이터 배열 속 null 값 예외처리 - stack 차트만 데이터 null에 대해서 0으로 치환하는 로직 수행하도록 수정 - Stack에서 값이 null인 series는 tooltip에 표시되지 않도록 수정 * [#823] [3.0] Stack Chart 데이터 배열 속 null 값 예외처리 ### line chart - Fill을 채우는 로직이 2번 수행되는 현상 제거 - 데이터가 null값일 경우 Fill을 정상적으로 수행하지 않는 로직 개선 - Stack 예제 코드 수정 ### 공통 - Stack Series의 y포지션을 계산하는 로직에서 바로 직전 series만 참조하는 로직 개선 - label타입이 'step'일 경우 null을 처리 하니 못하는 현상 제거 Co-authored-by: jinhee park --- docs/views/barChart/example/Stack.vue | 8 +-- docs/views/lineChart/example/Stack.vue | 30 ++++++++--- src/components/chart/element/element.line.js | 35 ++++++++++--- src/components/chart/helpers/helpers.util.js | 4 +- src/components/chart/model/model.series.js | 1 + src/components/chart/model/model.store.js | 52 ++++++++++++++----- .../chart/plugins/plugins.interaction.js | 6 ++- 7 files changed, 98 insertions(+), 38 deletions(-) diff --git a/docs/views/barChart/example/Stack.vue b/docs/views/barChart/example/Stack.vue index bdf47e80e..83b33ee56 100644 --- a/docs/views/barChart/example/Stack.vue +++ b/docs/views/barChart/example/Stack.vue @@ -20,10 +20,10 @@ ], labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'], data: { - series1: [100, 150, 51, 40, 50], - series2: [110, 100, 151, 50, 250], - series3: [200, 40, 50, 100, 250], - series4: [80, 100, 151, 150, 250], + series1: [null, 150, 51, 40, 50], + series2: [110, null, 151, 50, 250], + series3: [200, 40, null, 100, 250], + series4: [80, 100, 151, null, 250], }, }; diff --git a/docs/views/lineChart/example/Stack.vue b/docs/views/lineChart/example/Stack.vue index 287f2ca80..f0b64e611 100644 --- a/docs/views/lineChart/example/Stack.vue +++ b/docs/views/lineChart/example/Stack.vue @@ -13,11 +13,12 @@ const time = dayjs().format('YYYY-MM-DD HH:mm:ss'); const chartData = { series: { - series1: { name: 'series#1', fill: true, point: false }, - series2: { name: 'series#2', fill: true, point: false }, + series1: { name: 'series#1', fill: true, point: true }, + series2: { name: 'series#2', fill: true, point: true }, + series3: { name: 'series#3', fill: true, point: true }, }, groups: [ - ['series1', 'series2'], + ['series1', 'series2', 'series3'], ], labels: [ dayjs(time), @@ -29,8 +30,9 @@ dayjs(time).add(6, 'day'), ], data: { - series1: [100, 25, 36, 47, 0, 50, 80], - series2: [80, 36, 25, 47, 15, 100, 0], + series1: [100, 25, 47, 47, 40, 50, 100], + series2: [100, 25, 47, 47, null, null, null], + series3: [100, 50, 50, 50, null, 50, 100], }, }; @@ -45,18 +47,30 @@ show: true, position: 'right', }, + tooltip: { + use: true, + }, axesX: [{ type: 'time', - showGrid: true, - categoryMode: true, + showGrid: false, timeFormat: 'MM/DD', interval: 'day', + labelStyle: { + color: '#A4A4A4', + fontSize: '11px', + fontFamily: 'Roboto', + }, }], axesY: [{ type: 'linear', - showGrid: false, + showGrid: true, startToZero: true, autoScaleRatio: 0.1, + labelStyle: { + color: '#A4A4A4', + fontSize: '11px', + fontFamily: 'Roboto', + }, }], }; diff --git a/src/components/chart/element/element.line.js b/src/components/chart/element/element.line.js index 01aa66aa2..98a7f6e81 100644 --- a/src/components/chart/element/element.line.js +++ b/src/components/chart/element/element.line.js @@ -92,9 +92,12 @@ class Line { const xsp = chartRect.x1 + labelOffset.left + (barAreaByCombo / 2); const ysp = chartRect.y2 - labelOffset.bottom; + const getXPos = val => Canvas.calculateX(val, minmaxX.graphMin, minmaxX.graphMax, xArea, xsp); + const getYPos = val => Canvas.calculateY(val, minmaxY.graphMin, minmaxY.graphMax, yArea, ysp); + this.data.reduce((prev, curr, ix, item) => { - x = Canvas.calculateX(curr.x, minmaxX.graphMin, minmaxX.graphMax, xArea, xsp); - y = Canvas.calculateY(curr.y, minmaxY.graphMin, minmaxY.graphMax, yArea, ysp); + x = getXPos(curr.x); + y = getYPos(curr.y); if (x !== null) { x += Util.aliasPixel(x); @@ -102,7 +105,8 @@ class Line { if (y === null || x === null) { if (ix - 1 > -1) { - if (this.fill && prev.y !== null) { + // draw fill(area) series not stacked + if (this.fill && prev.y !== null && !this.stackIndex) { ctx.stroke(); ctx.lineTo(prev.xp, endPoint); ctx.lineTo(item[startFillIndex].xp, endPoint); @@ -132,12 +136,27 @@ class Line { if (this.fill && dataLen) { ctx.fillStyle = Util.colorStringToRgba(mainColor, fillOpacity); - if (this.stackIndex) { - this.data.slice().reverse().forEach((curr) => { - x = Canvas.calculateX(curr.x, minmaxX.graphMin, minmaxX.graphMax, xArea, xsp); - y = Canvas.calculateY(curr.b, minmaxY.graphMin, minmaxY.graphMax, yArea, ysp); - ctx.lineTo(x, y); + if (this.stackIndex) { + const reversedDataList = this.data.slice().reverse(); + reversedDataList.forEach((curr, ix) => { + x = getXPos(curr.x); + y = getYPos(curr.b); + + const prev = reversedDataList[ix - 1]; + if (curr.o !== null) { + if (prev && prev.o == null) { + ctx.moveTo(x, getYPos(curr.b + curr.o)); + } + + ctx.lineTo(x, y); + + if (ix === reversedDataList.length - 1) { + ctx.lineTo(x, getYPos(curr.b + curr.o)); + } + } else if (prev && prev.o) { + ctx.lineTo(getXPos(prev.x), getYPos(prev.b + prev.o)); + } }); } else if (startFillIndex < dataLen) { ctx.lineTo(this.data[dataLen - 1].xp, endPoint); diff --git a/src/components/chart/helpers/helpers.util.js b/src/components/chart/helpers/helpers.util.js index 4c1a0534d..5db578c8b 100644 --- a/src/components/chart/helpers/helpers.util.js +++ b/src/components/chart/helpers/helpers.util.js @@ -213,11 +213,11 @@ export default { }; array.forEach((item) => { - if (minMax.max.length < item.length) { + if (minMax?.max?.length < item?.length) { minMax.max = item; } - if (minMax.min.length > item.length) { + if (minMax?.min?.length > item?.length) { minMax.min = item; } }); diff --git a/src/components/chart/model/model.series.js b/src/components/chart/model/model.series.js index 85bbc5481..6d8db35a7 100644 --- a/src/components/chart/model/model.series.js +++ b/src/components/chart/model/model.series.js @@ -72,6 +72,7 @@ const modules = { series.groupIndex = gIdx; series.isExistGrp = true; series.bsId = prev; + series.bsIds = group.filter((item, idx) => item !== curr && sIdx > idx); if (!series.show) { interpolation--; diff --git a/src/components/chart/model/model.store.js b/src/components/chart/model/model.store.js index 148abd3e2..16ace7ba5 100644 --- a/src/components/chart/model/model.store.js +++ b/src/components/chart/model/model.store.js @@ -22,13 +22,13 @@ const modules = { } else { type.forEach((sId) => { const series = this.seriesList[sId]; + const sData = data[sId]; - if (series && data[sId]) { + if (series && sData) { if (series.isExistGrp && series.stackIndex) { - const bs = this.seriesList[series.bsId]; - series.data = this.addSeriesStackDS(data[sId], label, bs.data, series.stackIndex); + series.data = this.addSeriesStackDS(sData, label, series.bsIds, series.stackIndex); } else { - series.data = this.addSeriesDS(data[sId], label); + series.data = this.addSeriesDS(sData, label); } series.minMax = this.getSeriesMinMax(series.data); } @@ -173,20 +173,39 @@ const modules = { * Take data and label to create stack data for each series * @param {object} data chart series info * @param {object} label chart label - * @param {array} base stacked base data + * @param {array} bsIds stacked base data ID List * @param {number} sIdx series ordered index * * @returns {array} data for each series */ - addSeriesStackDS(data, label, base, sIdx = 0) { + addSeriesStackDS(data, label, bsIds, sIdx = 0) { const isHorizontal = this.options.horizontal; const sdata = []; + const getBaseDataPosition = (baseIndex, dataIndex) => { + const nextBaseSeriesIndex = baseIndex - 1; + const baseSeries = this.seriesList[bsIds[baseIndex]]; + const baseDataList = baseSeries.data; + const baseData = baseDataList[dataIndex]; + const position = isHorizontal ? baseData?.x : baseData?.y; + + if (position == null || !baseSeries.show) { + if (nextBaseSeriesIndex > -1) { + return getBaseDataPosition(nextBaseSeriesIndex, dataIndex); + } + + return 0; + } + + return position; + }; + data.forEach((curr, index) => { - let bdata = base[index]; - let odata = curr; - let ldata = label[index]; - let gdata = curr; + const baseIndex = bsIds.length - 1 < 0 ? 0 : bsIds.length - 1; + let bdata = getBaseDataPosition(baseIndex, index); // base(previous) series data + let odata = curr; // current series original data + let ldata = label[index]; // label data + let gdata = curr; // current series data which added previous series's value if (bdata != null && ldata != null) { if (gdata && typeof gdata === 'object' && (curr.x || curr.y)) { @@ -194,12 +213,17 @@ const modules = { ldata = isHorizontal ? curr.y : curr.x; } + const oData = odata?.value ?? odata; if (sIdx > 0) { - bdata = isHorizontal ? bdata.x : bdata.y; - gdata = bdata + (odata?.value ?? odata); + if (oData != null) { + gdata = bdata + oData; + } else { + gdata = null; + bdata = 0; + } } else { bdata = 0; - gdata = odata; + gdata = oData; } sdata.push(this.addData(gdata, ldata, odata, bdata)); @@ -230,7 +254,7 @@ const modules = { } if (ldata !== null) { - sdata.push(this.addData(gdata, ldata)); + sdata.push(this.addData(gdata, ldata, gdata)); } }); diff --git a/src/components/chart/plugins/plugins.interaction.js b/src/components/chart/plugins/plugins.interaction.js index c7b9e7f6b..8267d667e 100644 --- a/src/components/chart/plugins/plugins.interaction.js +++ b/src/components/chart/plugins/plugins.interaction.js @@ -309,9 +309,11 @@ const modules = { let gdata; if (item.data.o === null) { - gdata = isHorizontal ? item.data.x : item.data.y; + if (!series.isExistGrp) { + gdata = isHorizontal ? item.data.x : item.data.y; + } } else if (!isNaN(item.data.o)) { - gdata = isHorizontal ? item.data.x : item.data.y; + gdata = item.data.o; } if (gdata !== null && gdata !== undefined) {