evui 3.4.110 → 3.4.111

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.110",
3
+ "version": "3.4.111",
4
4
  "description": "A EXEM Library project",
5
5
  "author": "exem <dev_client@ex-em.com>",
6
6
  "license": "MIT",
@@ -570,8 +570,9 @@ class EvChart {
570
570
 
571
571
  /**
572
572
  * Get chart DOM size and set canvas size
573
+ * @typedef {import('./model/index').ChartDOMSize} ChartDOMSize
573
574
  *
574
- * @returns {object} chart size information
575
+ * @returns {ChartDOMSize} chart size information
575
576
  */
576
577
  getChartDOMRect() {
577
578
  const rect = this.chartDOM?.getBoundingClientRect();
@@ -586,8 +587,9 @@ class EvChart {
586
587
 
587
588
  /**
588
589
  * Calculate chart size
590
+ * @typedef {import('./model/index').ChartRect} ChartRect
589
591
  *
590
- * @returns {object} chart size information
592
+ * @returns {ChartRect} chart size information
591
593
  */
592
594
  getChartRect() {
593
595
  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,23 @@ class Line {
33
34
  this.size = {
34
35
  comboOffset: 0,
35
36
  };
36
- this.usePassingValue = !!this.passingValue;
37
+ this.usePassingValue = !isUndefined(this.passingValue) && this.interpolation === 'linear';
37
38
  }
38
39
 
40
+ /**
41
+ * @typedef {Object} LineDrawParam
42
+ * @property {CanvasRenderingContext2D} ctx - 캔버스 렌더링 컨텍스트
43
+ * @property {object} chartRect - 차트 영역 정보
44
+ * @property {object} labelOffset - 라벨 오프셋 정보
45
+ * @property {object} axesSteps - 축 스텝 정보
46
+ * @property {object} [selectLabel] - 선택된 라벨 정보
47
+ * @property {object} [selectSeries] - 선택된 시리즈 정보
48
+ * @property {object} [legendHitInfo] - 범례 히트 정보
49
+ * @property {boolean} [isBrush] - 브러시 사용 여부
50
+ */
39
51
  /**
40
52
  * Draw series data
41
- * @param {object} param object for drawing series data
53
+ * @param {LineDrawParam} param object for drawing series data
42
54
  *
43
55
  * @returns {undefined}
44
56
  */
@@ -91,8 +103,6 @@ class Line {
91
103
 
92
104
  const endPoint = chartRect.y2 - labelOffset.bottom;
93
105
 
94
- let x;
95
- let y;
96
106
  let barAreaByCombo = 0;
97
107
 
98
108
  const minmaxX = axesSteps.x[this.xAxisIndex];
@@ -114,51 +124,39 @@ class Line {
114
124
  const getYPos = val => Canvas.calculateY(val, minmaxY.graphMin, minmaxY.graphMax, yArea, ysp);
115
125
 
116
126
  // draw line
117
- let needCutoff = false;
118
- this.data.reduce((prev, curr) => {
119
- x = getXPos(curr.x);
120
- y = getYPos(curr.y);
127
+ let prevValid;
128
+ this.data.forEach((curr) => {
129
+ let x = getXPos(curr.x);
130
+ let y = getYPos(curr.y);
131
+
132
+ if (this.isExistGrp && this.usePassingValue && curr.o === this.passingValue) {
133
+ y = getYPos(curr.b ?? 0);
134
+ }
121
135
 
122
136
  if (x !== null) {
123
137
  x += Util.aliasPixel(x);
124
138
  }
125
139
 
126
- if (this.usePassingValue) {
127
- if (curr.o === this.passingValue) {
128
- y = getYPos(prev.y);
140
+ curr.xp = x;
141
+ curr.yp = y;
129
142
 
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;
141
-
142
- return curr;
143
+ if (this.usePassingValue && curr.o === this.passingValue) {
144
+ if (!this.isExistGrp) {
145
+ return;
143
146
  }
144
147
  }
145
148
 
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) {
149
+ if ((isNil(prevValid?.y) && this.usePassingValue && this.passingValue !== prevValid?.o)
150
+ || (!this.usePassingValue && isNil(curr.o) && this.interpolation !== 'zero')
151
+ || (!this.usePassingValue && isNil(prevValid?.y))
152
+ || (isNil(curr.o) && curr.y == null && this.passingValue !== curr.o)) {
151
153
  ctx.moveTo(x, y);
152
- needCutoff = false;
153
154
  } else {
154
155
  ctx.lineTo(x, y);
155
156
  }
156
157
 
157
- curr.xp = x; // eslint-disable-line
158
- curr.yp = y; // eslint-disable-line
159
-
160
- return curr;
161
- }, this.data[0]);
158
+ prevValid = curr;
159
+ });
162
160
 
163
161
  ctx.stroke();
164
162
  if (this.segments) {
@@ -195,19 +193,22 @@ class Line {
195
193
  // ex) [10, passing, null, 10, 10, passing, 10] -> [[0, 1], [3, 6]]
196
194
  let start = null;
197
195
  let end = null;
198
- const valueArray = this.data.map(item => item?.o);
196
+ const valueArray = this.data.map(item => (item?.o));
197
+ /** @type {Array<[number, number]>} */
199
198
  const needFillDataIndexList = [];
200
199
  for (let i = 0; i < valueArray.length + 1; i++) {
201
- if (Util.isNullOrUndefined(valueArray[i])) {
200
+ const isNoneInterpolation = this.interpolation === 'none' || (this.usePassingValue && this.passingValue !== null);
201
+
202
+ if (isNoneInterpolation ? isNil(valueArray[i]) : isUndefined(valueArray[i])) {
202
203
  if (start !== null && end !== null) {
203
204
  const temp = valueArray.slice(start, i);
204
205
  const lastNormalValueIndex = temp.findLastIndex(
205
- item => item !== Util.isNullOrUndefined(item) && item !== this.passingValue);
206
+ item => !isNil(item) && item !== this.passingValue);
206
207
  needFillDataIndexList.push([start, start + lastNormalValueIndex]);
207
208
  start = null;
208
209
  end = null;
209
210
  }
210
- } else if (valueArray[i] === this.passingValue) {
211
+ } else if (this.usePassingValue && valueArray[i] === this.passingValue) {
211
212
  end = i;
212
213
  } else {
213
214
  start = start === null ? i : start;
@@ -231,7 +232,7 @@ class Line {
231
232
 
232
233
  if (ix === startIndex) {
233
234
  ctx.moveTo(currData.xp, currData.yp);
234
- } else if (this.isExistGrp || this.passingValue !== currData.o) {
235
+ } else if (this.isExistGrp || this.passingValue !== currData.o || (this.interpolation === 'zero' && isNil(currData.o))) {
235
236
  ctx.lineTo(currData.xp, currData.yp);
236
237
  }
237
238
 
@@ -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
+ */
@@ -50,9 +50,13 @@ const modules = {
50
50
 
51
51
  if (series && sData) {
52
52
  if (series.isExistGrp && series.stackIndex && !series.isOverlapping) {
53
- series.data = this.addSeriesStackDS(sData, label, series.bsIds, series.stackIndex);
53
+ series.data = this.addSeriesStackDS(
54
+ sData, label, series.bsIds, series.stackIndex, series.interpolation,
55
+ );
54
56
  } else {
55
- series.data = this.addSeriesDS(sData, label, series.isExistGrp);
57
+ series.data = this.addSeriesDS(
58
+ sData, label, series.isExistGrp, series.interpolation,
59
+ );
56
60
  }
57
61
  series.minMax = this.getSeriesMinMax(series.data);
58
62
  }
@@ -385,10 +389,13 @@ const modules = {
385
389
  * @param {object} label chart label
386
390
  * @param {array} bsIds stacked base data ID List
387
391
  * @param {number} sIdx series ordered index
392
+ * @param {import('./index').InterpolationType} interpolation interpolation type
388
393
  *
389
- * @returns {array} data for each series
394
+ * @typedef {import('./index').ChartSeriesDataPoint} ChartSeriesDataPoint
395
+ *
396
+ * @returns {ChartSeriesDataPoint[]} data for each series
390
397
  */
391
- addSeriesStackDS(data, label, bsIds, sIdx = 0) {
398
+ addSeriesStackDS(data, label, bsIds, sIdx = 0, interpolation = 'none') {
392
399
  const isHorizontal = this.options.horizontal;
393
400
  const sdata = [];
394
401
 
@@ -429,15 +436,14 @@ const modules = {
429
436
  if (oData != null) {
430
437
  gdata = bdata + oData;
431
438
  } else {
432
- gdata = null;
433
- bdata = 0;
439
+ gdata = odata;
434
440
  }
435
441
  } else {
436
442
  bdata = 0;
437
443
  gdata = oData;
438
444
  }
439
445
 
440
- sdata.push(this.addData(gdata, ldata, odata, bdata));
446
+ sdata.push(this.addData(gdata, ldata, odata, bdata, interpolation));
441
447
  }
442
448
  });
443
449
 
@@ -449,10 +455,13 @@ const modules = {
449
455
  * @param {object} data chart series info
450
456
  * @param {object} label chart label
451
457
  * @param {boolean} isBase is Base(bottommost) series at stack chart
458
+ * @param {import('./index').InterpolationType} interpolation interpolation type
459
+ *
460
+ * @typedef {import('./index').ChartSeriesDataPoint} ChartSeriesDataPoint
452
461
  *
453
- * @returns {array} data for each series
462
+ * @returns {ChartSeriesDataPoint[]} data for each series
454
463
  */
455
- addSeriesDS(data, label, isBase) {
464
+ addSeriesDS(data, label, isBase, interpolation = 'none') {
456
465
  const isHorizontal = this.options.horizontal;
457
466
  const sdata = [];
458
467
  const passingValue = this.seriesList[Object.keys(this.seriesList)[0]]?.passingValue;
@@ -470,7 +479,13 @@ const modules = {
470
479
  const isPassingValueWithStack = isBase
471
480
  && !Util.isNullOrUndefined(passingValue)
472
481
  && gdata === passingValue;
473
- sdata.push(this.addData(isPassingValueWithStack ? 0 : gdata, ldata, gdata));
482
+ sdata.push(this.addData(
483
+ isPassingValueWithStack ? 0 : gdata,
484
+ ldata,
485
+ gdata,
486
+ null,
487
+ interpolation,
488
+ ));
474
489
  }
475
490
  });
476
491
 
@@ -520,10 +535,13 @@ const modules = {
520
535
  * @param {object} ldata label data (x-axis value for vertical chart)
521
536
  * @param {object} odata original data (without stacked value)
522
537
  * @param {object} bdata base data (stacked value)
523
-
524
- * @returns {object} data for each graph point
538
+ * @param {import('./index').InterpolationType} interpolation interpolation type
539
+ *
540
+ * @typedef {import('./index').ChartSeriesDataPoint} ChartSeriesDataPoint
541
+ *
542
+ * @returns {ChartSeriesDataPoint} data for each graph point
525
543
  */
526
- addData(gdata, ldata, odata = null, bdata = null) {
544
+ addData(gdata, ldata, odata = null, bdata = null, interpolation = 'none') {
527
545
  let data;
528
546
  let gdataValue = null;
529
547
  let odataValue = null;
@@ -536,7 +554,7 @@ const modules = {
536
554
  gdataColor = gdata.color;
537
555
  dataTextColor = gdata.textColor;
538
556
  } else {
539
- gdataValue = gdata ?? null;
557
+ gdataValue = interpolation === 'zero' && !gdata ? bdata ?? 0 : gdata ?? null;
540
558
  }
541
559
 
542
560
  if (odata !== null && typeof odata === 'object') {
@@ -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
  }