evui 3.4.129 → 3.4.131

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.
@@ -1,6 +1,6 @@
1
1
  import { convertToPercent } from '@/common/utils';
2
2
  import debounce from '@/common/utils.debounce';
3
- import { inRange, isNil } from 'lodash-es';
3
+ import { inRange } from 'lodash-es';
4
4
  import Canvas from '../helpers/helpers.canvas';
5
5
  import Util from '../helpers/helpers.util';
6
6
 
@@ -783,8 +783,8 @@ const modules = {
783
783
  y1: this.chartRect.y1 + this.labelOffset.top,
784
784
  y2: this.chartRect.y2 - this.labelOffset.bottom,
785
785
  };
786
- const mouseXIp = 5; // mouseInterpolation - increased for better edge detection
787
- const mouseYIp = 10;
786
+ const mouseXIp = 15; // mouseInterpolation - 넓은 범위에서 감지
787
+ const mouseYIp = 15; // Y축도 동일하게 증가
788
788
  const options = this.options;
789
789
 
790
790
  if (offsetX >= (graphPos.x1 - mouseXIp) && offsetX <= (graphPos.x2 + mouseXIp)
@@ -858,10 +858,18 @@ const modules = {
858
858
  *
859
859
  * @returns {undefined}
860
860
  */
861
- drawSyncedIndicator({ horizontal, label, mousePosition, useAxisTrigger }) {
861
+ drawSyncedIndicator({ horizontal, label, mousePosition, dataLabel, isTooltipBased }) {
862
862
  if (!mousePosition || !!horizontal !== !!this.options.horizontal) {
863
863
  return;
864
864
  }
865
+
866
+ // tooltip 기반 동기화인 경우
867
+ if (isTooltipBased) {
868
+ this.drawSyncedIndicatorForTooltip({ dataLabel, mousePosition });
869
+ return;
870
+ }
871
+
872
+ // 기존 시간 기반 동기화
865
873
  if (
866
874
  this.options.syncHover === false
867
875
  || (!horizontal && !this.options.axesX.every(({ type }) => type === 'time'))
@@ -882,7 +890,6 @@ const modules = {
882
890
  }
883
891
 
884
892
  this.overlayClear();
885
-
886
893
  const graphPos = {
887
894
  x1: this.chartRect.x1 + this.labelOffset.left,
888
895
  x2: this.chartRect.x2 - this.labelOffset.right,
@@ -890,17 +897,7 @@ const modules = {
890
897
  y2: this.chartRect.y2 - this.labelOffset.bottom,
891
898
  };
892
899
 
893
- if (useAxisTrigger && label) {
894
- const matchIndex = this.data.labels?.findIndex(l => l?.valueOf() === label?.valueOf());
895
- if (matchIndex >= 0) {
896
- const seriesId = Object.keys(this.seriesList)?.[0];
897
- const dataPoint = this.seriesList?.[seriesId]?.data?.[matchIndex];
898
- if (dataPoint?.xp !== undefined && dataPoint?.xp !== null) {
899
- const yPosition = !isNil(dataPoint.yp) ? dataPoint.yp : (graphPos.y1 + graphPos.y2) / 2;
900
- this.drawIndicator([dataPoint.xp, yPosition], this.options.indicator.color);
901
- }
902
- }
903
- } else if (horizontal) {
900
+ if (horizontal) {
904
901
  const chartHeight = graphPos.y2 - graphPos.y1;
905
902
  const offsetY = (chartHeight * (label - fromTime)) / (toTime - fromTime) + graphPos.y1;
906
903
  this.drawIndicator([graphPos.x2, offsetY], this.options.indicator.color);
@@ -911,6 +908,68 @@ const modules = {
911
908
  }
912
909
  },
913
910
 
911
+
912
+ /**
913
+ * 제공된 dataLabel과 일치하는 Label이 있다면 indicator를 그림
914
+ * @param {object} dataLabel data label
915
+ * @param {object} mousePosition mouse position
916
+ *
917
+ * @returns {undefined}
918
+ */
919
+ drawSyncedIndicatorForTooltip({ dataLabel, mousePosition }) {
920
+ if (!this.data?.labels || !dataLabel) {
921
+ return;
922
+ }
923
+
924
+ const matchingLabelIndex = this.data.labels.findIndex(
925
+ label => label?.valueOf() === dataLabel?.valueOf(),
926
+ );
927
+ if (matchingLabelIndex === -1) {
928
+ this.overlayClear();
929
+ return;
930
+ }
931
+
932
+ const { horizontal } = this.options;
933
+ const { top, bottom, left, right } = this.chartDOM.getBoundingClientRect();
934
+ const isHoveredChart = inRange(mousePosition[0], left, right)
935
+ && inRange(mousePosition[1], bottom, top);
936
+ if (isHoveredChart) {
937
+ return;
938
+ }
939
+
940
+ this.overlayClear();
941
+
942
+ const graphPos = {
943
+ x1: this.chartRect.x1 + this.labelOffset.left,
944
+ x2: this.chartRect.x2 - this.labelOffset.right,
945
+ y1: this.chartRect.y1 + this.labelOffset.top,
946
+ y2: this.chartRect.y2 - this.labelOffset.bottom,
947
+ };
948
+
949
+ const labelsCount = this.data.labels.length;
950
+ let indicatorPosition;
951
+
952
+ if (horizontal) {
953
+ const chartHeight = graphPos.y2 - graphPos.y1;
954
+ // CategoryMode인 경우 라벨들이 균등 간격으로 배치됨
955
+ const isCategoryMode = this.options.axesY?.some(axis => axis.categoryMode);
956
+ const positionY = isCategoryMode
957
+ ? graphPos.y1 + (chartHeight * (matchingLabelIndex + 0.5)) / labelsCount
958
+ : graphPos.y1 + (chartHeight * matchingLabelIndex) / (labelsCount - 1);
959
+ indicatorPosition = [graphPos.x2, positionY];
960
+ } else {
961
+ const chartWidth = graphPos.x2 - graphPos.x1;
962
+ // CategoryMode인 경우 라벨들이 균등 간격으로 배치됨
963
+ const isCategoryMode = this.options.axesX?.some(axis => axis.categoryMode);
964
+ const positionX = isCategoryMode
965
+ ? graphPos.x1 + (chartWidth * (matchingLabelIndex + 0.5)) / labelsCount
966
+ : graphPos.x1 + (chartWidth * matchingLabelIndex) / (labelsCount - 1);
967
+ indicatorPosition = [positionX, graphPos.y2];
968
+ }
969
+
970
+ this.drawIndicator(indicatorPosition, this.options.indicator.color);
971
+ },
972
+
914
973
  /**
915
974
  * Clear tooltip canvas
916
975
  *
@@ -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,7 +138,6 @@ class Scale {
138
138
 
139
139
  /**
140
140
  * With range information, calculate how many labels in axis
141
- * linear type은 scale.linear.js에서 처리
142
141
  * @param {object} range min/max information
143
142
  *
144
143
  * @returns {object} steps, interval, min/max graph value
@@ -340,13 +339,6 @@ class Scale {
340
339
  ctx.beginPath();
341
340
  ticks[ix] = axisMinForLabel + (ix * stepValue);
342
341
 
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
-
350
342
  linePosition = labelCenter + aliasPixel;
351
343
  labelText = this.getLabelFormat(Math.min(axisMax, ticks[ix]), {
352
344
  prev: ticks[ix - 1] ?? '',
@@ -443,17 +435,15 @@ class Scale {
443
435
  }
444
436
 
445
437
  const mergedPlotBandOpt = defaultsDeep({}, plotBand, PLOT_BAND_OPTION);
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;
438
+ const { from, to, label: labelOpt } = mergedPlotBandOpt;
449
439
 
450
440
  this.setPlotBandStyle(mergedPlotBandOpt);
451
441
 
452
442
  let fromPos;
453
443
  let toPos;
454
444
  if (this.type === 'x') {
455
- fromPos = Canvas.calculateX(from, axisMin, axisMax, xArea, minX);
456
- toPos = Canvas.calculateX(to, axisMin, axisMax, xArea, minX);
445
+ fromPos = Canvas.calculateX(from ?? minX, axisMin, axisMax, xArea, minX);
446
+ toPos = Canvas.calculateX(to ?? maxX, axisMin, axisMax, xArea, minX);
457
447
 
458
448
  if (fromPos === null || toPos === null) {
459
449
  return;
@@ -461,8 +451,8 @@ class Scale {
461
451
 
462
452
  this.drawXPlotBand(fromPos, toPos, minX, maxX, minY, maxY);
463
453
  } else {
464
- fromPos = Canvas.calculateY(from, axisMin, axisMax, yArea, maxY);
465
- toPos = Canvas.calculateY(to, axisMin, axisMax, yArea, maxY);
454
+ fromPos = Canvas.calculateY(from ?? axisMin, axisMin, axisMax, yArea, maxY);
455
+ toPos = Canvas.calculateY(to ?? axisMax, axisMin, axisMax, yArea, maxY);
466
456
 
467
457
  if (fromPos === null || toPos === null) {
468
458
  return;
@@ -25,195 +25,15 @@ 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
-
33
31
  const max = range.maxValue;
34
32
  const min = range.minValue;
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
- }
33
+ const step = range.maxSteps;
120
34
 
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
- };
35
+ return this.interval ? this.interval : Math.ceil((max - min) / step);
138
36
  }
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
- }
217
37
  }
218
38
 
219
39
  export default LinearScale;
@@ -90,7 +90,6 @@ const DEFAULT_OPTIONS = {
90
90
  combo: false,
91
91
  tooltip: {
92
92
  use: true,
93
- trigger: 'axis',
94
93
  sortByValue: true,
95
94
  backgroundColor: '#4C4C4C',
96
95
  fontColor: '#FFFFFF',