evui 3.4.123 → 3.4.201

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.
@@ -25,21 +25,23 @@ const modules = {
25
25
  seriesIDs.forEach((seriesID) => {
26
26
  const series = this.seriesList[seriesID];
27
27
  const sData = data[seriesID];
28
+ const passingValue = series?.passingValue;
28
29
 
29
30
  if (series && sData) {
30
31
  series.data = this.addSeriesDSforScatter(sData);
31
- series.minMax = this.getSeriesMinMax(series.data);
32
+ series.minMax = this.getSeriesMinMax(series.data, passingValue);
32
33
  }
33
34
  });
34
35
  } else if (typeKey === 'heatMap') {
35
36
  seriesIDs.forEach((seriesID) => {
36
37
  const series = this.seriesList[seriesID];
38
+ const passingValue = series?.passingValue;
37
39
  const sData = data[seriesID];
38
40
 
39
41
  if (series && sData) {
40
42
  series.labels = label;
41
43
  series.data = this.addSeriesDSForHeatMap(sData);
42
- series.minMax = this.getSeriesMinMax(series.data);
44
+ series.minMax = this.getSeriesMinMax(series.data, passingValue);
43
45
  series.valueOpt = this.getSeriesValueOptForHeatMap(series);
44
46
  }
45
47
  });
@@ -68,7 +70,7 @@ const modules = {
68
70
  } else {
69
71
  series.data = this.addSeriesDS(sData, label, series.isExistGrp);
70
72
  }
71
- series.minMax = this.getSeriesMinMax(series.data);
73
+ series.minMax = this.getSeriesMinMax(series.data, series.passingValue);
72
74
  }
73
75
  });
74
76
  }
