evui 3.4.116 → 3.4.117

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "evui",
3
- "version": "3.4.116",
3
+ "version": "3.4.117",
4
4
  "description": "A EXEM Library project",
5
5
  "author": "exem <dev_client@ex-em.com>",
6
6
  "license": "MIT",
@@ -576,8 +576,9 @@ class EvChart {
576
576
 
577
577
  /**
578
578
  * Get chart DOM size and set canvas size
579
+ * @typedef {import('./model/index').ChartDOMSize} ChartDOMSize
579
580
  *
580
- * @returns {object} chart size information
581
+ * @returns {ChartDOMSize} chart size information
581
582
  */
582
583
  getChartDOMRect() {
583
584
  const rect = this.chartDOM?.getBoundingClientRect();
@@ -592,8 +593,9 @@ class EvChart {
592
593
 
593
594
  /**
594
595
  * Calculate chart size
596
+ * @typedef {import('./model/index').ChartRect} ChartRect
595
597
  *
596
- * @returns {object} chart size information
598
+ * @returns {ChartRect} chart size information
597
599
  */
598
600
  getChartRect() {
599
601
  const { width, height } = this.getChartDOMRect();
@@ -1,4 +1,4 @@
1
- import { defaultsDeep } from 'lodash-es';
1
+ import { defaultsDeep, isNil, isUndefined } from 'lodash-es';
2
2
  import { COLOR, LINE_OPTION } from '../helpers/helpers.constant';
3
3
  import Util from '../helpers/helpers.util';
4
4
  import Canvas from '../helpers/helpers.canvas';
@@ -26,6 +26,7 @@ class Line {
26
26
  normal: { opacity: 1, lineWidth: 1 },
27
27
  highlight: { opacity: 1, lineWidth: 2 },
28
28
  };
29
+ /** @type {import('../model/index').ChartSeriesDataPoint[]} */
29
30
  this.data = [];
30
31
  this.beforeMouseXp = 0;
31
32
  this.beforeMouseYp = 0;
@@ -33,12 +34,26 @@ class Line {
33
34
  this.size = {
34
35
  comboOffset: 0,
35
36
  };
36
- this.usePassingValue = !!this.passingValue;
37
37
  }
38
38
 
39
+ useLinearInterpolation() {
40
+ return this.interpolation === 'linear' || (this.interpolation === 'none' && !!this.passingValue && this.hasPassingValueInData);
41
+ }
42
+
43
+ /**
44
+ * @typedef {Object} LineDrawParam
45
+ * @property {CanvasRenderingContext2D} ctx - 캔버스 렌더링 컨텍스트
46
+ * @property {object} chartRect - 차트 영역 정보
47
+ * @property {object} labelOffset - 라벨 오프셋 정보
48
+ * @property {object} axesSteps - 축 스텝 정보
49
+ * @property {object} [selectLabel] - 선택된 라벨 정보
50
+ * @property {object} [selectSeries] - 선택된 시리즈 정보
51
+ * @property {object} [legendHitInfo] - 범례 히트 정보
52
+ * @property {boolean} [isBrush] - 브러시 사용 여부
53
+ */
39
54
  /**
40
55
  * Draw series data
41
- * @param {object} param object for drawing series data
56
+ * @param {LineDrawParam} param object for drawing series data
42
57
  *
43
58
  * @returns {undefined}
44
59
  */
@@ -91,8 +106,8 @@ class Line {
91
106
 
92
107
  const endPoint = chartRect.y2 - labelOffset.bottom;
93
108
 
94
- let x;
95
- let y;
109
+ const isLinearInterpolation = this.useLinearInterpolation();
110
+
96
111
  let barAreaByCombo = 0;
97
112
 
98
113
  const minmaxX = axesSteps.x[this.xAxisIndex];
@@ -114,51 +129,37 @@ class Line {
114
129
  const getYPos = val => Canvas.calculateY(val, minmaxY.graphMin, minmaxY.graphMax, yArea, ysp);
115
130
 
116
131
  // draw line
117
- let needCutoff = false;
118
- this.data.reduce((prev, curr) => {
119
- x = getXPos(curr.x);
120
- y = getYPos(curr.y);
132
+ let prevValid;
133
+ this.data.forEach((curr) => {
134
+ let x = getXPos(curr.x);
135
+ let y = getYPos(curr.y);
136
+
137
+ if (this.isExistGrp && isLinearInterpolation && curr.o === null) {
138
+ y = getYPos(curr.b ?? 0);
139
+ }
121
140
 
122
141
  if (x !== null) {
123
142
  x += Util.aliasPixel(x);
124
143
  }
125
144
 
126
- if (this.usePassingValue) {
127
- if (curr.o === this.passingValue) {
128
- y = getYPos(prev.y);
129
-
130
- if (prev.o === null) {
131
- needCutoff = true;
132
- }
133
-
134
- if (this.isExistGrp && !needCutoff) {
135
- y = getYPos(curr.b ?? 0);
136
- ctx.lineTo(x, y);
137
- }
138
-
139
- curr.xp = x;
140
- curr.yp = y;
145
+ curr.xp = x;
146
+ curr.yp = y;
141
147
 
142
- return curr;
148
+ if (isLinearInterpolation && curr.o === null) {
149
+ if (!this.isExistGrp) {
150
+ return;
143
151
  }
144
152
  }
145
153
 
146
- const isNullValue = Util.isNullOrUndefined(prev.o)
147
- || Util.isNullOrUndefined(curr.o)
148
- || Util.isNullOrUndefined(curr.x)
149
- || Util.isNullOrUndefined(curr.y);
150
- if (isNullValue || needCutoff) {
154
+ if ((isNil(prevValid?.y) && !this.isExistGrp)
155
+ || (!isLinearInterpolation && (isNil(prevValid?.y) || isNil(curr.o)))) {
151
156
  ctx.moveTo(x, y);
152
- needCutoff = false;
153
157
  } else {
154
158
  ctx.lineTo(x, y);
155
159
  }
156
160
 
157
- curr.xp = x; // eslint-disable-line
158
- curr.yp = y; // eslint-disable-line
159
-
160
- return curr;
161
- }, this.data[0]);
161
+ prevValid = curr;
162
+ });
162
163
 
163
164
  ctx.stroke();
164
165
  if (this.segments) {
@@ -195,19 +196,21 @@ class Line {
195
196
  // ex) [10, passing, null, 10, 10, passing, 10] -> [[0, 1], [3, 6]]
196
197
  let start = null;
197
198
  let end = null;
198
- const valueArray = this.data.map(item => item?.o);
199
+ const valueArray = this.data.map(item => (item?.o));
200
+ /** @type {Array<[number, number]>} */
199
201
  const needFillDataIndexList = [];
200
202
  for (let i = 0; i < valueArray.length + 1; i++) {
201
- if (Util.isNullOrUndefined(valueArray[i])) {
203
+ if ((isLinearInterpolation && isUndefined(valueArray[i]))
204
+ || (!isLinearInterpolation && isNil(valueArray[i]))) {
202
205
  if (start !== null && end !== null) {
203
206
  const temp = valueArray.slice(start, i);
204
207
  const lastNormalValueIndex = temp.findLastIndex(
205
- item => item !== Util.isNullOrUndefined(item) && item !== this.passingValue);
208
+ item => !isNil(item) && item !== null);
206
209
  needFillDataIndexList.push([start, start + lastNormalValueIndex]);
207
210
  start = null;
208
211
  end = null;
209
212
  }
210
- } else if (valueArray[i] === this.passingValue) {
213
+ } else if (isLinearInterpolation && valueArray[i] === null) {
211
214
  end = i;
212
215
  } else {
213
216
  start = start === null ? i : start;
@@ -231,7 +234,7 @@ class Line {
231
234
 
232
235
  if (ix === startIndex) {
233
236
  ctx.moveTo(currData.xp, currData.yp);
234
- } else if (this.isExistGrp || this.passingValue !== currData.o) {
237
+ } else if (this.isExistGrp || currData.o !== null) {
235
238
  ctx.lineTo(currData.xp, currData.yp);
236
239
  }
237
240
 
@@ -256,14 +259,18 @@ class Line {
256
259
  ctx.strokeStyle = Util.colorStringToRgba(mainColor, mainColorOpacity);
257
260
  const focusStyle = Util.colorStringToRgba(pointFillColor, 1);
258
261
  const blurStyle = Util.colorStringToRgba(pointFillColor, pointFillColorOpacity);
262
+ const isLinearSingle = this.interpolation === 'linear' && this.data.filter(item => item.o !== null).length === 1;
259
263
 
260
264
  this.data.forEach((curr, ix) => {
261
- if (curr.xp === null || curr.yp === null || curr.o === this.passingValue) {
265
+ if (curr.xp === null || curr.yp === null || curr.o === null) {
262
266
  return;
263
267
  }
264
268
 
265
- const isSingle = Util.isNullOrUndefined(this.data[ix - 1]?.o)
266
- && Util.isNullOrUndefined(this.data[ix + 1]?.o);
269
+ const prevData = this.data[ix - 1]?.o;
270
+ const nextData = this.data[ix + 1]?.o;
271
+
272
+ const isSingle = (!isLinearInterpolation && isNil(prevData) && isNil(nextData))
273
+ || isLinearSingle;
267
274
  const isSelectedLabel = selectedLabelIndexList.includes(ix);
268
275
  if (this.point || isSingle || isSelectedLabel) {
269
276
  ctx.fillStyle = isSelectedLabel && !legendHitInfo ? focusStyle : blurStyle;
@@ -290,7 +297,7 @@ class Line {
290
297
  const { xp, yp, o } = gdata;
291
298
 
292
299
  ctx.save();
293
- if (xp !== null && yp !== null && o !== this.passingValue && this.pointHighlight) {
300
+ if (xp !== null && yp !== null && o !== null && this.pointHighlight) {
294
301
  ctx.strokeStyle = Util.colorStringToRgba(this.color, 0);
295
302
  ctx.fillStyle = Util.colorStringToRgba(this.color, this.highlight.maxShadowOpacity);
296
303
  Canvas.drawPoint(ctx, this.pointStyle, this.highlight.maxShadowSize, xp, yp);
@@ -320,6 +327,7 @@ class Line {
320
327
  const item = { data: null, hit: false, color: this.color };
321
328
  const gdata = this.data.filter(data => !Util.isNullOrUndefined(data.x));
322
329
  const SPARE_XP = 0.5;
330
+ const isLinearInterpolation = this.useLinearInterpolation();
323
331
 
324
332
  if (gdata?.length) {
325
333
  if (typeof dataIndex === 'number' && this.show) {
@@ -402,7 +410,7 @@ class Line {
402
410
  }
403
411
  }
404
412
 
405
- if (this.usePassingValue && item?.data?.o === this.passingValue) {
413
+ if (isLinearInterpolation && item?.data?.o === null) {
406
414
  item.data = null;
407
415
  }
408
416
 
@@ -3,13 +3,13 @@ import Util from './helpers.util';
3
3
  export default {
4
4
  /**
5
5
  * Calculate X position
6
- * @param {any} value graph value
6
+ * @param {number|null|undefined} value graph value
7
7
  * @param {number} min min value
8
8
  * @param {number} max max value
9
9
  * @param {number} area height for axis
10
10
  * @param {number} startPoint startPoint
11
11
  *
12
- * @returns {any} position
12
+ * @returns {number|null} position
13
13
  */
14
14
  calculateX(value, min, max, area, startPoint = 0) {
15
15
  if (value === null || value === undefined) {
@@ -45,13 +45,13 @@ export default {
45
45
 
46
46
  /**
47
47
  * Calculate Y position
48
- * @param {any} value graph value
48
+ * @param {number|null|undefined} value graph value
49
49
  * @param {number} min min value
50
50
  * @param {number} max max value
51
51
  * @param {number} area height for axis
52
52
  * @param {number} startPoint startPoint
53
53
  *
54
- * @returns {any} position
54
+ * @returns {number|null} position
55
55
  */
56
56
  calculateY(value, min, max, area, startPoint = 0) {
57
57
  let calcY;
@@ -45,6 +45,7 @@ export const LINE_OPTION = {
45
45
  fillOpacity: 0.4,
46
46
  showLegend: true,
47
47
  passingValue: null,
48
+ interpolation: 'none',
48
49
  };
49
50
 
50
51
  export const BAR_OPTION = {
@@ -2,3 +2,49 @@ import Store from './model.store';
2
2
  import Series from './model.series';
3
3
 
4
4
  export default { Store, Series };
5
+
6
+
7
+ /**
8
+ * @typedef {Object} ChartDOMSize
9
+ * @property {number} width - 차트 DOM의 너비
10
+ * @property {number} height - 차트 DOM의 높이
11
+ */
12
+
13
+ /**
14
+ * @typedef {Object} ChartRect
15
+ * @property {number} x1 - 차트 영역의 시작 X 좌표
16
+ * @property {number} x2 - 차트 영역의 끝 X 좌표
17
+ * @property {number} y1 - 차트 영역의 시작 Y 좌표
18
+ * @property {number} y2 - 차트 영역의 끝 Y 좌표
19
+ * @property {number} chartWidth - 실제 차트 그리기 영역의 너비
20
+ * @property {number} chartHeight - 실제 차트 그리기 영역의 높이
21
+ * @property {number} width - 전체 차트 컨테이너의 너비
22
+ * @property {number} height - 전체 차트 컨테이너의 높이
23
+ */
24
+
25
+
26
+ /**
27
+ * @typedef {Object} MouseLabelValue
28
+ * @property {string|number} labelVal - 마우스 위치에 해당하는 라벨 값
29
+ * @property {number} labelIdx - 라벨 인덱스 (없으면 -1)
30
+ */
31
+
32
+
33
+ /**
34
+ * @typedef {Object} ChartSeriesDataPoint
35
+ * @property {number|null} x - x축 값 또는 라벨
36
+ * @property {number|null} y - y축 값 또는 데이터 값
37
+ * @property {number|null} o - 원본 데이터 값
38
+ * @property {number|null} b - 스택형 차트의 베이스 값
39
+ * @property {number|null} xp - x좌표 위치(픽셀 등)
40
+ * @property {number|null} yp - y좌표 위치(픽셀 등)
41
+ * @property {number|null} w - 너비
42
+ * @property {number|null} h - 높이
43
+ * @property {string|null} dataColor - 데이터 색상
44
+ * @property {string|null} dataTextColor - 텍스트 색상
45
+ */
46
+
47
+
48
+ /**
49
+ * @typedef {'none' | 'linear' | 'zero'} InterpolationType
50
+ */
@@ -46,7 +46,19 @@ const modules = {
46
46
  } else {
47
47
  seriesIDs.forEach((seriesID) => {
48
48
  const series = this.seriesList[seriesID];
49
- const sData = data[seriesID];
49
+
50
+ const hasPassingValueInData = data[seriesID].some(item => item === series.passingValue);
51
+ series.hasPassingValueInData = hasPassingValueInData;
52
+
53
+ const sData = data[seriesID].map((item) => {
54
+ if (series.interpolation === 'zero' && !item) {
55
+ return 0;
56
+ }
57
+ if (item === series.passingValue) {
58
+ return null;
59
+ }
60
+ return item;
61
+ });
50
62
 
51
63
  if (series && sData) {
52
64
  if (series.isExistGrp && series.stackIndex && !series.isOverlapping) {
@@ -386,7 +398,9 @@ const modules = {
386
398
  * @param {array} bsIds stacked base data ID List
387
399
  * @param {number} sIdx series ordered index
388
400
  *
389
- * @returns {array} data for each series
401
+ * @typedef {import('./index').ChartSeriesDataPoint} ChartSeriesDataPoint
402
+ *
403
+ * @returns {ChartSeriesDataPoint[]} data for each series
390
404
  */
391
405
  addSeriesStackDS(data, label, bsIds, sIdx = 0) {
392
406
  const isHorizontal = this.options.horizontal;
@@ -429,8 +443,7 @@ const modules = {
429
443
  if (oData != null) {
430
444
  gdata = bdata + oData;
431
445
  } else {
432
- gdata = null;
433
- bdata = 0;
446
+ gdata = odata;
434
447
  }
435
448
  } else {
436
449
  bdata = 0;
@@ -450,7 +463,9 @@ const modules = {
450
463
  * @param {object} label chart label
451
464
  * @param {boolean} isBase is Base(bottommost) series at stack chart
452
465
  *
453
- * @returns {array} data for each series
466
+ * @typedef {import('./index').ChartSeriesDataPoint} ChartSeriesDataPoint
467
+ *
468
+ * @returns {ChartSeriesDataPoint[]} data for each series
454
469
  */
455
470
  addSeriesDS(data, label, isBase) {
456
471
  const isHorizontal = this.options.horizontal;
@@ -470,7 +485,8 @@ const modules = {
470
485
  const isPassingValueWithStack = isBase
471
486
  && !Util.isNullOrUndefined(passingValue)
472
487
  && gdata === passingValue;
473
- sdata.push(this.addData(isPassingValueWithStack ? 0 : gdata, ldata, gdata));
488
+ sdata.push(this.addData(isPassingValueWithStack ? 0 : gdata, ldata, gdata,
489
+ ));
474
490
  }
475
491
  });
476
492
 
@@ -520,8 +536,10 @@ const modules = {
520
536
  * @param {object} ldata label data (x-axis value for vertical chart)
521
537
  * @param {object} odata original data (without stacked value)
522
538
  * @param {object} bdata base data (stacked value)
523
-
524
- * @returns {object} data for each graph point
539
+ *
540
+ * @typedef {import('./index').ChartSeriesDataPoint} ChartSeriesDataPoint
541
+ *
542
+ * @returns {ChartSeriesDataPoint} data for each graph point
525
543
  */
526
544
  addData(gdata, ldata, odata = null, bdata = null) {
527
545
  let data;
@@ -1008,12 +1026,17 @@ const modules = {
1008
1026
 
1009
1027
  return result;
1010
1028
  },
1029
+ /**
1030
+ * @typedef {Object} LabelInfoResult
1031
+ * @property {number} labelIndex - 선택된 라벨의 인덱스
1032
+ * @property {object} hitInfo - 해당 위치에서의 히트 정보 (getItemByPosition 반환값)
1033
+ */
1011
1034
  /**
1012
1035
  * Find label info by position x and y
1013
1036
  * @param {array} offset position x and y
1014
1037
  * @param {string | null} targetAxis target Axis Location ('xAxis', 'yAxis' , null)
1015
1038
  *
1016
- * @returns {object} clicked label information
1039
+ * @returns {LabelInfoResult} clicked label information
1017
1040
  */
1018
1041
  getLabelInfoByPosition(offset, targetAxis) {
1019
1042
  const [x, y] = offset;
@@ -1097,11 +1120,14 @@ const modules = {
1097
1120
 
1098
1121
  /**
1099
1122
  * Get current mouse target label value in label array or calculated using mouse position
1123
+ *
1124
+ * @typedef {import('./index').MouseLabelValue} MouseLabelValue
1125
+ *
1100
1126
  * @param {string} targetAxis target Axis Location ('xAxis', 'yAxis')
1101
1127
  * @param {array} offset return value from getMousePosition()
1102
1128
  * @param {number} labelIndex
1103
1129
  *
1104
- * @returns {object} current mouse target label value
1130
+ * @returns {MouseLabelValue} current mouse target label value
1105
1131
  */
1106
1132
  getCurMouseLabelVal(targetAxis, offset, labelIndex) {
1107
1133
  const { type: chartType, horizontal } = this.options;
@@ -868,7 +868,7 @@ const modules = {
868
868
  if (item?.data) {
869
869
  let gdata;
870
870
 
871
- if (item.data.o === null) {
871
+ if (item.data.o === null && series.interpolation !== 'zero') {
872
872
  if (!series.isExistGrp) {
873
873
  gdata = isHorizontal ? item.data.x : item.data.y;
874
874
  }