evui 3.4.107 → 3.4.109
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/dist/evui.common.js +269 -118
- package/dist/evui.common.js.map +1 -1
- package/dist/evui.umd.js +269 -118
- package/dist/evui.umd.js.map +1 -1
- package/dist/evui.umd.min.js +1 -1
- package/dist/evui.umd.min.js.map +1 -1
- package/package.json +1 -1
- package/src/components/chart/chart.core.js +9 -0
- package/src/components/chart/element/element.bar.js +102 -92
- package/src/components/chart/model/model.store.js +8 -3
- package/src/components/chart/plugins/plugins.scrollbar.js +123 -13
package/package.json
CHANGED
|
@@ -957,6 +957,15 @@ class EvChart {
|
|
|
957
957
|
* @returns {undefined}
|
|
958
958
|
*/
|
|
959
959
|
resize(promiseRes) {
|
|
960
|
+
// 차트 크기가 변경될 때 저장된 스크롤 픽셀 위치를 초기화하여
|
|
961
|
+
// 새로운 크기에 맞춰 스크롤바 크기/위치를 재계산하도록 함
|
|
962
|
+
if (this.scrollbar?.x) {
|
|
963
|
+
delete this.scrollbar.x.savedPosition;
|
|
964
|
+
}
|
|
965
|
+
if (this.scrollbar?.y) {
|
|
966
|
+
delete this.scrollbar.y.savedPosition;
|
|
967
|
+
}
|
|
968
|
+
|
|
960
969
|
this.clear();
|
|
961
970
|
this.bufferCtx.restore();
|
|
962
971
|
this.bufferCtx.save();
|
|
@@ -46,7 +46,6 @@ class Bar {
|
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
const { isHorizontal, showValue } = this;
|
|
49
|
-
|
|
50
49
|
const ctx = param.ctx;
|
|
51
50
|
const chartRect = param.chartRect;
|
|
52
51
|
const labelOffset = param.labelOffset;
|
|
@@ -70,6 +69,7 @@ class Bar {
|
|
|
70
69
|
[minIndex, maxIndex] = [minmaxX.minIndex, minmaxX.maxIndex];
|
|
71
70
|
}
|
|
72
71
|
|
|
72
|
+
// minIndex, maxIndex가 유효하면 실제 그릴 데이터 개수로 보정
|
|
73
73
|
if (truthyNumber(minIndex) && truthyNumber(maxIndex)) {
|
|
74
74
|
totalCount = (maxIndex - minIndex) + 1;
|
|
75
75
|
}
|
|
@@ -126,112 +126,117 @@ class Bar {
|
|
|
126
126
|
this.borderRadius = param.borderRadius;
|
|
127
127
|
this.filteredCount = totalCount;
|
|
128
128
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
this.data.forEach((dataItem, index) => {
|
|
132
|
-
ctx.beginPath();
|
|
129
|
+
const startIndex = truthyNumber(minIndex) ? minIndex : 0;
|
|
130
|
+
const endIndex = truthyNumber(maxIndex) ? maxIndex : this.data.length - 1;
|
|
133
131
|
|
|
134
|
-
|
|
132
|
+
// 스크롤 범위 내에서만 루프 돌림
|
|
133
|
+
for (let i = startIndex; i <= endIndex; i++) {
|
|
134
|
+
const screenIndex = i - startIndex; // 현재 화면상의 위치 인덱스
|
|
135
|
+
const item = this.data[i]; // 실제 데이터 인덱스에 해당하는 항목
|
|
136
|
+
if (item) {
|
|
137
|
+
// 스크롤 offset(minIndex)만큼 보정해서 그리기
|
|
135
138
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
139
|
+
let categoryPoint;
|
|
140
|
+
if (isHorizontal) {
|
|
141
|
+
categoryPoint = ysp - (cArea * (screenIndex)) - cPad;
|
|
142
|
+
} else {
|
|
143
|
+
categoryPoint = xsp + (cArea * (screenIndex)) + cPad;
|
|
144
|
+
}
|
|
141
145
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
146
|
+
if (isHorizontal) {
|
|
147
|
+
x = xsp;
|
|
148
|
+
y = Math.round(categoryPoint - ((bArea * barSeriesX) - (h + bPad)));
|
|
149
|
+
} else {
|
|
150
|
+
x = Math.round(categoryPoint + ((bArea * barSeriesX) - (w + bPad)));
|
|
151
|
+
y = ysp;
|
|
152
|
+
}
|
|
147
153
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
154
|
+
if (isHorizontal) {
|
|
155
|
+
if (item.b) {
|
|
156
|
+
w = Canvas.calculateX(item.x - item.b, minmaxX.graphMin, minmaxX.graphMax, xArea);
|
|
157
|
+
x = Canvas.calculateX(item.b, minmaxX.graphMin, minmaxX.graphMax, xArea, xsp);
|
|
158
|
+
} else {
|
|
159
|
+
w = Canvas.calculateX(item.x, minmaxX.graphMin, minmaxX.graphMax, xArea);
|
|
160
|
+
}
|
|
161
|
+
} else if (item.b) { // vertical stack bar chart
|
|
162
|
+
h = Canvas.calculateY(item.y - item.b, minmaxY.graphMin, minmaxY.graphMax, yArea);
|
|
163
|
+
y = Canvas.calculateY(item.b, minmaxY.graphMin, minmaxY.graphMax, yArea, ysp);
|
|
164
|
+
} else { // vertical bar chart
|
|
165
|
+
h = Canvas.calculateY(item.y, minmaxY.graphMin, minmaxY.graphMax, yArea);
|
|
166
|
+
}
|
|
155
167
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
168
|
+
const barColor = item.dataColor || this.color;
|
|
169
|
+
|
|
170
|
+
const legendHitInfo = param?.legendHitInfo;
|
|
171
|
+
const selectLabelOption = param?.selectLabel?.option;
|
|
172
|
+
const selectItemOption = param?.selectItem?.option;
|
|
173
|
+
const selectedLabelList = param?.selectLabel?.selected?.dataIndex ?? [];
|
|
174
|
+
const {
|
|
175
|
+
dataIndex: selectedItemDataIndex,
|
|
176
|
+
seriesID: selectedItemSeriesId,
|
|
177
|
+
} = param?.selectItem?.selected ?? {};
|
|
178
|
+
|
|
179
|
+
let isDownplay = false;
|
|
180
|
+
|
|
181
|
+
if (legendHitInfo) {
|
|
182
|
+
isDownplay = legendHitInfo?.sId !== this.sId;
|
|
183
|
+
} else if (selectLabelOption?.use && selectLabelOption?.useSeriesOpacity) {
|
|
184
|
+
isDownplay = selectedLabelList.length && !selectedLabelList.includes(i);
|
|
185
|
+
} else if (truthy(selectedItemDataIndex) && selectItemOption?.useSeriesOpacity) {
|
|
186
|
+
if (this.isExistGrp) {
|
|
187
|
+
isDownplay = selectedItemDataIndex !== i;
|
|
188
|
+
} else {
|
|
189
|
+
isDownplay = selectedItemDataIndex !== i || selectedItemSeriesId !== this.sId;
|
|
190
|
+
}
|
|
162
191
|
}
|
|
163
|
-
} else if (item.b) { // vertical stack bar chart
|
|
164
|
-
h = Canvas.calculateY(item.y - item.b, minmaxY.graphMin, minmaxY.graphMax, yArea);
|
|
165
|
-
y = Canvas.calculateY(item.b, minmaxY.graphMin, minmaxY.graphMax, yArea, ysp);
|
|
166
|
-
} else { // vertical bar chart
|
|
167
|
-
h = Canvas.calculateY(item.y, minmaxY.graphMin, minmaxY.graphMax, yArea);
|
|
168
|
-
}
|
|
169
192
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
seriesID: selectedItemSeriesId,
|
|
179
|
-
} = param?.selectItem?.selected ?? {};
|
|
180
|
-
|
|
181
|
-
let isDownplay = false;
|
|
182
|
-
|
|
183
|
-
if (legendHitInfo) {
|
|
184
|
-
isDownplay = legendHitInfo?.sId !== this.sId;
|
|
185
|
-
} else if (selectLabelOption?.use && selectLabelOption?.useSeriesOpacity) {
|
|
186
|
-
isDownplay = selectedLabelList.length && !selectedLabelList.includes(index);
|
|
187
|
-
} else if (truthy(selectedItemDataIndex) && selectItemOption?.useSeriesOpacity) {
|
|
188
|
-
if (this.isExistGrp) {
|
|
189
|
-
isDownplay = selectedItemDataIndex !== index;
|
|
193
|
+
if (typeof barColor !== 'string') {
|
|
194
|
+
ctx.fillStyle = Canvas.createGradient(
|
|
195
|
+
ctx,
|
|
196
|
+
isHorizontal,
|
|
197
|
+
{ x, y, w, h },
|
|
198
|
+
barColor,
|
|
199
|
+
isDownplay,
|
|
200
|
+
);
|
|
190
201
|
} else {
|
|
191
|
-
|
|
202
|
+
const noneDownplayOpacity = barColor.includes('rgba') ? Util.getOpacity(barColor) : 1;
|
|
203
|
+
const opacity = isDownplay ? 0.1 : noneDownplayOpacity;
|
|
204
|
+
|
|
205
|
+
ctx.fillStyle = Util.colorStringToRgba(barColor, opacity);
|
|
192
206
|
}
|
|
193
|
-
}
|
|
194
207
|
|
|
195
|
-
|
|
196
|
-
ctx.fillStyle = Canvas.createGradient(
|
|
208
|
+
this.drawBar({
|
|
197
209
|
ctx,
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
barColor,
|
|
201
|
-
isDownplay,
|
|
202
|
-
);
|
|
203
|
-
} else {
|
|
204
|
-
const noneDownplayOpacity = barColor.includes('rgba') ? Util.getOpacity(barColor) : 1;
|
|
205
|
-
const opacity = isDownplay ? 0.1 : noneDownplayOpacity;
|
|
210
|
+
positions: { x, y, w, h },
|
|
211
|
+
});
|
|
206
212
|
|
|
207
|
-
|
|
208
|
-
|
|
213
|
+
if (showValue.use) {
|
|
214
|
+
this.drawValueLabels({
|
|
215
|
+
context: ctx,
|
|
216
|
+
data: item,
|
|
217
|
+
positions: {
|
|
218
|
+
x,
|
|
219
|
+
y,
|
|
220
|
+
h,
|
|
221
|
+
w,
|
|
222
|
+
},
|
|
223
|
+
isHighlight: false,
|
|
224
|
+
textColor: item.dataTextColor,
|
|
225
|
+
});
|
|
226
|
+
}
|
|
209
227
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
228
|
+
// 좌표 및 인덱스 정보 세팅 (툴팁/hover용)
|
|
229
|
+
item.xp = x; // eslint-disable-line
|
|
230
|
+
item.yp = y; // eslint-disable-line
|
|
231
|
+
item.w = w; // eslint-disable-line
|
|
232
|
+
item.h = isHorizontal ? -h : h; // eslint-disable-line
|
|
233
|
+
item.index = i; // 실제 데이터 인덱스 (스크롤 offset 포함)
|
|
214
234
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
data: item,
|
|
219
|
-
positions: {
|
|
220
|
-
x,
|
|
221
|
-
y,
|
|
222
|
-
h,
|
|
223
|
-
w,
|
|
224
|
-
},
|
|
225
|
-
isHighlight: false,
|
|
226
|
-
textColor: item.dataTextColor,
|
|
227
|
-
});
|
|
235
|
+
// 검색(hitInfo) 로직은 this.data[0..filteredCount-1] 범위만 검사하므로,
|
|
236
|
+
// 현재 화면에 그린 항목을 배열 앞쪽으로 매핑해준다.
|
|
237
|
+
this.data[screenIndex] = item;
|
|
228
238
|
}
|
|
229
|
-
|
|
230
|
-
item.xp = x; // eslint-disable-line
|
|
231
|
-
item.yp = y; // eslint-disable-line
|
|
232
|
-
item.w = w; // eslint-disable-line
|
|
233
|
-
item.h = isHorizontal ? -h : h; // eslint-disable-line
|
|
234
|
-
});
|
|
239
|
+
}
|
|
235
240
|
}
|
|
236
241
|
|
|
237
242
|
/**
|
|
@@ -511,6 +516,8 @@ class Bar {
|
|
|
511
516
|
return;
|
|
512
517
|
}
|
|
513
518
|
|
|
519
|
+
ctx.save();
|
|
520
|
+
|
|
514
521
|
if (isBorderRadius && !isStackBar) {
|
|
515
522
|
try {
|
|
516
523
|
this.drawRoundedRect(ctx, positions);
|
|
@@ -520,6 +527,8 @@ class Bar {
|
|
|
520
527
|
} else {
|
|
521
528
|
ctx.fillRect(x, y, w, h);
|
|
522
529
|
}
|
|
530
|
+
|
|
531
|
+
ctx.restore();
|
|
523
532
|
}
|
|
524
533
|
|
|
525
534
|
drawRoundedRect(ctx, positions) {
|
|
@@ -540,6 +549,7 @@ class Bar {
|
|
|
540
549
|
|
|
541
550
|
ctx.clip(squarePath);
|
|
542
551
|
|
|
552
|
+
ctx.beginPath();
|
|
543
553
|
ctx.moveTo(x, y);
|
|
544
554
|
|
|
545
555
|
if (isHorizontal) {
|
|
@@ -101,9 +101,6 @@ const modules = {
|
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
this.dataSet[key].length = this.options.realTimeScatter.range || 300;
|
|
104
|
-
this.dataSet[key].toTime = Math.floor(Date.now() / 1000) * 1000;
|
|
105
|
-
this.dataSet[key].fromTime = this.dataSet[key].toTime - this.dataSet[key].length * 1000;
|
|
106
|
-
this.dataSet[key].endIndex = this.dataSet[key].length - 1;
|
|
107
104
|
|
|
108
105
|
for (let i = 0; i < storeLength; i++) {
|
|
109
106
|
const item = data[i];
|
|
@@ -114,6 +111,14 @@ const modules = {
|
|
|
114
111
|
}
|
|
115
112
|
|
|
116
113
|
lastTime = Math.floor(lastTime / 1000) * 1000;
|
|
114
|
+
|
|
115
|
+
const dataGroupLastTime = this.dataSet[key].dataGroup.at(-1)?.data?.at(-1)?.x || Date.now();
|
|
116
|
+
|
|
117
|
+
this.dataSet[key].toTime = lastTime
|
|
118
|
+
|| (dataGroupLastTime ? Math.floor(dataGroupLastTime / 1000) * 1000 : 0);
|
|
119
|
+
this.dataSet[key].fromTime = this.dataSet[key].toTime - this.dataSet[key].length * 1000;
|
|
120
|
+
this.dataSet[key].endIndex = this.dataSet[key].length - 1;
|
|
121
|
+
|
|
117
122
|
if (
|
|
118
123
|
(this.dataSet[key].toTime - lastTime) / 1000
|
|
119
124
|
> this.dataSet[key].length && key === ''
|
|
@@ -60,8 +60,25 @@ const module = {
|
|
|
60
60
|
limitMin = +minMax.min;
|
|
61
61
|
limitMax = +minMax.max;
|
|
62
62
|
}
|
|
63
|
-
|
|
64
|
-
|
|
63
|
+
|
|
64
|
+
const originalWidth = max - min;
|
|
65
|
+
const availableWidth = limitMax - limitMin;
|
|
66
|
+
|
|
67
|
+
if (originalWidth >= availableWidth) {
|
|
68
|
+
scrollbarOpt.range[0] = limitMin;
|
|
69
|
+
scrollbarOpt.range[1] = limitMax;
|
|
70
|
+
} else {
|
|
71
|
+
scrollbarOpt.range[0] = +min < limitMin ? limitMin : +min;
|
|
72
|
+
scrollbarOpt.range[1] = +max > limitMax ? limitMax : +max;
|
|
73
|
+
|
|
74
|
+
if (scrollbarOpt.range[1] - scrollbarOpt.range[0] < originalWidth) {
|
|
75
|
+
scrollbarOpt.range[0] = scrollbarOpt.range[1] - originalWidth;
|
|
76
|
+
|
|
77
|
+
if (scrollbarOpt.range[0] < limitMin) {
|
|
78
|
+
scrollbarOpt.range[0] = limitMin;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
65
82
|
}
|
|
66
83
|
}
|
|
67
84
|
},
|
|
@@ -93,7 +110,16 @@ const module = {
|
|
|
93
110
|
const axisOpt = dir === 'x' ? this.axesX : this.axesY;
|
|
94
111
|
const isUpdateAxesRange = !isEqual(newOpt?.[0]?.range, axisOpt?.[0]?.range);
|
|
95
112
|
if (isUpdateAxesRange || updateData) {
|
|
96
|
-
this.
|
|
113
|
+
const isResetPosition = dir === 'x' ? this.options.axesX?.[0]?.scrollbar?.resetPosition : this.options.axesY?.[0]?.scrollbar?.resetPosition;
|
|
114
|
+
if (isUpdateAxesRange || isResetPosition) {
|
|
115
|
+
this.scrollbar[dir].range = newOpt?.[0]?.range?.length ? [...newOpt?.[0]?.range] : null;
|
|
116
|
+
// range가 업데이트되면 저장된 스크롤 위치를 초기화
|
|
117
|
+
delete this.scrollbar[dir].savedPosition;
|
|
118
|
+
} else if (updateData) {
|
|
119
|
+
// 데이터가 업데이트되면 저장된 픽셀 위치는 더 이상 유효하지 않으므로 삭제하여
|
|
120
|
+
// 논리적 범위에 따라 다시 계산하도록 합니다.
|
|
121
|
+
delete this.scrollbar[dir].savedPosition;
|
|
122
|
+
}
|
|
97
123
|
this.initScrollbarRange(dir);
|
|
98
124
|
}
|
|
99
125
|
this.scrollbar[dir].use = !!newOpt?.[0].scrollbar?.use;
|
|
@@ -104,11 +130,15 @@ const module = {
|
|
|
104
130
|
*/
|
|
105
131
|
updateScrollbarPosition() {
|
|
106
132
|
if (this.scrollbar.x?.use && this.scrollbar.x?.isInit) {
|
|
107
|
-
|
|
133
|
+
// resetPosition 옵션에 따라 preservePosition 결정
|
|
134
|
+
const preservePosition = !this.options.axesX?.[0]?.scrollbar?.resetPosition;
|
|
135
|
+
this.setScrollbarPosition('x', preservePosition);
|
|
108
136
|
}
|
|
109
137
|
|
|
110
138
|
if (this.scrollbar.y?.use && this.scrollbar.y?.isInit) {
|
|
111
|
-
|
|
139
|
+
// resetPosition 옵션에 따라 preservePosition 결정
|
|
140
|
+
const preservePosition = !this.options.axesY?.[0]?.scrollbar?.resetPosition;
|
|
141
|
+
this.setScrollbarPosition('y', preservePosition);
|
|
112
142
|
}
|
|
113
143
|
},
|
|
114
144
|
|
|
@@ -195,8 +225,9 @@ const module = {
|
|
|
195
225
|
/**
|
|
196
226
|
* set scrollbar position
|
|
197
227
|
* @param dir axis direction ('x' | 'y')
|
|
228
|
+
* @param preservePosition 기존 위치를 유지할지 여부
|
|
198
229
|
*/
|
|
199
|
-
setScrollbarPosition(dir) {
|
|
230
|
+
setScrollbarPosition(dir, preservePosition = false) {
|
|
200
231
|
const scrollbarOpt = this.scrollbar[dir];
|
|
201
232
|
if (!scrollbarOpt.use || !scrollbarOpt.range) {
|
|
202
233
|
return;
|
|
@@ -218,7 +249,17 @@ const module = {
|
|
|
218
249
|
const fullSize = isXScroll ? (aPos.x2 - aPos.x1) : (aPos.y2 - aPos.y1);
|
|
219
250
|
const buttonSize = scrollbarOpt.showButton ? scrollHeight : 0;
|
|
220
251
|
const trackSize = fullSize - (buttonSize * 2);
|
|
221
|
-
|
|
252
|
+
|
|
253
|
+
// 현재 위치를 보존해야 하는 경우 기존 위치를 저장
|
|
254
|
+
let savedThumbPosition = null;
|
|
255
|
+
if (preservePosition && scrollbarOpt.savedPosition !== undefined) {
|
|
256
|
+
savedThumbPosition = scrollbarOpt.savedPosition;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const thumbSize = this.getScrollbarThumbSize(dir, trackSize, savedThumbPosition);
|
|
260
|
+
|
|
261
|
+
// 새로 계산된 위치를 저장
|
|
262
|
+
scrollbarOpt.savedPosition = thumbSize.position;
|
|
222
263
|
|
|
223
264
|
let scrollbarStyle = 'display: block;';
|
|
224
265
|
let scrollbarTrackStyle;
|
|
@@ -293,8 +334,9 @@ const module = {
|
|
|
293
334
|
* get scrollbar thumb size
|
|
294
335
|
* @param dir axis direction ('x' | 'y')
|
|
295
336
|
* @param trackSize scrollbar track size
|
|
337
|
+
* @param savedThumbPosition 기존 위치를 보존해야 하는 경우 저장된 위치
|
|
296
338
|
*/
|
|
297
|
-
getScrollbarThumbSize(dir, trackSize) {
|
|
339
|
+
getScrollbarThumbSize(dir, trackSize, savedThumbPosition) {
|
|
298
340
|
const scrollbarOpt = this.scrollbar[dir];
|
|
299
341
|
const [min, max] = scrollbarOpt.range;
|
|
300
342
|
const axesType = scrollbarOpt.type;
|
|
@@ -338,6 +380,11 @@ const module = {
|
|
|
338
380
|
scrollbarOpt.steps = steps;
|
|
339
381
|
scrollbarOpt.interval = interval;
|
|
340
382
|
|
|
383
|
+
// 기존 위치를 보존해야 하는 경우 저장된 위치를 사용
|
|
384
|
+
if (savedThumbPosition !== null) {
|
|
385
|
+
thumbPosition = savedThumbPosition;
|
|
386
|
+
}
|
|
387
|
+
|
|
341
388
|
return {
|
|
342
389
|
size: thumbSize,
|
|
343
390
|
position: thumbPosition,
|
|
@@ -396,6 +443,9 @@ const module = {
|
|
|
396
443
|
if (!isOutOfRange) {
|
|
397
444
|
scrollbarOpt.range = [minValue, maxValue];
|
|
398
445
|
|
|
446
|
+
// 사용자가 스크롤할 때는 저장된 위치를 초기화
|
|
447
|
+
delete scrollbarOpt.savedPosition;
|
|
448
|
+
|
|
399
449
|
this.update({
|
|
400
450
|
updateSeries: false,
|
|
401
451
|
updateSelTip: { update: false, keepDomain: false },
|
|
@@ -481,9 +531,60 @@ const module = {
|
|
|
481
531
|
};
|
|
482
532
|
|
|
483
533
|
this.onScrollbarWheel = (e) => {
|
|
534
|
+
const isTooltipVisible = this.tooltipDOM?.style?.display === 'block';
|
|
535
|
+
const tooltipBodyDOM = this.tooltipBodyDOM
|
|
536
|
+
|| this.tooltipDOM?.querySelector(this.options.tooltip.htmlScrollTarget);
|
|
537
|
+
|
|
538
|
+
if (isTooltipVisible && tooltipBodyDOM) {
|
|
539
|
+
const { scrollTop, scrollHeight, clientHeight } = tooltipBodyDOM;
|
|
540
|
+
const isAtTop = scrollTop <= 0;
|
|
541
|
+
const isAtBottom = scrollTop + clientHeight >= scrollHeight;
|
|
542
|
+
|
|
543
|
+
const isScrollingUp = e.deltaY < 0;
|
|
544
|
+
const isScrollingDown = e.deltaY > 0;
|
|
545
|
+
|
|
546
|
+
if ((isAtTop && isScrollingUp) || (isAtBottom && isScrollingDown)) {
|
|
547
|
+
// 툴팁의 스크롤이 맨 위나 맨 아래에 닿았는데 스크롤 하면 차트 스크롤 허용
|
|
548
|
+
} else {
|
|
549
|
+
// 툴팁 내부 스크롤만 수행
|
|
550
|
+
return;
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
484
554
|
e.preventDefault();
|
|
485
555
|
|
|
486
|
-
|
|
556
|
+
const threshold = 1; // 최소 스크롤 임계값
|
|
557
|
+
|
|
558
|
+
// Shift + 휠: 가로 스크롤 (일반 마우스 휠 지원)
|
|
559
|
+
if (this.scrollbar.x?.use && e.shiftKey && Math.abs(e.deltaY) > threshold) {
|
|
560
|
+
this.updateScrollbarRange('x', e.deltaY > 0);
|
|
561
|
+
return;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// 대각선 스크롤 처리: 더 큰 방향을 우선으로 처리
|
|
565
|
+
const absX = Math.abs(e.deltaX);
|
|
566
|
+
const absY = Math.abs(e.deltaY);
|
|
567
|
+
|
|
568
|
+
if (absX > threshold && absY > threshold) {
|
|
569
|
+
// 두 방향 모두 임계값 이상일 때: 더 큰 방향을 우선 처리
|
|
570
|
+
if (absX > absY && this.scrollbar.x?.use) {
|
|
571
|
+
this.updateScrollbarRange('x', e.deltaX > 0);
|
|
572
|
+
} else if (absY > absX && this.scrollbar.y?.use) {
|
|
573
|
+
this.updateScrollbarRange('y', e.deltaY < 0);
|
|
574
|
+
}
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// 가로 스크롤 처리 (deltaX - 트랙패드 좌우 스크롤)
|
|
579
|
+
if (this.scrollbar.x?.use && absX > threshold) {
|
|
580
|
+
this.updateScrollbarRange('x', e.deltaX > 0);
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
// 세로 스크롤 처리 (deltaY)
|
|
585
|
+
if (this.scrollbar.y?.use && absY > threshold) {
|
|
586
|
+
this.updateScrollbarRange('y', e.deltaY < 0);
|
|
587
|
+
}
|
|
487
588
|
};
|
|
488
589
|
|
|
489
590
|
if (this.scrollbar.x.use && !this.scrollbar.x.isInit) {
|
|
@@ -498,6 +599,10 @@ const module = {
|
|
|
498
599
|
scrollbarYDOM.addEventListener('click', this.onScrollbarClick);
|
|
499
600
|
scrollbarYDOM.addEventListener('mousedown', this.onScrollbarDown);
|
|
500
601
|
scrollbarYDOM.addEventListener('mouseleave', this.onScrollbarLeave);
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
// 가로 또는 세로 스크롤바가 있으면 휠 이벤트 등록
|
|
605
|
+
if (this.scrollbar.x?.use || this.scrollbar.y?.use) {
|
|
501
606
|
this.overlayCanvas?.addEventListener('wheel', this.onScrollbarWheel, { passive: false });
|
|
502
607
|
}
|
|
503
608
|
},
|
|
@@ -560,6 +665,10 @@ const module = {
|
|
|
560
665
|
}
|
|
561
666
|
|
|
562
667
|
this.scrollbar[dir].range = [movedMin, movedMax];
|
|
668
|
+
|
|
669
|
+
// 사용자가 드래그로 스크롤할 때는 저장된 위치를 초기화
|
|
670
|
+
delete this.scrollbar[dir].savedPosition;
|
|
671
|
+
|
|
563
672
|
this.update({
|
|
564
673
|
updateSeries: false,
|
|
565
674
|
updateSelTip: { update: false, keepDomain: false },
|
|
@@ -606,13 +715,14 @@ const module = {
|
|
|
606
715
|
* @param dir axis direction ('x' | 'y')
|
|
607
716
|
*/
|
|
608
717
|
destroyScrollbar(dir) {
|
|
609
|
-
const
|
|
718
|
+
const scrollbarDOM = this.scrollbar[dir].dom;
|
|
610
719
|
|
|
611
|
-
if (
|
|
612
|
-
|
|
720
|
+
if (scrollbarDOM) {
|
|
721
|
+
scrollbarDOM.remove();
|
|
613
722
|
this.scrollbar[dir] = { isInit: false };
|
|
614
723
|
|
|
615
|
-
|
|
724
|
+
// 가로, 세로 스크롤바 모두 없어지면 휠 이벤트 제거
|
|
725
|
+
if (!this.scrollbar.x?.use && !this.scrollbar.y?.use) {
|
|
616
726
|
this.overlayCanvas?.removeEventListener('wheel', this.onScrollbarWheel, { passive: false });
|
|
617
727
|
}
|
|
618
728
|
}
|