@@ -408,17 +410,21 @@ const modules = {
408
410
  const isHorizontal = this.options.horizontal;
409
411
  const sdata = [];
410
412
 
411
- const getBaseDataPosition = (baseIndex, dataIndex) => {
413
+ const getBaseDataPosition = (baseIndex, dataIndex, curr) => {
412
414
  const nextBaseSeriesIndex = baseIndex - 1;
413
415
  const baseSeries = this.seriesList[bsIds[baseIndex]];
414
416
  const baseDataList = baseSeries.data;
415
417
  const baseData = baseDataList[dataIndex];
416
418
  const position = isHorizontal ? baseData?.x : baseData?.y;
417
- const isPassingValue = baseSeries.passingValue === baseData?.o;
418
419
 
419
- if (isPassingValue || position == null || !baseSeries.show) {
420
+ const baseValue = baseData?.o;
421
+ const isPassingValue = !Util.isNullOrUndefined(baseSeries?.passingValue)
422
+ && baseSeries?.passingValue === baseValue;
423
+ const isSameSign = (curr >= 0 && baseValue >= 0) || (curr < 0 && baseValue < 0);
424
+
425
+ if (isPassingValue || position == null || !isSameSign || !baseSeries.show) {
420
426
  if (nextBaseSeriesIndex > -1) {
421
- return getBaseDataPosition(nextBaseSeriesIndex, dataIndex);
427
+ return getBaseDataPosition(nextBaseSeriesIndex, dataIndex, curr);
422
428
  }
423
429
 
424
430
  return 0;
@@ -429,7 +435,7 @@ const modules = {
429
435
 
430
436
  data.forEach((curr, index) => {
431
437
  const baseIndex = bsIds.length - 1 < 0 ? 0 : bsIds.length - 1;
432
- let bdata = getBaseDataPosition(baseIndex, index); // base(previous) series data
438
+ let bdata = getBaseDataPosition(baseIndex, index, curr); // base(previous) series data
433
439
  let odata = curr; // current series original data
434
440
  let ldata = label[index]; // label data
435
441
  let gdata = curr; // current series data which added previous series's value
@@ -582,30 +588,34 @@ const modules = {
582
588
  return data;
583
589
  },
584
590
 
585
-
586
591
  /**
587
592
  * Take series data to create min/max info for each series
588
593
  * @param {object} data series data
589
594
  *
590
595
  * @returns {object} min/max info for series
591
596
  */
592
- getSeriesMinMax(data) {
597
+ getSeriesMinMax(data, passingValue) {
593
598
  const def = { minX: null, minY: null, maxX: null, maxY: null, maxDomain: null };
594
599
  const isHorizontal = this.options.horizontal;
595
600
 
596
601
  if (data.length) {
602
+ const usePassingValue = !Util.isNullOrUndefined(passingValue);
603
+
597
604
  return data.reduce((acc, p, index) => {
598
605
  const minmax = acc;
599
606
  const px = p.x?.value || p.x;
600
607
  const py = p.y?.value || p.y;
608
+ const po = p.o?.value || p.o;
601
609
 
602
- if (px <= minmax.minX) {
610
+ if ((usePassingValue ? (po !== passingValue && px <= minmax.minX) : px <= minmax.minX)) {
603
611
  minmax.minX = (px === null) ? 0 : px;
604
612
  }
605
- if (py <= minmax.minY) {
613
+
614
+ if ((usePassingValue ? (po !== passingValue && py <= minmax.minY) : py <= minmax.minY)) {
606
615
  minmax.minY = (py === null) ? 0 : py;
607
616
  }
608
- if (px >= minmax.maxX) {
617
+
618
+ if ((usePassingValue ? (po !== passingValue && px >= minmax.maxX) : px >= minmax.maxX)) {
609
619
  minmax.maxX = (px === null) ? 0 : px;
610
620
 
611
621
  if (isHorizontal && px !== null) {
@@ -613,7 +623,8 @@ const modules = {
613
623
  minmax.maxDomainIndex = index;
614
624
  }
615
625
  }
616
- if (py >= minmax.maxY) {
626
+
627
+ if ((usePassingValue ? (po !== passingValue && py >= minmax.maxY) : py >= minmax.maxY)) {
617
628
  minmax.maxY = (py === null) ? 0 : py;
618
629
 
619
630
  if (!isHorizontal && py !== null) {
@@ -1256,13 +1267,25 @@ const modules = {
1256
1267
  minmax.y[axisY].min = smm.minY;
1257
1268
  }
1258
1269
  }
1259
- if (smm.maxX >= minmax.x[axisX].max) {
1270
+
1271
+ const isExistGrp = this.seriesList[key].isExistGrp;
1272
+ const maxXisNegative = minmax.x[axisX].max < 0;
1273
+
1274
+ if (isExistGrp && maxXisNegative) {
1275
+ minmax.x[axisX].max = smm.maxX;
1276
+ minmax.x[axisX].maxSID = key;
1277
+ } else if (!minmax.x[axisX].max || smm.maxX >= minmax.x[axisX].max) {
1260
1278
  minmax.x[axisX].max = smm.maxX;
1261
1279
  minmax.x[axisX].maxSID = key;
1262
1280
  }
1263
- if (smm.maxY >= minmax.y[axisY].max) {
1281
+
1282
+ const maxYisNegative = minmax.y[axisY].max < 0;
1283
+ if (isExistGrp && maxYisNegative) {
1284
+ minmax.y[axisY].max = smm.maxY;
1285
+ minmax.y[axisY].maxSID = key;
1286
+ } else if (!minmax.y[axisY].max || smm.maxY >= minmax.y[axisY].max) {
1264
1287
  minmax.y[axisY].max = smm.maxY;
1265
- minmax.y[axisX].maxSID = key;
1288
+ minmax.y[axisY].maxSID = key;
1266
1289
  }
1267
1290
  }
1268
1291
 
@@ -183,8 +183,7 @@ const modules = {
183
183
 
184
184
  let useLegendSeries = [];
185
185
  if (groups) {
186
- useLegendSeries = groups.slice().reverse()
187
- .filter(sId => this.seriesList[sId].showLegend)
186
+ useLegendSeries = groups.filter(sId => this.seriesList[sId].showLegend)
188
187
  .map(sId => [sId, this.seriesList[sId]]);
189
188
  } else {
190
189
  useLegendSeries = Object.entries(this.seriesList)
@@ -221,9 +220,8 @@ const modules = {
221
220
  }
222
221
  },
223
222
  /**
224
- * Adds legends for each group in `groups` array, iterating through each series
225
- * within the group in reverse order. This ensures the legends align with the series
226
- * order as displayed in the chart. Only adds series with `showLegend` set to `true`.
223
+ * Adds legends for each group in `groups` array, iterating through each series within the group.
224
+ * Only adds series with `showLegend` set to `true`.
227
225
  *
228
226
  * @param {Array} groups - Array of groups containing series identifiers.
229
227
  * @param {Object} seriesList - Object containing all series, keyed by series ID.
@@ -232,7 +230,7 @@ const modules = {
232
230
  */
233
231
  addLegendForGroups(groups, seriesList, useTable) {
234
232
  groups.forEach((group) => {
235
- group.slice().reverse().forEach((sId) => {
233
+ group.forEach((sId) => {
236
234
  const series = seriesList[sId];
237
235
  if (series && series.showLegend) {
238
236
  this.addLegendBasedOnType(series, useTable);
@@ -93,8 +93,8 @@ class Scale {
93
93
  }
94
94
 
95
95
  if (this.startToZero) {
96
- minValue = 0;
97
- }
96
+ minValue = 0;
97
+ }
98
98
 
99
99
  if (maxValue === minValue) {
100
100
  maxValue += 1;
@@ -138,6 +138,7 @@ class Scale {
138
138
 
139
139
  /**
140
140
  * With range information, calculate how many labels in axis
141
+ * linear type은 scale.linear.js에서 처리
141
142
  * @param {object} range min/max information
142
143
  *
143
144
  * @returns {object} steps, interval, min/max graph value
@@ -339,6 +340,13 @@ class Scale {
339
340
  ctx.beginPath();
340
341
  ticks[ix] = axisMinForLabel + (ix * stepValue);
341
342
 
343
+ const isZeroLine = ticks[ix] === 0;
344
+ if (isZeroLine && this.zeroLineColor) {
345
+ ctx.strokeStyle = this.zeroLineColor;
346
+ } else {
347
+ ctx.strokeStyle = this.gridLineColor;
348
+ }
349
+
342
350
  linePosition = labelCenter + aliasPixel;
343
351
  labelText = this.getLabelFormat(Math.min(axisMax, ticks[ix]), {
344
352
  prev: ticks[ix - 1] ?? '',
@@ -435,15 +443,17 @@ class Scale {
435
443
  }
436
444
 
437
445
  const mergedPlotBandOpt = defaultsDeep({}, plotBand, PLOT_BAND_OPTION);
438
- const { from, to, label: labelOpt } = mergedPlotBandOpt;
446
+ const { from: userDefinedFrom, to: userDefinedTo, label: labelOpt } = mergedPlotBandOpt;
447
+ const from = userDefinedFrom ? Math.max(userDefinedFrom, axisMin) : axisMin;
448
+ const to = userDefinedTo ? Math.min(userDefinedTo, axisMax) : axisMax;
439
449
 
440
450
  this.setPlotBandStyle(mergedPlotBandOpt);
441
451
 
442
452
  let fromPos;
443
453
  let toPos;
444
454
  if (this.type === 'x') {
445
- fromPos = Canvas.calculateX(from ?? minX, axisMin, axisMax, xArea, minX);
446
- toPos = Canvas.calculateX(to ?? maxX, axisMin, axisMax, xArea, minX);
455
+ fromPos = Canvas.calculateX(from, axisMin, axisMax, xArea, minX);
456
+ toPos = Canvas.calculateX(to, axisMin, axisMax, xArea, minX);
447
457
 
448
458
  if (fromPos === null || toPos === null) {
449
459
  return;
@@ -451,8 +461,8 @@ class Scale {
451
461
 
452
462
  this.drawXPlotBand(fromPos, toPos, minX, maxX, minY, maxY);
453
463
  } else {
454
- fromPos = Canvas.calculateY(from ?? axisMin, axisMin, axisMax, yArea, maxY);
455
- toPos = Canvas.calculateY(to ?? axisMax, axisMin, axisMax, yArea, maxY);
464
+ fromPos = Canvas.calculateY(from, axisMin, axisMax, yArea, maxY);
465
+ toPos = Canvas.calculateY(to, axisMin, axisMax, yArea, maxY);
456
466
 
457
467
  if (fromPos === null || toPos === null) {
458
468
  return;
@@ -478,8 +488,6 @@ class Scale {
478
488
  const mergedPlotLineOpt = defaultsDeep({}, plotLine, PLOT_LINE_OPTION);
479
489
  const { value, label: labelOpt } = mergedPlotLineOpt;
480
490
 
481
- this.setPlotLineStyle(mergedPlotLineOpt);
482
-
483
491
  let dataPos;
484
492
  if (this.type === 'x') {
485
493
  dataPos = Canvas.calculateX(value, axisMin, axisMax, xArea, minX);
@@ -488,6 +496,7 @@ class Scale {
488
496
  return;
489
497
  }
490
498
 
499
+ this.setPlotLineStyle(mergedPlotLineOpt);
491
500
  this.drawXPlotLine(dataPos, minX, maxX, minY, maxY);
492
501
  } else {
493
502
  dataPos = Canvas.calculateY(value, axisMin, axisMax, yArea, maxY);
@@ -496,6 +505,7 @@ class Scale {
496
505
  return;
497
506
  }
498
507
 
508
+ this.setPlotLineStyle(mergedPlotLineOpt);
499
509
  this.drawYPlotLine(dataPos, minX, maxX, minY, maxY);
500
510
  }
501
511
 
@@ -25,15 +25,195 @@ class LinearScale extends Scale {
25
25
  * Calculate interval
26
26
  * @param {object} range range information
27
27
  *
28
- * @returns {number} interval
28
+ * @returns {number} interval (한 칸에 표시할 값의 간격)
29
29
  */
30
30
  getInterval(range) {
31
+ if (this.interval) return this.interval;
32
+
31
33
  const max = range.maxValue;
32
34
  const min = range.minValue;
33
- const step = range.maxSteps;
35
+ const steps = range.maxSteps;
36
+
37
+ // step이 0이면 interval 계산 불가
38
+ if (!steps || steps <= 0) return 0;
39
+
40
+ // startToZero이고, 최소값이 음수일 경우 0을 반드시 포함
41
+ if (this.startToZero && min < 0) {
42
+ const totalRange = Math.abs(min) + Math.abs(max);
43
+
44
+ // 비율로 나눔
45
+ const negativeRatio = Math.abs(min) / totalRange;
46
+ const positiveRatio = Math.abs(max) / totalRange;
47
+
48
+ // 각 방향에 최소 1칸 이상 배정되도록 보장
49
+ let negativeSteps = Math.max(1, Math.round(negativeRatio * steps));
50
+ let positiveSteps = Math.max(1, steps - negativeSteps);
51
+
52
+ // 다시 합이 steps보다 커질 수도 있으니, 조정
53
+ if (negativeSteps + positiveSteps > steps) {
54
+ // 가장 큰 쪽에서 하나 줄임
55
+ if (negativeRatio > positiveRatio) {
56
+ negativeSteps -= 1;
57
+ } else {
58
+ positiveSteps -= 1;
59
+ }
60
+ }
61
+
62
+ return Math.ceil(
63
+ Math.max(
64
+ Math.abs(min) / (negativeSteps || 1),
65
+ Math.abs(max) / (positiveSteps || 1),
66
+ ),
67
+ );
68
+ }
69
+ return Math.ceil((max - min) / steps);
70
+ }
71
+
72
+ /**
73
+ * With range information, calculate how many labels in axis
74
+ * @param {object} range min/max information
75
+ *
76
+ * @returns {object} steps, interval, min/max graph value
77
+ */
78
+ calculateSteps(range) {
79
+ const { maxValue, minValue } = range;
80
+ let { maxSteps = 1 } = range;
81
+
82
+ let interval = this.getInterval(range);
83
+ let graphMin = 0;
84
+ let graphMax = 0;
85
+
86
+ // 그래프 최대/최소 값 계산
87
+ if (minValue >= 0) {
88
+ // 전부 양수
89
+ graphMin = +minValue;
90
+ graphMax = Math.ceil(maxValue / interval) * interval;
91
+ } else if (maxValue >= 0) {
92
+ // 양수/음수 혼합
93
+ graphMin = Math.floor(minValue / interval) * interval;
94
+ graphMax = Math.ceil(maxValue / interval) * interval;
95
+ } else {
96
+ // 전부 음수
97
+ graphMax = +maxValue;
98
+ graphMin = Math.floor(minValue / interval) * interval;
99
+ }
100
+
101
+ const graphRange = graphMax - graphMin;
102
+ let numberOfSteps = Math.round(graphRange / interval);
103
+
104
+ // 특수 케이스: 양수 최소값, 최대값이 1일 경우
105
+ if (minValue > 0 && maxValue === 1) {
106
+ if (!this.decimalPoint) {
107
+ interval = 1;
108
+ numberOfSteps = 1;
109
+ maxSteps = 1;
110
+ } else if (maxSteps > 2) {
111
+ interval = 0.2;
112
+ numberOfSteps = 5;
113
+ maxSteps = 5;
114
+ } else {
115
+ interval = 0.5;
116
+ numberOfSteps = 2;
117
+ maxSteps = 2;
118
+ }
119
+ }
34
120
 
35
- return this.interval ? this.interval : Math.ceil((max - min) / step);
121
+ // 최대 스텝 조정
122
+ while (numberOfSteps > maxSteps) {
123
+ interval *= 2;
124
+ numberOfSteps = Math.round(graphRange / interval);
125
+ interval = Math.ceil(graphRange / numberOfSteps);
126
+ }
127
+
128
+ if (graphRange > (numberOfSteps * interval)) {
129
+ interval = Math.ceil(graphRange / numberOfSteps);
130
+ }
131
+
132
+ return {
133
+ steps: numberOfSteps,
134
+ interval,
135
+ graphMin,
136
+ graphMax,
137
+ };
36
138
  }
139
+
140
+ /**
141
+ * Calculate min/max value, label and size information for axis
142
+ * @param {object} minMax min/max information
143
+ * @param {object} scrollbarOpt scrollbar option
144
+ *
145
+ * @returns {object} min/max value and label
146
+ */
147
+ calculateScaleRange(minMax, scrollbarOpt) {
148
+ let maxValue;
149
+ let minValue;
150
+ let isDefaultMaxSameAsMin = false;
151
+
152
+ const range = scrollbarOpt?.use ? scrollbarOpt?.range : this.range;
153
+ if (Array.isArray(range) && range?.length === 2) {
154
+ if (this.options.type === 'heatMap') {
155
+ maxValue = range[1] > +minMax.max ? +minMax.max : range[1];
156
+ minValue = range[0] < +minMax.min ? +minMax.min : range[0];
157
+ } else {
158
+ maxValue = range[1];
159
+ minValue = range[0];
160
+ }
161
+ } else if (typeof range === 'function') {
162
+ [minValue, maxValue] = range(minMax.min, minMax.max);
163
+ } else {
164
+ maxValue = minMax.max;
165
+ minValue = minMax.min;
166
+ }
167
+
168
+ // autoScaleRatio 적용 케이스
169
+ if (this.autoScaleRatio) {
170
+ const temp = maxValue;
171
+ // 양수 방향에만 autoScaleRatio 적용
172
+ maxValue = Math.ceil(maxValue * (this.autoScaleRatio + 1));
173
+
174
+ if (maxValue > 0 && minValue < 0) {
175
+ // 양수/음수 혼합 케이스 -- 음수 방향에도 maxValue 증가분만큼 더하기
176
+ const diff = temp - maxValue;
177
+ minValue += diff;
178
+ } else if (maxValue < 0 && minValue < 0) {
179
+ // 전부 음수 케이스 -- 음수 방향에도 autoScaleRatio 적용
180
+ minValue = Math.ceil(minValue * (this.autoScaleRatio + 1));
181
+ }
182
+ }
183
+
184
+ // 0 기준 축 설정 케이스
185
+ if (this.startToZero) {
186
+ if (minValue > 0) {
187
+ minValue = 0;
188
+ }
189
+
190
+ if (maxValue < 0) {
191
+ maxValue = 0;
192
+ }
193
+ }
194
+
195
+ if (maxValue === minValue) {
196
+ maxValue += 1;
197
+ isDefaultMaxSameAsMin = true;
198
+ }
199
+
200
+ const minLabel = this.getLabelFormat(minValue);
201
+ const maxLabel = this.getLabelFormat(maxValue, {
202
+ isMaxValueSameAsMin: isDefaultMaxSameAsMin,
203
+ });
204
+
205
+ return {
206
+ min: minValue,
207
+ max: maxValue,
208
+ minLabel,
209
+ maxLabel,
210
+ size: Util.calcTextSize(
211
+ maxLabel,
212
+ Util.getLabelStyle(this.labelStyle),
213
+ this.labelStyle?.padding,
214
+ ),
215
+ };
216
+ }
37
217
  }
38
218
 
39
219
  export default LinearScale;