evui 3.4.150 → 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.150",
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({
@@ -96,13 +96,13 @@ const modules = {
96
96
  const key = keys[x];
97
97
  const data = datas[key];
98
98
  const storeLength = data?.length;
99
- let lastTime = 0;
100
99
 
101
- if (!this.isInit || this.updateSeries) {
100
+ // 1) init / updateSeries 시 dataset shape 보장
101
+ if (!this.isInit || this.updateSeries || !this.dataSet[key]) {
102
102
  const defaultValues = {
103
103
  dataGroup: [],
104
104
  startIndex: 0,
105
- endIndex: 0,
105
+ endIndex: null,
106
106
  length: 0,
107
107
  fromTime: 0,
108
108
  toTime: 0,
@@ -114,122 +114,126 @@ const modules = {
114
114
  };
115
115
  }
116
116
 
117
- this.dataSet[key].length = this.options.realTimeScatter.range || 300;
117
+ const dataset = this.dataSet[key];
118
+ const dataGroup = dataset.dataGroup;
119
+
120
+ // 2) range(length) 결정 + 변경 감지
121
+ const nextLength = this.options.realTimeScatter.range || 300;
122
+ const lengthChanged = dataset.length !== nextLength;
123
+ dataset.length = nextLength;
124
+ const length = dataset.length;
118
125
 
126
+ // 3) 이번 배치의 lastTime(초 단위) 계산
127
+ let lastTime = 0;
119
128
  for (let i = 0; i < storeLength; i++) {
120
129
  const item = data[i];
121
-
122
- if (lastTime < item.x) {
130
+ if (item && lastTime < item.x) {
123
131
  lastTime = item.x;
124
132
  }
125
133
  }
126
134
 
127
- lastTime = Math.floor(lastTime / 1000) * 1000;
135
+ lastTime = lastTime ? Math.floor(lastTime / 1000) * 1000 : 0;
128
136
 
129
- const dataGroupLastTime = this.dataSet[key].dataGroup.at(-1)?.data?.at(-1)?.x || Date.now();
137
+ const dataGroupLastTime = dataGroup.at(-1)?.data?.at(-1)?.x || Date.now();
138
+ const fallbackTime = Math.floor(dataGroupLastTime / 1000) * 1000;
130
139
 
131
- this.dataSet[key].toTime = lastTime
132
- || (dataGroupLastTime ? Math.floor(dataGroupLastTime / 1000) * 1000 : 0);
133
- this.dataSet[key].fromTime = this.dataSet[key].toTime - this.dataSet[key].length * 1000;
134
- this.dataSet[key].endIndex = this.dataSet[key].length - 1;
140
+ // 4) prevToTime은 덮기 전 값 (없으면 fallback)
141
+ const prevToTime = dataset.toTime || fallbackTime;
135
142
 
136
- if (
137
- (this.dataSet[key].toTime - lastTime) / 1000
138
- > this.dataSet[key].length && key === ''
139
- ) {
140
- return;
141
- }
143
+ // 5) nextToTime 결정: 새 데이터가 있으면 lastTime, 없으면 이전 유지
144
+ const nextToTime = lastTime || prevToTime;
142
145
 
143
- let gapCount = (lastTime - this.dataSet[key].toTime) / 1000;
144
- if (gapCount > 0) {
145
- this.dataSet[key].toTime = lastTime;
146
- this.dataSet[key].fromTime = lastTime
147
- - this.dataSet[key].length * 1000;
146
+ // 6) endIndex/startIndex 초기화 (최초 1회) + length 변경 시 재구성
147
+ if (dataset.endIndex == null || lengthChanged) {
148
+ dataset.startIndex = 0;
149
+ dataset.endIndex = length - 1;
150
+
151
+ // dataGroup 크기 맞추고 모두 reset
152
+ dataGroup.length = length;
153
+ for (let i = 0; i < length; i++) {
154
+ dataGroup[i] = dataGroup[i] || { data: [], max: 0, min: Infinity };
155
+ dataGroup[i].data.length = 0;
156
+ dataGroup[i].max = 0;
157
+ dataGroup[i].min = Infinity;
158
+ }
159
+
160
+ // toTime/fromTime도 새 기준으로 맞춤
161
+ dataset.toTime = nextToTime;
162
+ dataset.fromTime = dataset.toTime - length * 1000;
148
163
  }
149
164
 
150
- for (let i = 0; i < this.dataSet[key].length; i++) {
151
- const defaultValues = {
152
- data: [],
153
- max: 0,
154
- min: Infinity,
155
- };
165
+ // 7) gapCount 계산 (반드시 정수) prevToTime 기준
166
+ const rawGap = (nextToTime - prevToTime) / 1000;
167
+ const gapCount = Number.isFinite(rawGap) ? Math.max(0, Math.floor(rawGap)) : 0;
156
168
 
157
- this.dataSet[key].dataGroup[i] = {
158
- ...defaultValues,
159
- ...this.dataSet[key].dataGroup[i],
160
- };
169
+ // 8) to/from 갱신
170
+ dataset.toTime = nextToTime;
171
+ dataset.fromTime = dataset.toTime - length * 1000;
172
+
173
+ // (원래 코드에 있던 early return 유지)
174
+ if (lastTime && (dataset.toTime - lastTime) / 1000 > length && key === '') {
175
+ return;
161
176
  }
162
- if (gapCount > 0) {
163
- if (gapCount >= this.dataSet[key].length) {
164
- for (let i = 0; i < this.dataSet[key].length; i++) {
165
- this.dataSet[key].dataGroup[i].data.length = 0;
166
- this.dataSet[key].dataGroup[i].max = 0;
167
- this.dataSet[key].dataGroup[i].min = Infinity;
168
- }
169
177
 
170
- this.dataSet[key].startIndex = 0;
171
- this.dataSet[key].endIndex = this.dataSet[key].length - 1;
172
- } else {
173
- while (gapCount > 0) {
174
- if (
175
- this.dataSet[key].dataGroup[this.dataSet[key].startIndex]
176
- === null
177
- ) {
178
- this.dataSet[key].dataGroup[this.dataSet[key].startIndex] = {
179
- data: [],
180
- max: 0,
181
- min: Infinity,
182
- };
183
- } else {
184
- this.dataSet[key]
185
- .dataGroup[this.dataSet[key].startIndex].data.length = 0;
186
- this.dataSet[key]
187
- .dataGroup[this.dataSet[key].startIndex].max = 0;
188
- this.dataSet[key]
189
- .dataGroup[this.dataSet[key].startIndex].min = Infinity;
190
- }
178
+ const resetDataGroup = (group) => {
179
+ group.data.length = 0;
180
+ group.max = 0;
181
+ group.min = Infinity;
182
+ };
191
183
 
192
- ++this.dataSet[key].startIndex;
184
+ // 9) dataGroup 슬롯 확보
185
+ for (let i = 0; i < length; i++) {
186
+ if (!dataGroup[i]) {
187
+ dataGroup[i] = { data: [], max: 0, min: Infinity };
188
+ } else if (!dataGroup[i].data) {
189
+ dataGroup[i].data = [];
190
+ dataGroup[i].max = 0;
191
+ dataGroup[i].min = Infinity;
192
+ }
193
+ }
193
194
 
194
- if (this.dataSet[key].startIndex >= this.dataSet[key].length) {
195
- this.dataSet[key].startIndex = 0;
196
- }
195
+ // 10) gap만큼 전진 + 지나간 버킷 clear
196
+ if (gapCount > 0) {
197
+ if (gapCount >= length) {
198
+ for (let i = 0; i < length; i++) resetDataGroup(dataGroup[i]);
199
+ dataset.startIndex = 0;
200
+ dataset.endIndex = length - 1;
201
+ } else {
202
+ let currentStart = dataset.startIndex;
203
+ let currentEnd = dataset.endIndex;
197
204
 
198
- ++this.dataSet[key].endIndex;
199
- if (this.dataSet[key].endIndex >= this.dataSet[key].length) {
200
- this.dataSet[key].endIndex = 0;
201
- }
202
- --gapCount;
205
+ for (let i = 0; i < gapCount; i++) {
206
+ resetDataGroup(dataGroup[currentStart]);
207
+ currentStart = (currentStart + 1) % length;
208
+ currentEnd = (currentEnd + 1) % length;
203
209
  }
210
+
211
+ dataset.startIndex = currentStart;
212
+ dataset.endIndex = currentEnd;
204
213
  }
205
214
  }
206
215
 
216
+ // 11) 데이터 push (윈도우 안에 들어오는 것만)
207
217
  for (let i = 0; i < storeLength; i++) {
208
218
  const item = data[i];
209
- const xAxisTime = Math.floor(item.x / 1000) * 1000;
219
+ if (item) {
220
+ const xAxisTime = Math.floor(item.x / 1000) * 1000;
221
+
222
+ if (dataset.fromTime <= xAxisTime) {
223
+ let index = dataset.endIndex - (dataset.toTime - xAxisTime) / 1000;
224
+ if (index < 0) index = length + index;
225
+
226
+ const group = dataGroup[index];
227
+ group.data.push({
228
+ x: item.x,
229
+ y: item.y,
230
+ o: item.value ?? item.y,
231
+ color: item.color,
232
+ });
210
233
 
211
- if (this.dataSet[key].fromTime <= xAxisTime) {
212
- let index = this.dataSet[key].endIndex
213
- - (this.dataSet[key].toTime - xAxisTime) / 1000;
214
- if (index < 0) {
215
- index = this.dataSet[key].length + index;
234
+ group.max = Math.max(group.max, item.y);
235
+ group.min = Math.min(group.min, item.y);
216
236
  }
217
-
218
- this.dataSet[key].dataGroup[index].data.push({
219
- x: item.x,
220
- y: item.y,
221
- o: item.value ?? item.y,
222
- color: item.color,
223
- });
224
-
225
- this.dataSet[key].dataGroup[index].max = Math.max(
226
- this.dataSet[key].dataGroup[index].max,
227
- item.y,
228
- );
229
- this.dataSet[key].dataGroup[index].min = Math.min(
230
- this.dataSet[key].dataGroup[index].min,
231
- item.y,
232
- );
233
237
  }
234
238
  }
235
239
 
@@ -239,10 +243,10 @@ const modules = {
239
243
  minY: Infinity,
240
244
  };
241
245
 
242
- const { fromTime, toTime } = this.dataSet[key];
246
+ const { fromTime, toTime } = dataset;
243
247
 
244
- for (let i = 0; i < this.dataSet[key].length; i++) {
245
- const groupData = this.dataSet[key].dataGroup[i].data;
248
+ for (let i = 0; i < length; i++) {
249
+ const groupData = dataGroup[i].data;
246
250
  for (let j = 0; j < groupData.length; j++) {
247
251
  const item = groupData[j];
248
252
  // 현재 시간 범위 내의 데이터만 minMax 계산에 포함
@@ -267,8 +271,8 @@ const modules = {
267
271
 
268
272
  minMaxValues.maxY = Math.max(minMaxValues.maxY, tempMinMax.maxY);
269
273
  minMaxValues.minY = Math.min(minMaxValues.minY, tempMinMax.minY);
270
- minMaxValues.fromTime = this.dataSet[key].fromTime;
271
- minMaxValues.toTime = this.dataSet[key].toTime;
274
+ minMaxValues.fromTime = dataset.fromTime;
275
+ minMaxValues.toTime = dataset.toTime;
272
276
  }
273
277
 
274
278
  this.seriesInfo.charts.scatter.forEach((seriesID) => {
@@ -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
  /**