evui 3.4.154 → 3.4.155

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.154",
3
+ "version": "3.4.155",
4
4
  "description": "A EXEM Library project",
5
5
  "author": "exem <dev_client@ex-em.com>",
6
6
  "license": "MIT",
@@ -268,7 +268,13 @@ class EvChart {
268
268
 
269
269
  this.axesRange = this.getAxesRange();
270
270
  this.labelOffset = this.getLabelOffset();
271
+
271
272
  this.labelRange = this.getAxesLabelRange();
273
+
274
+ if (this.scrollbar?.x?.use || this.scrollbar?.y?.use) {
275
+ this.updateScrollbarPosition();
276
+ }
277
+
272
278
  this.axesSteps = this.calculateSteps();
273
279
 
274
280
  this.adjustXAndYAxisWidth();
@@ -276,10 +282,6 @@ class EvChart {
276
282
  this.drawAxis(hitInfo);
277
283
  this.drawSeries(hitInfo);
278
284
 
279
- if (this.scrollbar?.x?.use || this.scrollbar?.y?.use) {
280
- this.updateScrollbarPosition();
281
- }
282
-
283
285
  this.drawTip();
284
286
 
285
287
  if (
@@ -856,12 +858,18 @@ class EvChart {
856
858
  * @param {boolean} updateData is update data
857
859
  * @returns {undefined}
858
860
  */
859
- updateScrollbar(updateData) {
860
- if (this.scrollbar?.x?.isInit || this.options.axesX?.[0]?.scrollbar?.use) {
861
+ updateScrollbar(updateData, updateByScrollbar) {
862
+ const isForceUpdate = updateByScrollbar || updateData;
863
+ const xUse = this.options.axesX?.[0]?.scrollbar?.use ?? false;
864
+ const yUse = this.options.axesY?.[0]?.scrollbar?.use ?? false;
865
+ const prevXUse = this.scrollbar?.x?.use ?? false;
866
+ const prevYUse = this.scrollbar?.y?.use ?? false;
867
+
868
+ if (xUse !== prevXUse || xUse || (isForceUpdate && xUse)) {
861
869
  this.updateScrollbarInfo('x', updateData);
862
870
  }
863
871
 
864
- if (this.scrollbar?.y?.isInit || this.options.axesY?.[0]?.scrollbar?.use) {
872
+ if (yUse !== prevYUse || yUse || (isForceUpdate && yUse)) {
865
873
  this.updateScrollbarInfo('y', updateData);
866
874
  }
867
875
  }
@@ -886,13 +894,14 @@ class EvChart {
886
894
  updateData,
887
895
  updateTooltip,
888
896
  lightUpdate,
897
+ updateByScrollbar,
889
898
  } = updateInfo;
890
899
 
891
900
  if (!this.isInit) {
892
901
  return;
893
902
  }
894
903
 
895
- this.updateScrollbar(updateData);
904
+ this.updateScrollbar(updateData, updateByScrollbar);
896
905
 
897
906
  this.resetProps();
898
907
 
@@ -115,13 +115,12 @@ class Bar {
115
115
  const startIndex = truthyNumber(minIndex) ? minIndex : 0;
116
116
  const endIndex = truthyNumber(maxIndex) ? maxIndex : this.data.length - 1;
117
117
 
118
- // 스크롤 범위 내에서만 루프 돌림
118
+ this.visibleStartIndex = startIndex;
119
+
119
120
  for (let i = startIndex; i <= endIndex; i++) {
120
- const screenIndex = i - startIndex; // 현재 화면상의 위치 인덱스
121
- const item = this.data[i]; // 실제 데이터 인덱스에 해당하는 항목
121
+ const screenIndex = i - startIndex;
122
+ const item = this.data[i];
122
123
  if (item) {
123
- // 스크롤 offset(minIndex)만큼 보정해서 그리기
124
-
125
124
  const categoryPoint = isHorizontal
126
125
  ? ysp - (cArea * screenIndex) - cPad
127
126
  : xsp + (cArea * screenIndex) + cPad;
@@ -214,11 +213,7 @@ class Bar {
214
213
  item.yp = y; // eslint-disable-line
215
214
  item.w = w; // eslint-disable-line
216
215
  item.h = isHorizontal ? -h : h; // eslint-disable-line
217
- item.index = i; // 실제 데이터 인덱스 (스크롤 offset 포함)
218
-
219
- // 검색(hitInfo) 로직은 this.data[0..filteredCount-1] 범위만 검사하므로,
220
- // 현재 화면에 그린 항목을 배열 앞쪽으로 매핑해준다.
221
- this.data[screenIndex] = item;
216
+ item.index = i;
222
217
  }
223
218
  }
224
219
  }
@@ -294,13 +289,18 @@ class Bar {
294
289
  */
295
290
  findGraphData(offset, isHorizontal, dataIndex, useIndicatorOnLabel) {
296
291
  if (typeof dataIndex === 'number' && this.show && useIndicatorOnLabel) {
297
- const gdata = this.data;
292
+ const barData = this.data;
298
293
  const item = { data: null, hit: false, color: this.color };
299
294
 
300
- if (gdata[dataIndex]) {
301
- item.data = gdata[dataIndex];
302
- item.index = dataIndex;
303
- item.hit = this.isPointInBar(offset, gdata[dataIndex]);
295
+ // dataIndex 현재 화면에 보이는 범위로 clamp하여 stale xp/yp 참조 방지
296
+ const visStart = this.visibleStartIndex ?? 0;
297
+ const visEnd = visStart + (this.filteredCount ?? barData.length) - 1;
298
+ const clampedIndex = Math.max(visStart, Math.min(dataIndex, visEnd));
299
+
300
+ if (barData[clampedIndex]) {
301
+ item.data = barData[clampedIndex];
302
+ item.index = clampedIndex;
303
+ item.hit = this.isPointInBar(offset, barData[clampedIndex]);
304
304
  }
305
305
 
306
306
  return item;
@@ -326,10 +326,11 @@ class Bar {
326
326
  const [xp, yp] = offset;
327
327
  const item = { data: null, hit: false, color: this.color };
328
328
  const gdata = this.data;
329
+ const startIdx = this.visibleStartIndex ?? 0;
329
330
  const totalCount = this.filteredCount ?? gdata.length;
330
331
 
331
- let s = 0;
332
- let e = totalCount - 1;
332
+ let s = startIdx;
333
+ let e = startIdx + totalCount - 1;
333
334
 
334
335
  while (s <= e) {
335
336
  const m = Math.floor((s + e) / 2);
@@ -339,7 +339,7 @@ const modules = {
339
339
  let labelCount = labelAxes.labels.length;
340
340
  if (scrollbarOpt?.use) {
341
341
  const { range, interval, type } = scrollbarOpt;
342
- const [min, max] = range;
342
+ const [min, max] = range ?? [];
343
343
  if (truthyNumber(min) && truthyNumber(max)) {
344
344
  labelCount = Math.floor((+max - +min) / interval) + 1;
345
345
  startIndex = type === 'step' ? min : labelAxes.labels.findIndex(v => v === +min);
@@ -29,11 +29,15 @@ const module = {
29
29
  scrollbarOpt[key] = merged[key];
30
30
  });
31
31
 
32
- delete scrollbarOpt.savedPosition;
32
+ if (scrollbarOpt.resetPosition) {
33
+ scrollbarOpt.range = axisOpt?.[0]?.range?.length ? [...axisOpt?.[0]?.range] : null;
34
+ this.resetScrollbarSavedPositions(dir);
35
+ }
33
36
 
34
37
  if (!scrollbarOpt.isInit) {
35
38
  scrollbarOpt.type = axisOpt?.[0]?.type;
36
39
  scrollbarOpt.range = axisOpt?.[0]?.range?.length ? [...axisOpt?.[0]?.range] : null;
40
+ this.resetScrollbarSavedPositions(dir);
37
41
 
38
42
  this.initScrollbarRange(dir);
39
43
  this.createScrollbarLayout(dir);
@@ -104,16 +108,18 @@ const module = {
104
108
  const axisOpt = dir === 'x' ? this.axesX : this.axesY;
105
109
  const isUpdateAxesRange = !isEqual(newOpt?.[0]?.range, axisOpt?.[0]?.range);
106
110
  if (isUpdateAxesRange || updateData) {
107
- const isResetPosition = dir === 'x' ? this.options.axesX?.[0]?.scrollbar?.resetPosition : this.options.axesY?.[0]?.scrollbar?.resetPosition;
108
- if (isUpdateAxesRange || isResetPosition) {
111
+ const isResetPosition = dir === 'x'
112
+ ? this.options.axesX?.[0]?.scrollbar?.resetPosition
113
+ : this.options.axesY?.[0]?.scrollbar?.resetPosition;
114
+
115
+ if (isUpdateAxesRange) {
109
116
  this.scrollbar[dir].range = newOpt?.[0]?.range?.length ? [...newOpt?.[0]?.range] : null;
110
- // range가 업데이트되면 저장된 스크롤 위치를 초기화
111
- delete this.scrollbar[dir].savedPosition;
112
- } else if (updateData) {
113
- // 데이터가 업데이트되면 저장된 픽셀 위치는 더 이상 유효하지 않으므로 삭제하여
114
- // 논리적 범위에 따라 다시 계산하도록 합니다.
115
- delete this.scrollbar[dir].savedPosition;
116
117
  }
118
+
119
+ if (isResetPosition || updateData) {
120
+ this.resetScrollbarSavedPositions(dir);
121
+ }
122
+
117
123
  this.initScrollbarRange(dir);
118
124
  }
119
125
  this.scrollbar[dir].use = !!newOpt?.[0].scrollbar?.use;
@@ -244,16 +250,29 @@ const module = {
244
250
  const buttonSize = scrollbarOpt.showButton ? scrollHeight : 0;
245
251
  const trackSize = fullSize - (buttonSize * 2);
246
252
 
247
- // 현재 위치를 보존해야 하는 경우 기존 위치를 저장
248
- let savedThumbPosition = null;
249
- if (preservePosition && scrollbarOpt.savedPosition !== undefined) {
250
- savedThumbPosition = scrollbarOpt.savedPosition;
251
- }
253
+ const thumbSize = this.getScrollbarThumbSize(dir, trackSize);
252
254
 
253
- const thumbSize = this.getScrollbarThumbSize(dir, trackSize, savedThumbPosition);
255
+ // 비율로 저장된 위치가 있으면 새 track 크기에 맞게 복원
256
+ if (preservePosition && scrollbarOpt.savedPositionRatio !== undefined) {
257
+ const maxPosition = Math.max(0, trackSize - thumbSize.size);
258
+ if (scrollbarOpt.savedAtStart) {
259
+ thumbSize.position = 0;
260
+ } else if (scrollbarOpt.savedAtEnd) {
261
+ thumbSize.position = maxPosition;
262
+ } else {
263
+ thumbSize.position = Math.min(scrollbarOpt.savedPositionRatio * trackSize, maxPosition);
264
+ }
265
+ }
254
266
 
255
- // 새로 계산된 위치를 저장
256
- scrollbarOpt.savedPosition = thumbSize.position;
267
+ // 위치를 비율 처음/끝 고정 여부로 저장
268
+ // currentMaxPosition === 0 (thumbSize >= trackSize) 인 경우 저장하지 않음
269
+ // → trackSize가 다시 커졌을 때 이전 savedAtEnd/savedAtStart/savedPositionRatio 상태를 유지
270
+ const currentMaxPosition = Math.max(0, trackSize - thumbSize.size);
271
+ if (currentMaxPosition > 0) {
272
+ scrollbarOpt.savedPositionRatio = thumbSize.position / trackSize;
273
+ scrollbarOpt.savedAtStart = thumbSize.position <= 0;
274
+ scrollbarOpt.savedAtEnd = thumbSize.position >= currentMaxPosition;
275
+ }
257
276
 
258
277
  let scrollbarStyle = 'display: block;';
259
278
  let scrollbarTrackStyle;
@@ -328,9 +347,8 @@ const module = {
328
347
  * get scrollbar thumb size
329
348
  * @param dir axis direction ('x' | 'y')
330
349
  * @param trackSize scrollbar track size
331
- * @param savedThumbPosition 기존 위치를 보존해야 하는 경우 저장된 위치
332
350
  */
333
- getScrollbarThumbSize(dir, trackSize, savedThumbPosition) {
351
+ getScrollbarThumbSize(dir, trackSize) {
334
352
  const scrollbarOpt = this.scrollbar[dir];
335
353
  const [min, max] = scrollbarOpt.range;
336
354
  const axesType = scrollbarOpt.type;
@@ -374,11 +392,6 @@ const module = {
374
392
  scrollbarOpt.steps = steps;
375
393
  scrollbarOpt.interval = interval;
376
394
 
377
- // 기존 위치를 보존해야 하는 경우 저장된 위치를 사용
378
- if (savedThumbPosition !== null) {
379
- thumbPosition = savedThumbPosition;
380
- }
381
-
382
395
  return {
383
396
  size: thumbSize,
384
397
  position: thumbPosition,
@@ -438,16 +451,32 @@ const module = {
438
451
  scrollbarOpt.range = [minValue, maxValue];
439
452
 
440
453
  // 사용자가 스크롤할 때는 저장된 위치를 초기화
441
- delete scrollbarOpt.savedPosition;
454
+ this.resetScrollbarSavedPositions(dir);
442
455
 
443
456
  this.update({
444
457
  updateSeries: false,
445
458
  updateSelTip: { update: false, keepDomain: false },
446
459
  lightUpdate: minValue > 1,
460
+ updateByScrollbar: true,
447
461
  });
448
462
  }
449
463
  },
450
464
 
465
+ /**
466
+ * reset scrollbar saved positions
467
+ * @param dir axis direction ('x' | 'y')
468
+ */
469
+ resetScrollbarSavedPositions(dir) {
470
+ const scrollbarOpt = this.scrollbar[dir];
471
+ if (!scrollbarOpt) {
472
+ return;
473
+ }
474
+
475
+ delete scrollbarOpt.savedPositionRatio;
476
+ delete scrollbarOpt.savedAtStart;
477
+ delete scrollbarOpt.savedAtEnd;
478
+ },
479
+
451
480
  /**
452
481
  * create scroll event
453
482
  */
@@ -662,11 +691,13 @@ const module = {
662
691
  this.scrollbar[dir].range = [movedMin, movedMax];
663
692
 
664
693
  // 사용자가 드래그로 스크롤할 때는 저장된 위치를 초기화
665
- delete this.scrollbar[dir].savedPosition;
694
+ this.resetScrollbarSavedPositions(dir);
666
695
 
667
696
  this.update({
668
697
  updateSeries: false,
669
698
  updateSelTip: { update: false, keepDomain: false },
699
+ lightUpdate: movedMin > 1,
700
+ updateByScrollbar: true,
670
701
  });
671
702
  },
672
703