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/dist/evui.common.js +523 -177
- package/dist/evui.common.js.map +1 -1
- package/dist/evui.umd.js +523 -177
- 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.vue +68 -64
- package/src/components/chart/model/model.store.js +101 -97
- package/src/components/chart/plugins/plugins.legend.js +44 -8
- package/src/components/chart/scale/scale.linear.js +156 -25
- package/src/components/chart/scale/scale.time.js +94 -0
- package/src/components/chart/uses.js +7 -0
package/package.json
CHANGED
|
@@ -34,70 +34,71 @@
|
|
|
34
34
|
import EvChartToolbar from './ChartToolbar';
|
|
35
35
|
import { useModel, useWrapper, useZoomModel } from './uses';
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
37
|
+
export default {
|
|
38
|
+
name: 'EvChart',
|
|
39
|
+
components: {
|
|
40
|
+
EvChartToolbar,
|
|
41
|
+
},
|
|
42
|
+
props: {
|
|
43
|
+
selectedItem: {
|
|
44
|
+
type: Object,
|
|
45
|
+
default: null,
|
|
41
46
|
},
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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]
|
|
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 =
|
|
137
|
+
const dataGroupLastTime = dataGroup.at(-1)?.data?.at(-1)?.x || Date.now();
|
|
138
|
+
const fallbackTime = Math.floor(dataGroupLastTime / 1000) * 1000;
|
|
130
139
|
|
|
131
|
-
|
|
132
|
-
|
|
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
|
-
|
|
137
|
-
|
|
138
|
-
> this.dataSet[key].length && key === ''
|
|
139
|
-
) {
|
|
140
|
-
return;
|
|
141
|
-
}
|
|
143
|
+
// 5) nextToTime 결정: 새 데이터가 있으면 lastTime, 없으면 이전 유지
|
|
144
|
+
const nextToTime = lastTime || prevToTime;
|
|
142
145
|
|
|
143
|
-
|
|
144
|
-
if (
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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
|
-
|
|
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
|
-
|
|
195
|
-
|
|
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
|
-
|
|
199
|
-
|
|
200
|
-
|
|
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
|
-
|
|
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
|
-
|
|
212
|
-
|
|
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 } =
|
|
246
|
+
const { fromTime, toTime } = dataset;
|
|
243
247
|
|
|
244
|
-
for (let i = 0; i <
|
|
245
|
-
const groupData =
|
|
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 =
|
|
271
|
-
minMaxValues.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.
|
|
546
|
-
|
|
547
|
-
|
|
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.
|
|
754
|
-
|
|
755
|
-
|
|
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
|
/**
|