evui 3.4.151 → 3.4.152

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.151",
3
+ "version": "3.4.152",
4
4
  "description": "A EXEM Library project",
5
5
  "author": "exem <dev_client@ex-em.com>",
6
6
  "license": "MIT",
@@ -34,70 +34,71 @@
34
34
  import EvChartToolbar from './ChartToolbar';
35
35
  import { useModel, useWrapper, useZoomModel } from './uses';
36
36
 
37
- export default {
38
- name: 'EvChart',
39
- components: {
40
- EvChartToolbar,
37
+ export default {
38
+ name: 'EvChart',
39
+ components: {
40
+ EvChartToolbar,
41
+ },
42
+ props: {
43
+ selectedItem: {
44
+ type: Object,
45
+ default: null,
41
46
  },
42
- props: {
43
- selectedItem: {
44
- type: Object,
45
- default: null,
46
- },
47
- selectedLabel: {
48
- type: Object,
49
- default: null,
50
- },
51
- selectedSeries: {
52
- type: Object,
53
- default: null,
54
- },
55
- options: {
56
- type: Object,
57
- default: () => ({}),
58
- },
59
- data: {
60
- type: Object,
61
- default: () => ({}),
62
- },
63
- resizeTimeout: {
64
- type: Number,
65
- default: 0,
66
- },
67
- zoomStartIdx: {
68
- type: Number,
69
- default: 0,
70
- },
71
- zoomEndIdx: {
72
- type: Number,
73
- default: 0,
74
- },
75
- realTimeScatterReset: {
76
- type: Boolean,
77
- default: false,
78
- },
47
+ selectedLabel: {
48
+ type: Object,
49
+ default: null,
79
50
  },
80
- emits: [
81
- 'click',
82
- 'dbl-click',
83
- 'drag-select',
84
- 'mouse-move',
85
- 'update:selectedItem',
86
- 'update:selectedLabel',
87
- 'update:selectedSeries',
88
- 'update:zoomStartIdx',
89
- 'update:zoomEndIdx',
90
- 'update:realTimeScatterReset',
91
- ],
92
- setup(props, { emit }) {
93
- let evChart = null;
94
- const isMounted = ref(false);
95
- const injectIsChartGroup = inject('isChartGroup', false);
96
- const injectBrushSeries = inject('brushSeries', { list: [], chartIdx: null });
97
- const injectGroupSelectedLabel = inject('groupSelectedLabel', null);
98
- const injectGroupHoveredLabel = inject('groupHoveredLabel', null);
99
- const injectBrushIdx = inject('brushIdx', { start: 0, end: -1 });
100
- const injectEvChartPropsInGroup = inject('evChartPropsInGroup', []);
51
+ selectedSeries: {
52
+ type: Object,
53
+ default: null,
54
+ },
55
+ options: {
56
+ type: Object,
57
+ default: () => ({}),
58
+ },
59
+ data: {
60
+ type: Object,
61
+ default: () => ({}),
62
+ },
63
+ resizeTimeout: {
64
+ type: Number,
65
+ default: 0,
66
+ },
67
+ zoomStartIdx: {
68
+ type: Number,
69
+ default: 0,
70
+ },
71
+ zoomEndIdx: {
72
+ type: Number,
73
+ default: 0,
74
+ },
75
+ realTimeScatterReset: {
76
+ type: Boolean,
77
+ default: false,
78
+ },
79
+ },
80
+ emits: [
81
+ 'click',
82
+ 'dbl-click',
83
+ 'drag-select',
84
+ 'mouse-move',
85
+ 'update:selectedItem',
86
+ 'update:selectedLabel',
87
+ 'update:selectedSeries',
88
+ 'update:zoomStartIdx',
89
+ 'update:zoomEndIdx',
90
+ 'update:realTimeScatterReset',
91
+ 'click-legend',
92
+ ],
93
+ setup(props, { emit }) {
94
+ let evChart = null;
95
+ const isMounted = ref(false);
96
+ const injectIsChartGroup = inject('isChartGroup', false);
97
+ const injectBrushSeries = inject('brushSeries', { list: [], chartIdx: null });
98
+ const injectGroupSelectedLabel = inject('groupSelectedLabel', null);
99
+ const injectGroupHoveredLabel = inject('groupHoveredLabel', null);
100
+ const injectBrushIdx = inject('brushIdx', { start: 0, end: -1 });
101
+ const injectEvChartPropsInGroup = inject('evChartPropsInGroup', []);
101
102
 
102
103
  const {
103
104
  eventListeners,
@@ -269,9 +270,12 @@
269
270
 
270
271
  emit('update:realTimeScatterReset', false);
271
272
  }
272
- });
273
+ },
274
+ );
273
275
 
274
- watch(() => props.options.realTimeScatter?.use, (use) => {
276
+ watch(
277
+ () => props.options.realTimeScatter?.use,
278
+ (use) => {
275
279
  evChart.options.realTimeScatter.use = use ?? false;
276
280
 
277
281
  evChart.update({
@@ -542,10 +542,28 @@ const modules = {
542
542
  this.brushSeries.chartIdx = chartIdx;
543
543
  }
544
544
 
545
- this.update({
546
- updateSeries: false,
547
- updateSelTip: { update: true, keepDomain: true },
548
- });
545
+ if (this.options.eventBehavior?.legendClick !== 'emitOnly') {
546
+ this.update({
547
+ updateSeries: false,
548
+ updateSelTip: { update: true, keepDomain: true },
549
+ });
550
+ }
551
+
552
+ // click-legend event 발생
553
+ const activeSeries = Object.values(this.seriesList).filter(series => series.show);
554
+ const activeSeriesIds = activeSeries.map(series => series.sId);
555
+ const isActiveAll = activeSeriesIds.length === Object.values(this.seriesList).length;
556
+ const args = {
557
+ e,
558
+ data: {
559
+ seriesIds: isActiveAll ? [] : activeSeriesIds,
560
+ isActiveAll,
561
+ },
562
+ };
563
+
564
+ if (typeof this.listeners['click-legend'] === 'function') {
565
+ this.listeners['click-legend'](args);
566
+ }
549
567
  };
550
568
 
551
569
  /**
@@ -750,10 +768,28 @@ const modules = {
750
768
  }
751
769
  }
752
770
 
753
- this.update({
754
- updateSeries: false,
755
- updateSelTip: { update: true, keepDomain: true },
756
- });
771
+ if (this.options.eventBehavior?.legendClick !== 'emitOnly') {
772
+ this.update({
773
+ updateSeries: false,
774
+ updateSelTip: { update: true, keepDomain: true },
775
+ });
776
+ }
777
+
778
+ // click-legend event 발생
779
+ const activeSeries = series.colorState.filter(colorItem => colorItem.show);
780
+ const activeSerieIndices = activeSeries.map(colorItem => +colorItem.id.split('#')[1]);
781
+ const isActiveAll = series.colorState.length === activeSeries.length;
782
+ const args = {
783
+ e,
784
+ data: {
785
+ seriesIndices: isActiveAll ? [] : activeSerieIndices,
786
+ isActiveAll,
787
+ },
788
+ };
789
+
790
+ if (typeof this.listeners['click-legend'] === 'function') {
791
+ this.listeners['click-legend'](args);
792
+ }
757
793
  };
758
794
 
759
795
  /**
@@ -11,7 +11,8 @@ class LinearScale extends Scale {
11
11
  * @returns {string} formatted label
12
12
  */
13
13
  getTruthyValue(value) {
14
- return truthyNumber(value) ? Number(value.toFixed(this.decimalPoint)) : value;
14
+ const decimalPoint = this.adjustedDecimalPoint ?? this.decimalPoint;
15
+ return truthyNumber(value) ? Number(value.toFixed(decimalPoint)) : value;
15
16
  }
16
17
 
17
18
  getLabelFormat(value, data = {}) {
@@ -31,8 +32,8 @@ class LinearScale extends Scale {
31
32
  }
32
33
  }
33
34
 
34
-
35
- return Util.labelSignFormat(value, this.decimalPoint);
35
+ const decimalPoint = this.adjustedDecimalPoint ?? this.decimalPoint;
36
+ return Util.labelSignFormat(value, decimalPoint);
36
37
  }
37
38
 
38
39
 
@@ -58,41 +59,171 @@ class LinearScale extends Scale {
58
59
  return Math.ceil((max - min) / step);
59
60
  }
60
61
 
61
- /**
62
- * Get decimal point from range
63
- * @param {object} {
64
- * graphRange: number,
65
- * numberOfSteps: number,
66
- * interval: number,
67
- * }
62
+
63
+ /**
64
+ * Get auto decimal point from interval
65
+ * interval을 표현할 수 있는 최소 decimal 반환
66
+ * 너무 긴 decimal은 제한
67
+ * @param {number} interval
68
68
  * @returns {number} decimal point
69
69
  */
70
- getDecimalPointFromRange({
71
- graphRange,
72
- numberOfSteps,
73
- }) {
74
- if (numberOfSteps <= 0 || graphRange === 0) {
70
+ getAutoDecimalPointFromInterval(interval) {
71
+ if (!isFinite(interval) || interval === 0) {
75
72
  return 0;
76
73
  }
77
74
 
78
- const interval = graphRange / numberOfSteps;
79
- if (interval === 0) {
75
+ const absInterval = Math.abs(interval);
76
+
77
+ // 1 미만 값 처리 (소수점 최대 10자리 제한)
78
+ if (absInterval < 1) {
79
+ let decimals = 0;
80
+ let temp = absInterval;
81
+
82
+ while (temp < 1) {
83
+ temp *= 10;
84
+ decimals++;
85
+
86
+ if (decimals > 10) {
87
+ break;
88
+ }
89
+ }
90
+
91
+ return decimals;
92
+ }
93
+
94
+ // 1 이상 값 처리 (소수점 최대 2자리 제한)
95
+ for (let decimal = 0; decimal <= 6; decimal++) {
96
+ const rounded = Number(absInterval.toFixed(decimal));
97
+
98
+ if (Math.abs(rounded - absInterval) < 1e-10) {
99
+ return Math.min(decimal, 2);
100
+ }
101
+ }
102
+
103
+ return 2;
104
+ }
105
+
106
+ /**
107
+ * axis interval을 nice number로 변환
108
+ * (1, 2, 5 × 10^n)
109
+ *
110
+ * @param {Object} params
111
+ * @param {number} params.range
112
+ * @param {boolean} params.round
113
+ * @returns {number}
114
+ */
115
+ getNiceNumber({ range, round = false }) {
116
+ if (!isFinite(range) || range <= 0) {
80
117
  return 0;
81
118
  }
82
119
 
83
- let decimals = 0;
84
- let temp = interval;
120
+ const exponent = Math.floor(Math.log10(range));
121
+ const fraction = range / (10 ** exponent);
122
+
123
+ let niceFraction;
85
124
 
86
- while (temp < 1) {
87
- temp *= 10;
88
- decimals++;
125
+ if (round) {
126
+ if (fraction < 1.5) {
127
+ niceFraction = 1;
128
+ } else if (fraction < 3) {
129
+ niceFraction = 2;
130
+ } else if (fraction < 7) {
131
+ niceFraction = 5;
132
+ } else {
133
+ niceFraction = 10;
134
+ }
135
+ } else if (fraction <= 1) {
136
+ niceFraction = 1;
137
+ } else if (fraction <= 2) {
138
+ niceFraction = 2;
139
+ } else if (fraction <= 5) {
140
+ niceFraction = 5;
141
+ } else {
142
+ niceFraction = 10;
143
+ }
144
+
145
+ return niceFraction * (10 ** exponent);
146
+ }
89
147
 
90
- if (decimals > 10) {
91
- break;
148
+ /**
149
+ * With range information, calculate how many labels in axis
150
+ * @param {object} range min/max information
151
+ *
152
+ * @returns {object} steps, interval, min/max graph value
153
+ */
154
+ calculateSteps(range) {
155
+ const { minValue, maxValue } = range;
156
+ const maxSteps = Math.max(1, range.maxSteps);
157
+
158
+ const hasUserRange = Array.isArray(this.range) && this.range.length === 2;
159
+ const hasUserInterval = (
160
+ typeof this.interval === 'number'
161
+ || (typeof this.interval === 'object' && this.interval !== null)
162
+ );
163
+
164
+ const resolvedInterval = hasUserInterval ? this.getInterval(range) : null;
165
+ const isValidInterval = (
166
+ resolvedInterval != null
167
+ && resolvedInterval > 0
168
+ && isFinite(resolvedInterval)
169
+ );
170
+
171
+ const graphMin = +minValue;
172
+ let graphMax = +maxValue;
173
+ const graphRange = graphMax - graphMin;
174
+
175
+ let interval;
176
+ let steps;
177
+
178
+ if (hasUserRange && isValidInterval) {
179
+ // 1) user range + interval
180
+ const candidateSteps = graphRange / resolvedInterval;
181
+ const isExactlyDividable = Math.abs(candidateSteps - Math.round(candidateSteps)) < 1e-10;
182
+
183
+ if (isExactlyDividable && candidateSteps <= maxSteps) {
184
+ interval = resolvedInterval;
185
+ steps = Math.round(candidateSteps);
186
+ } else {
187
+ // interval 호환되지 않음 -> 사용자 interval을 사용하지 않음
188
+ steps = maxSteps;
189
+ interval = graphRange / steps;
190
+ }
191
+ } else if (hasUserRange) {
192
+ // 2) user range only
193
+ steps = maxSteps;
194
+ interval = graphRange / steps;
195
+ } else if (isValidInterval) {
196
+ // 3) user interval only
197
+ interval = resolvedInterval;
198
+ steps = Math.ceil(graphRange / interval);
199
+
200
+ while (steps > maxSteps) {
201
+ interval *= 2;
202
+ steps = Math.ceil(graphRange / interval);
92
203
  }
204
+
205
+ graphMax = graphMin + (interval * steps);
206
+ } else {
207
+ // 4) auto
208
+ interval = this.getNiceNumber({
209
+ range: graphRange / maxSteps,
210
+ round: true,
211
+ });
212
+
213
+ steps = Math.ceil(graphRange / interval);
214
+ graphMax = graphMin + (interval * steps);
93
215
  }
94
216
 
95
- return decimals;
217
+ this.adjustedDecimalPoint = this.decimalPoint === 'auto'
218
+ ? this.getAutoDecimalPointFromInterval(interval)
219
+ : this.decimalPoint;
220
+
221
+ return {
222
+ steps,
223
+ interval,
224
+ graphMin,
225
+ graphMax,
226
+ };
96
227
  }
97
228
  }
98
229
 
@@ -44,6 +44,100 @@ class TimeScale extends Scale {
44
44
  }
45
45
  return Math.ceil((max - min) / step);
46
46
  }
47
+
48
+ /**
49
+ * With range information, calculate how many labels in axis
50
+ * @param {object} range min/max information
51
+ *
52
+ * @returns {object} steps, interval, min/max graph value
53
+ */
54
+ calculateSteps(range) {
55
+ const { maxValue, minValue, maxSteps } = range;
56
+
57
+ // 사용자 interval로 인식하는 경우: 숫자 또는 객체({ time, unit }) 형태만
58
+ // 문자열('hour', 'second' 등)은 기존 로직(분기 D)으로 처리
59
+ const hasUserRange = Array.isArray(this.range) && this.range.length === 2;
60
+ const hasUserInterval = (
61
+ typeof this.interval === 'number'
62
+ || (typeof this.interval === 'object' && this.interval !== null)
63
+ );
64
+
65
+ const resolvedInterval = hasUserInterval ? this.getInterval(range) : null;
66
+ const isValidInterval = (
67
+ resolvedInterval != null
68
+ && resolvedInterval > 0
69
+ && isFinite(resolvedInterval)
70
+ );
71
+
72
+ const graphMin = +minValue;
73
+ let graphMax = +maxValue;
74
+ const graphRange = graphMax - graphMin;
75
+
76
+ let interval;
77
+ let steps;
78
+
79
+ if (hasUserRange && isValidInterval) {
80
+ // 1) user range + interval
81
+ const candidateSteps = graphRange / resolvedInterval;
82
+ const isExactlyDividable = Math.abs(candidateSteps - Math.round(candidateSteps)) < 1e-10;
83
+ if (isExactlyDividable && candidateSteps <= maxSteps) {
84
+ // 1-1) interval 호환되는 경우
85
+ interval = resolvedInterval;
86
+ steps = Math.round(candidateSteps);
87
+ } else {
88
+ // 1-2) interval 호환되지 않음 -> 사용자 interval을 사용하지 않음
89
+ steps = maxSteps;
90
+ interval = graphRange / steps;
91
+ }
92
+ } else if (hasUserRange) {
93
+ // 2) user range only
94
+ steps = maxSteps;
95
+ interval = graphRange / steps;
96
+ } else if (isValidInterval) {
97
+ // 3) user interval only
98
+ interval = resolvedInterval;
99
+ steps = Math.ceil(graphRange / interval);
100
+ while (steps > maxSteps) {
101
+ interval *= 2;
102
+ steps = Math.ceil(graphRange / interval);
103
+ }
104
+ graphMax = graphMin + (interval * steps);
105
+ } else {
106
+ // 4) 기존 로직
107
+ interval = this.getInterval(range);
108
+ let increase = minValue;
109
+ let numberOfSteps;
110
+
111
+ while (increase < maxValue) {
112
+ increase += interval;
113
+ }
114
+
115
+ graphMax = increase;
116
+
117
+ numberOfSteps = Math.round(graphRange / interval);
118
+
119
+ while (numberOfSteps > maxSteps) {
120
+ interval *= 2;
121
+ numberOfSteps = Math.round(graphRange / interval);
122
+ const tempInterval = graphRange / numberOfSteps;
123
+ interval = this.decimalPoint ? tempInterval : Math.ceil(tempInterval);
124
+ }
125
+
126
+ if (graphMax - graphMin > (numberOfSteps * interval)) {
127
+ const tempInterval = (graphMax - graphMin) / numberOfSteps;
128
+ interval = this.decimalPoint ? tempInterval : Math.ceil(tempInterval);
129
+ }
130
+
131
+ steps = numberOfSteps;
132
+ }
133
+
134
+ return {
135
+ steps,
136
+ interval,
137
+ graphMin,
138
+ graphMax,
139
+ };
140
+ }
47
141
  }
48
142
 
49
143
  export default TimeScale;
@@ -248,6 +248,9 @@ const DEFAULT_OPTIONS = {
248
248
  },
249
249
  seriesReverse: false,
250
250
  coordinateDedupe: true,
251
+ eventBehavior: {
252
+ legendClick: 'update',
253
+ },
251
254
  };
252
255
 
253
256
  const DEFAULT_DATA = {
@@ -402,6 +405,10 @@ export const useModel = (injectGroupSelectedLabel, injectGroupHoveredLabel) => {
402
405
  injectGroupHoveredLabel.value.label = null;
403
406
  }
404
407
  },
408
+ 'click-legend': async (e) => {
409
+ await nextTick();
410
+ emit('click-legend', e);
411
+ },
405
412
  };
406
413
 
407
414
  return {