evui 3.4.131 → 3.4.133

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.131",
3
+ "version": "3.4.133",
4
4
  "description": "A EXEM Library project",
5
5
  "author": "exem <dev_client@ex-em.com>",
6
6
  "license": "MIT",
@@ -341,7 +341,7 @@ class Line {
341
341
  item.hit = true;
342
342
  }
343
343
  }
344
- } else if (typeof this.beforeFindItemIndex === 'number' && this.show && useSelectLabelOrItem) {
344
+ } else if (typeof this.beforeFindItemIndex === 'number' && this.beforeFindItemIndex !== -1 && this.show && useSelectLabelOrItem) {
345
345
  item.data = gdata[this.beforeFindItemIndex];
346
346
  item.index = this.beforeFindItemIndex;
347
347
  } else {
@@ -434,27 +434,28 @@ class Line {
434
434
  }
435
435
 
436
436
  // 두 가지 임계값 설정
437
- const strictThreshold = avgInterval * 0.3; // 엄격한 임계값: 데이터 간격의 30%
438
- const relaxedThreshold = avgInterval; // 느슨한 임계값: 데이터 간격 전체
437
+ const threshold = Math.max(avgInterval, 1);
439
438
 
440
439
  // 1. 먼저 엄격한 임계값으로 정확한 매치 확인
441
- if (closestXDistance <= strictThreshold) {
440
+ if (closestXDistance <= threshold) {
442
441
  // 정확히 일치하거나 매우 가까운 데이터가 있음
443
442
  item.data = gdata[closestIndex];
444
443
  item.index = closestIndex;
445
444
  } else {
446
- // 2. 정확한 매치가 없을 때, 현재 X 위치 근처에 다른 유효 데이터가 있는지 확인
447
- let hasNearbyValidData = false;
448
- for (let i = 0; i < validData.length; i++) {
449
- const xDist = Math.abs(xp - validData[i].xp);
450
- if (xDist <= strictThreshold) {
451
- hasNearbyValidData = true;
452
- break;
445
+ // 2. 정확한 매치가 없을 때, 현재 X 위치 근처에 다른 데이터가 있는지 확인
446
+ let hasNearbyAnyData = false;
447
+ let closestDistance = isLinearInterpolation ? Infinity : threshold;
448
+ const dataSet = isLinearInterpolation ? validData : gdata;
449
+ for (let i = 0; i < dataSet.length; i++) {
450
+ const xDist = Math.abs(xp - dataSet[i].xp);
451
+ if (xDist <= closestDistance) {
452
+ hasNearbyAnyData = true;
453
+ closestDistance = xDist;
454
+ closestIndex = isLinearInterpolation ? dataSet[i].originalIndex : i;
453
455
  }
454
456
  }
455
457
 
456
- // 3. 근처에 다른 유효 데이터가 없을 때만 느슨한 임계값 적용
457
- if (!hasNearbyValidData && closestXDistance <= relaxedThreshold) {
458
+ if (hasNearbyAnyData) {
458
459
  item.data = gdata[closestIndex];
459
460
  item.index = closestIndex;
460
461
  }
@@ -317,7 +317,6 @@ const modules = {
317
317
  };
318
318
 
319
319
  const setSelectedLabelInfo = (targetAxis) => {
320
- const itemHitInfo = this.getItemByPosition(offset, false);
321
320
  const {
322
321
  labelIndex: clickedLabelIndex,
323
322
  } = this.getLabelInfoByPosition(offset, targetAxis);
@@ -336,8 +335,8 @@ const modules = {
336
335
  eventTarget: 'label',
337
336
  ...cloneDeep(this.defaultSelectInfo),
338
337
  };
339
- args.label = itemHitInfo.label;
340
- args.dataIndex = itemHitInfo.maxIndex;
338
+ args.label = this.defaultSelectInfo?.label?.at(0);
339
+ args.dataIndex = this.defaultSelectInfo?.dataIndex?.at(0);
341
340
  };
342
341
 
343
342
  const setSelectedSeriesInfo = () => {
@@ -906,75 +905,16 @@ const modules = {
906
905
  let maxg = null;
907
906
  let maxSID = null;
908
907
 
909
- // 파이 차트는 특별한 처리가 필요
910
- if (this.options.type === 'pie') {
911
- for (let ix = 0; ix < sIds.length; ix++) {
912
- const sId = sIds[ix];
913
- const series = this.seriesList[sId];
914
-
915
- if (series.findGraphData && series.show) {
916
- const item = series.findGraphData(offset);
917
-
918
- if (item?.data && item.hit) {
919
- const gdata = item.data.o;
920
-
921
- if (gdata !== null && gdata !== undefined) {
922
- const formattedSeriesName = this.getFormattedTooltipLabel({
923
- dataId: series.id,
924
- seriesId: sId,
925
- seriesName: series.name,
926
- itemData: item.data,
927
- });
928
- const sw = ctx ? ctx.measureText(formattedSeriesName).width : 1;
929
-
930
- item.id = series.id;
931
- item.name = formattedSeriesName;
932
- item.axis = { x: 0, y: 0 };
933
- items[sId] = item;
934
-
935
- const formattedTxt = this.getFormattedTooltipValue({
936
- dataId: series.id,
937
- seriesId: sId,
938
- seriesName: formattedSeriesName,
939
- value: gdata,
940
- itemData: item.data,
941
- });
942
-
943
- item.data.formatted = formattedTxt;
944
-
945
- if (maxsw < sw) {
946
- maxs = formattedSeriesName;
947
- maxsw = sw;
948
- }
949
-
950
- if (maxv.length <= `${formattedTxt}`.length) {
951
- maxv = `${formattedTxt}`;
952
- }
953
-
954
- if (maxg === null || maxg <= gdata) {
955
- maxg = gdata;
956
- maxSID = sId;
957
- }
958
-
959
- hitId = sId;
960
- }
961
- }
962
- }
963
- }
964
-
965
- const maxHighlight = maxg !== null ? [maxSID, maxg] : null;
966
- return { items, hitId, maxTip: [maxs, maxv], maxHighlight };
967
- }
968
-
969
908
  // 1. 먼저 공통으로 사용할 데이터 인덱스 결정
970
909
  const targetDataIndex = this.findClosestDataIndex(offset, sIds);
971
910
 
972
- if (targetDataIndex === -1) {
911
+ if (targetDataIndex === -1 && this.options.type !== 'pie') {
973
912
  return { items, hitId, maxTip: [maxs, maxv], maxHighlight: null };
974
913
  }
975
914
 
976
915
  // 2. 모든 시리즈가 동일한 데이터 인덱스 사용
977
916
  const allSeriesIsBar = sIds.every(sId => this.seriesList[sId].type === 'bar');
917
+
978
918
  for (let ix = 0; ix < sIds.length; ix++) {
979
919
  const sId = sIds[ix];
980
920
  const series = this.seriesList[sId];
@@ -1056,8 +996,6 @@ const modules = {
1056
996
  const [xp, yp] = offset;
1057
997
  const isHorizontal = !!this.options.horizontal;
1058
998
  const mousePos = isHorizontal ? yp : xp;
1059
- let closestDistance = Infinity;
1060
- let closestIndex = -1;
1061
999
 
1062
1000
  // 첫 번째 표시 중인 시리즈를 기준으로 라벨 위치 확인
1063
1001
  const referenceSeries = sIds.find(sId => this.seriesList[sId]?.show);
@@ -1067,6 +1005,39 @@ const modules = {
1067
1005
 
1068
1006
  const referenceData = this.seriesList[referenceSeries].data;
1069
1007
 
1008
+ // 데이터 간격 계산 - 모든 데이터(null 포함)의 평균 간격 사용
1009
+ let avgInterval = 50;
1010
+ if (referenceData.length > 1) {
1011
+ const intervals = [];
1012
+ for (let i = 1; i < referenceData.length; i++) {
1013
+ const prevPoint = referenceData[i - 1];
1014
+ const currPoint = referenceData[i];
1015
+ if (prevPoint && currPoint) {
1016
+ let prevPos;
1017
+ let currPos;
1018
+
1019
+ if (isHorizontal) {
1020
+ prevPos = prevPoint.h ? prevPoint.yp + (prevPoint.h / 2) : prevPoint.yp;
1021
+ currPos = currPoint.h ? currPoint.yp + (currPoint.h / 2) : currPoint.yp;
1022
+ } else {
1023
+ prevPos = prevPoint.w ? prevPoint.xp + (prevPoint.w / 2) : prevPoint.xp;
1024
+ currPos = currPoint.w ? currPoint.xp + (currPoint.w / 2) : currPoint.xp;
1025
+ }
1026
+
1027
+ if (prevPos !== null && currPos !== null) {
1028
+ intervals.push(Math.abs(currPos - prevPos));
1029
+ }
1030
+ }
1031
+ }
1032
+ if (intervals.length > 0) {
1033
+ avgInterval = intervals.reduce((a, b) => a + b, 0) / intervals.length;
1034
+ }
1035
+ }
1036
+
1037
+ let closestDistance = this.seriesList?.[referenceSeries]?.useLinearInterpolation?.()
1038
+ ? Infinity : avgInterval;
1039
+ let closestIndex = -1;
1040
+
1070
1041
  // 각 라벨에서 가장 가까운 것 찾기
1071
1042
  for (let i = 0; i < referenceData.length; i++) {
1072
1043
  // 이 라벨에 유효한 데이터가 있는 시리즈가 하나 이상 있는지 확인
@@ -206,15 +206,10 @@ const modules = {
206
206
  const { groups } = this.data;
207
207
  const { seriesList } = this;
208
208
 
209
- if (this.options.legend.virtualScroll) {
210
- if (this.useTable) {
211
- this.addLegendForGroups(groups, seriesList, true);
212
- this.addStandaloneLegends(seriesList, true);
213
- } else {
214
- this.renderVisibleLegendsFrameId = requestAnimationFrame(() => {
215
- this.renderVisibleLegends();
216
- });
217
- }
209
+ if (this.options.legend.virtualScroll && !this.useTable) {
210
+ this.renderVisibleLegendsFrameId = requestAnimationFrame(() => {
211
+ this.renderVisibleLegends();
212
+ });
218
213
  } else {
219
214
  this.addLegendForGroups(groups, seriesList, this.useTable);
220
215
  this.addStandaloneLegends(seriesList, this.useTable);
@@ -362,19 +357,116 @@ const modules = {
362
357
  }
363
358
 
364
359
  const classList = {
365
- container: `ev-chart-legend${this.useTable ? '--table__container' : '-container'}`,
360
+ container: `ev-chart-legend${this.useTable ? '--table__row' : '-container'}`,
366
361
  color: `ev-chart-legend${this.useTable ? '--table__color' : '-color'}`,
367
362
  name: `ev-chart-legend${this.useTable ? '--table__name' : '-name'}`,
368
363
  value: `ev-chart-legend${this.useTable ? '--table__value' : '-value'}`,
369
364
  };
370
365
 
366
+ /**
367
+ * callback for legendBoxDOM to show/hide clicked series
368
+ *
369
+ * @param {Element} _targetDOM - target DOM
370
+ * @param {string} _inactiveColor - inactive color
371
+ * @returns {void}
372
+ */
373
+ const inactiveDomAndSeries = (_targetDOM, _inactiveColor) => {
374
+ const _colorDOM = _targetDOM?.getElementsByClassName(classList.color)[0];
375
+ const _nameDOM = _targetDOM?.getElementsByClassName(classList.name)[0];
376
+ const _valueDOMList = _targetDOM?.getElementsByClassName(classList.value);
377
+ const _series = _targetDOM?.series;
378
+
379
+ _colorDOM.style.backgroundColor = _inactiveColor;
380
+ _colorDOM.style.borderColor = _inactiveColor;
381
+ _nameDOM.style.color = _inactiveColor;
382
+ _valueDOMList?.forEach((dom) => {
383
+ dom.style.color = _inactiveColor;
384
+ });
385
+
386
+ _series.show = false;
387
+ _targetDOM.dataset.inactive = true;
388
+ };
389
+
390
+ /**
391
+ * callback for legendBoxDOM to show/hide clicked series
392
+ *
393
+ * @param {Element} _targetDOM - target DOM
394
+ * @param {string} _activeColor - active color
395
+ * @returns {void}
396
+ */
397
+ const activeDomAndSeries = (_targetDOM, _activeColor) => {
398
+ let seriesColor;
399
+
400
+ const _colorDOM = _targetDOM?.getElementsByClassName(classList.color)[0];
401
+ const _nameDOM = _targetDOM?.getElementsByClassName(classList.name)[0];
402
+ const _valueDOMList = _targetDOM?.getElementsByClassName(classList.value);
403
+ const _series = _targetDOM?.series;
404
+
405
+ if (typeof _series.color !== 'string') {
406
+ seriesColor = _series.color[_series.color.length - 1][1];
407
+ } else {
408
+ seriesColor = _series.color;
409
+ }
410
+
411
+ if (_series.type === 'line' && _series.fill) {
412
+ _colorDOM.style.height = '8px';
413
+ _colorDOM.style.backgroundColor = Util.rgbaAdjustHalfOpacity(seriesColor);
414
+ _colorDOM.style.border = `1px solid ${seriesColor}`;
415
+ } else {
416
+ _colorDOM.style.backgroundColor = seriesColor;
417
+ }
418
+
419
+ _nameDOM.style.color = _activeColor;
420
+ _valueDOMList?.forEach((dom) => {
421
+ const style = this.options.legend.table?.columns[dom.dataset.type]?.style;
422
+ dom.style.color = style?.color ? style.color : _activeColor;
423
+ });
424
+
425
+ _series.show = true;
426
+ _targetDOM.dataset.inactive = false;
427
+ };
428
+
429
+ const hideAllSeries = () => {
430
+ const legendSeries = (() => {
431
+ if (this.data.groups.at(0)) {
432
+ return this.data.groups.at(0).slice().reverse()
433
+ .filter(sId => this.seriesList[sId].showLegend)
434
+ .map(sId => [sId, this.seriesList[sId]]);
435
+ }
436
+ return Object.entries(this.seriesList)
437
+ .filter(([, series]) => series.showLegend);
438
+ })();
439
+ legendSeries.forEach(([, s]) => {
440
+ s.show = false;
441
+ });
442
+ };
443
+ const showAllSeries = () => {
444
+ const legendSeries = (() => {
445
+ if (this.data.groups.at(0)) {
446
+ return this.data.groups.at(0).slice().reverse()
447
+ .filter(sId => this.seriesList[sId].showLegend)
448
+ .map(sId => [sId, this.seriesList[sId]]);
449
+ }
450
+ return Object.entries(this.seriesList)
451
+ .filter(([, series]) => series.showLegend);
452
+ })();
453
+ legendSeries.forEach(([, s]) => {
454
+ s.show = true;
455
+ });
456
+ };
457
+
371
458
  /**
372
459
  * callback for legendBoxDOM to show/hide clicked series
373
460
  *
374
461
  * @returns {undefined}
375
462
  */
463
+ /**
464
+ * 범례 박스 클릭 이벤트 핸들러
465
+ * 시리즈의 표시/숨김을 토글하고 범례의 시각적 상태를 변경
466
+ */
376
467
  this.onLegendBoxClick = (e) => {
377
468
  const { legend: opt } = this.options;
469
+
378
470
  if (opt?.stopClickEvt) {
379
471
  return;
380
472
  }
@@ -385,59 +477,63 @@ const modules = {
385
477
  return;
386
478
  }
387
479
 
388
- const series = targetDOM?.series;
389
-
390
480
  const colorDOM = targetDOM?.getElementsByClassName(classList.color)[0];
391
481
  const nameDOM = targetDOM?.getElementsByClassName(classList.name)[0];
392
- const valueDOMList = targetDOM?.getElementsByClassName(classList.value);
393
-
394
- const isActive = !targetDOM?.className.includes('inactive');
395
- if (isActive && this.seriesInfo.count === 1) {
396
- return;
397
- }
482
+ const isActive = targetDOM?.dataset.inactive === 'false';
398
483
 
399
484
  if (!colorDOM || !nameDOM) {
400
485
  return;
401
486
  }
402
487
 
403
- if (isActive) {
404
- this.seriesInfo.count--;
488
+ // clickMode active - 클릭시 활성화
489
+ if (opt.clickMode === 'active') {
490
+ const legendContainerDOMs = Array.from(
491
+ this.legendBoxDOM.getElementsByClassName(classList.container),
492
+ );
493
+ const isActiveAll = legendContainerDOMs.every(dom => dom.dataset.inactive === 'false');
405
494
 
406
- const inactiveColor = opt.inactive;
407
- colorDOM.style.backgroundColor = inactiveColor;
408
- colorDOM.style.borderColor = inactiveColor;
409
- nameDOM.style.color = inactiveColor;
410
- valueDOMList?.forEach((dom) => {
411
- dom.style.color = inactiveColor;
412
- });
413
- } else {
414
- this.seriesInfo.count++;
495
+ if (isActiveAll) {
496
+ legendContainerDOMs.forEach((dom) => {
497
+ inactiveDomAndSeries(dom, opt.inactive);
498
+ });
499
+ hideAllSeries();
500
+
501
+ activeDomAndSeries(targetDOM, opt.color);
502
+ this.seriesInfo.count = 1;
503
+ } else if (isActive) {
504
+ inactiveDomAndSeries(targetDOM, opt.inactive);
505
+ this.seriesInfo.count--;
506
+ } else if (!isActive) {
507
+ activeDomAndSeries(targetDOM, opt.color);
508
+ this.seriesInfo.count++;
509
+ }
415
510
 
416
- let seriesColor;
417
- if (typeof series.color !== 'string') {
418
- seriesColor = series.color[series.color.length - 1][1];
419
- } else {
420
- seriesColor = series.color;
511
+ const isInactiveAll = legendContainerDOMs.every(dom => dom.dataset.inactive === 'true');
512
+
513
+ if (isInactiveAll) {
514
+ legendContainerDOMs.forEach((dom) => {
515
+ activeDomAndSeries(dom, opt.color);
516
+ });
517
+ showAllSeries();
518
+ this.seriesInfo.count = legendContainerDOMs.length;
421
519
  }
520
+ }
422
521
 
423
- if (series.type === 'line' && series.fill) {
424
- colorDOM.style.height = '8px';
425
- colorDOM.style.backgroundColor = Util.rgbaAdjustHalfOpacity(seriesColor);
426
- colorDOM.style.border = `1px solid ${seriesColor}`;
427
- } else {
428
- colorDOM.style.backgroundColor = seriesColor;
522
+ // clickMode inactive - 클릭시 비활성화
523
+ if (opt.clickMode !== 'active') {
524
+ if (isActive && this.seriesInfo.count === 1) {
525
+ return;
429
526
  }
430
527
 
431
- nameDOM.style.color = opt.color;
432
- valueDOMList?.forEach((dom) => {
433
- const style = opt.table?.columns[dom.dataset.type]?.style;
434
- dom.style.color = style?.color ? style.color : opt.color;
435
- });
528
+ if (isActive) {
529
+ inactiveDomAndSeries(targetDOM, opt.inactive);
530
+ this.seriesInfo.count--;
531
+ } else {
532
+ activeDomAndSeries(targetDOM, opt.color);
533
+ this.seriesInfo.count++;
534
+ }
436
535
  }
437
536
 
438
- series.show = !series.show;
439
- targetDOM.classList.toggle('inactive');
440
-
441
537
  if (this.brushSeries) {
442
538
  const seriesList = [...this.brushSeries.list];
443
539
  seriesList[chartIdx] = this.seriesList;
@@ -515,6 +611,73 @@ const modules = {
515
611
  if (this.isInitLegend) {
516
612
  return;
517
613
  }
614
+ const classList = {
615
+ container: `ev-chart-legend${this.useTable ? '--table__row' : '-container'}`,
616
+ color: `ev-chart-legend${this.useTable ? '--table__color' : '-color'}`,
617
+ name: `ev-chart-legend${this.useTable ? '--table__name' : '-name'}`,
618
+ };
619
+
620
+ /**
621
+ * callback for legendBoxDOM to show/hide clicked series
622
+ *
623
+ * @param {Element} _targetDOM - target DOM
624
+ * @param {string} _inactiveColor - inactive color
625
+ * @returns {void}
626
+ */
627
+ const inactiveDomAndSeries = (_targetDOM, _inactiveColor) => {
628
+ const _colorDOM = _targetDOM?.getElementsByClassName(classList.color)[0];
629
+ const _nameDOM = _targetDOM?.getElementsByClassName(classList.name)[0];
630
+ const _series = Object.values(this.seriesList)[0];
631
+ const targetId = _targetDOM?.series?.cId;
632
+
633
+ _colorDOM.style.backgroundColor = _inactiveColor;
634
+ _colorDOM.style.borderColor = _inactiveColor;
635
+ _nameDOM.style.color = _inactiveColor;
636
+
637
+ const targetIndex = _series.colorState.findIndex(colorItem => colorItem.id === targetId);
638
+ if (targetIndex > -1) {
639
+ _series.colorState[targetIndex].show = false;
640
+ }
641
+
642
+ _targetDOM.dataset.inactive = true;
643
+ };
644
+
645
+ /**
646
+ * callback for legendBoxDOM to show/hide clicked series
647
+ *
648
+ * @param {Element} _targetDOM - target DOM
649
+ * @param {string} _activeColor - active color
650
+ * @returns {void}
651
+ */
652
+ const activeDomAndSeries = (_targetDOM, _activeColor) => {
653
+ const _colorDOM = _targetDOM?.getElementsByClassName(classList.color)[0];
654
+ const _nameDOM = _targetDOM?.getElementsByClassName(classList.name)[0];
655
+ const _series = Object.values(this.seriesList)[0];
656
+ const targetId = _targetDOM?.series?.cId;
657
+
658
+ _colorDOM.style.backgroundColor = _targetDOM?.series?.color;
659
+ _nameDOM.style.color = _activeColor;
660
+
661
+ const targetIndex = _series.colorState.findIndex(colorItem => colorItem.id === targetId);
662
+ if (targetIndex > -1) {
663
+ _series.colorState[targetIndex].show = true;
664
+ }
665
+
666
+ _targetDOM.dataset.inactive = false;
667
+ };
668
+
669
+ const hideAllSeries = () => {
670
+ const series = Object.values(this.seriesList)[0];
671
+ series.colorState.forEach((colorItem) => {
672
+ colorItem.show = false;
673
+ });
674
+ };
675
+ const showAllSeries = () => {
676
+ const series = Object.values(this.seriesList)[0];
677
+ series.colorState.forEach((colorItem) => {
678
+ colorItem.show = true;
679
+ });
680
+ };
518
681
 
519
682
  /**
520
683
  * callback for legendBoxDOM to show/hide clicked series
@@ -533,36 +696,57 @@ const modules = {
533
696
  return;
534
697
  }
535
698
 
536
- const colorDOM = targetDOM?.getElementsByClassName('ev-chart-legend-color')[0];
537
- const nameDOM = targetDOM?.getElementsByClassName('ev-chart-legend-name')[0];
538
- const targetId = targetDOM?.series?.cId;
539
- const isActive = !colorDOM?.className.includes('inactive');
699
+ const colorDOM = targetDOM?.getElementsByClassName(classList.color)[0];
700
+ const nameDOM = targetDOM?.getElementsByClassName(classList.name)[0];
701
+ const isActive = targetDOM?.dataset.inactive === 'false';
540
702
  const activeCount = series.colorState.filter(colorItem => colorItem.show).length;
541
703
 
542
- if (isActive && activeCount === 1) {
543
- return;
544
- }
545
-
546
704
  if (!colorDOM || !nameDOM) {
547
705
  return;
548
706
  }
549
707
 
550
- if (isActive) {
551
- colorDOM.style.backgroundColor = opt.inactive;
552
- colorDOM.style.borderColor = opt.inactive;
553
- nameDOM.style.color = opt.inactive;
554
- } else {
555
- colorDOM.style.backgroundColor = targetDOM?.series?.color;
556
- nameDOM.style.color = opt.color;
557
- }
708
+ // clickMode active - 클릭시 활성화
709
+ if (opt.clickMode === 'active') {
710
+ const legendContainerDOMs = Array.from(
711
+ this.legendBoxDOM.getElementsByClassName(classList.container),
712
+ );
713
+ const isActiveAll = legendContainerDOMs.every(dom => dom.dataset.inactive === 'false');
558
714
 
559
- const targetIndex = series.colorState.findIndex(colorItem => colorItem.id === targetId);
560
- if (targetIndex > -1) {
561
- series.colorState[targetIndex].show = !isActive;
715
+ if (isActiveAll) {
716
+ legendContainerDOMs.forEach((dom) => {
717
+ inactiveDomAndSeries(dom, opt.inactive);
718
+ });
719
+ hideAllSeries();
720
+
721
+ activeDomAndSeries(targetDOM, opt.color);
722
+ } else if (isActive) {
723
+ inactiveDomAndSeries(targetDOM, opt.inactive);
724
+ } else if (!isActive) {
725
+ activeDomAndSeries(targetDOM, opt.color);
726
+ }
727
+
728
+ const isInactiveAll = legendContainerDOMs.every(dom => dom.dataset.inactive === 'true');
729
+
730
+ if (isInactiveAll) {
731
+ legendContainerDOMs.forEach((dom) => {
732
+ activeDomAndSeries(dom, opt.color);
733
+ });
734
+ showAllSeries();
735
+ }
562
736
  }
563
737
 
564
- colorDOM.classList.toggle('inactive');
565
- nameDOM.classList.toggle('inactive');
738
+ // clickMode inactive - 클릭시 비활성화
739
+ if (opt.clickMode !== 'active') {
740
+ if (isActive && activeCount === 1) {
741
+ return;
742
+ }
743
+
744
+ if (isActive) {
745
+ inactiveDomAndSeries(targetDOM, opt.inactive);
746
+ } else {
747
+ activeDomAndSeries(targetDOM, opt.color);
748
+ }
749
+ }
566
750
 
567
751
  this.update({
568
752
  updateSeries: false,
@@ -791,13 +975,19 @@ const modules = {
791
975
  *
792
976
  * @returns {undefined}
793
977
  */
978
+ /**
979
+ * 새로운 범례 아이템을 생성하고 범례 영역에 추가
980
+ * @param {Object} series - 시리즈 정보 객체
981
+ */
794
982
  addLegend(series) {
795
983
  const opt = this.options.legend;
984
+
796
985
  const containerDOM = document.createElement('div');
797
986
  const colorDOM = document.createElement('span');
798
987
  const nameDOM = document.createElement('div');
799
988
 
800
- containerDOM.className = `ev-chart-legend-container ${!series.show ? ' inactive' : ''}`;
989
+ containerDOM.className = 'ev-chart-legend-container';
990
+ containerDOM.dataset.inactive = !series.show;
801
991
  containerDOM.series = series;
802
992
 
803
993
  colorDOM.className = 'ev-chart-legend-color';
@@ -808,7 +998,6 @@ const modules = {
808
998
 
809
999
  nameDOM.className = 'ev-chart-legend-name';
810
1000
 
811
- // set series color
812
1001
  let seriesColor;
813
1002
  if (!series.show) {
814
1003
  seriesColor = opt.inactive;
@@ -828,6 +1017,7 @@ const modules = {
828
1017
  }
829
1018
 
830
1019
  colorDOM.dataset.type = 'color';
1020
+
831
1021
  nameDOM.style.color = opt.color;
832
1022
  nameDOM.textContent = series.name;
833
1023
  nameDOM.setAttribute('title', series.name);
@@ -842,12 +1032,14 @@ const modules = {
842
1032
  } else {
843
1033
  containerDOM.style.width = '100%';
844
1034
  }
1035
+
845
1036
  containerDOM.style.height = `${this.legendItemHeight}px`;
846
1037
  containerDOM.style.display = 'inline-block';
847
1038
  containerDOM.style.overflow = 'hidden';
848
1039
  containerDOM.dataset.type = 'container';
849
1040
 
850
1041
  this.legendBoxDOM.insertBefore(containerDOM, this.legendBottomSpacer);
1042
+
851
1043
  if (series.show) {
852
1044
  this.seriesInfo.count++;
853
1045
  }
@@ -869,7 +1061,8 @@ const modules = {
869
1061
 
870
1062
  // create row
871
1063
  const rowDOM = document.createElement('tr');
872
- rowDOM.className = `ev-chart-legend--table__row ${!series.show ? ' inactive' : ''}`;
1064
+ rowDOM.className = 'ev-chart-legend--table__row';
1065
+ rowDOM.dataset.inactive = !series.show;
873
1066
  Util.setDOMStyle(rowDOM, opt.table?.style?.row);
874
1067
  rowDOM.series = series;
875
1068
  rowDOM.dataset.type = 'container';
@@ -39,6 +39,7 @@ const DEFAULT_OPTIONS = {
39
39
  height: 24,
40
40
  allowResize: false,
41
41
  virtualScroll: false,
42
+ clickMode: 'active',
42
43
  table: {
43
44
  use: false,
44
45
  columns: {
@@ -300,9 +300,11 @@ export const useDropdown = (param) => {
300
300
  if (props.filterable) {
301
301
  filterTextRef.value = '';
302
302
  }
303
- mv.value = val;
304
303
  isDropbox.value = false;
305
- changeMv();
304
+ if (mv.value !== val) {
305
+ mv.value = val;
306
+ changeMv();
307
+ }
306
308
  };
307
309
  const multipleClickItem = (val) => {
308
310
  if (props.filterable) {