evui 3.4.70 → 3.4.72

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.70",
3
+ "version": "3.4.72",
4
4
  "description": "A EXEM Library project",
5
5
  "author": "exem <dev_client@ex-em.com>",
6
6
  "license": "MIT",
@@ -960,6 +960,10 @@ class EvChart {
960
960
  if (this.options.legend.type === 'gradient') {
961
961
  this.legendBoxDOM.removeEventListener('mousedown', this.onLegendMouseDown);
962
962
  }
963
+ if (this.options.legend.virtualScroll && !this.useTable) {
964
+ this.legendBoxDOM.removeEventListener('resize', this.updateVisibleRowCount);
965
+ this.legendBoxDOM.removeEventListener('scroll', this.renderVisibleLegends);
966
+ }
963
967
  }
964
968
 
965
969
  if (this.resizeDOM) {
@@ -984,6 +988,15 @@ class EvChart {
984
988
  this.tooltipDOM = null;
985
989
  }
986
990
 
991
+ if (this.renderVisibleLegendsFrameId != null) {
992
+ cancelAnimationFrame(this.renderVisibleLegendsFrameId);
993
+ this.renderVisibleLegendsFrameId = null;
994
+ }
995
+ if (this.updateVisibleRowCountFrameId != null) {
996
+ cancelAnimationFrame(this.updateVisibleRowCountFrameId);
997
+ this.updateVisibleRowCountFrameId = null;
998
+ }
999
+
987
1000
  this.wrapperDOM = null;
988
1001
  this.chartDOM = null;
989
1002
  this.legendDOM = null;
@@ -16,7 +16,7 @@ class Line {
16
16
 
17
17
  ['color', 'pointFill', 'fillColor'].forEach((colorProp) => {
18
18
  if (this[colorProp] === undefined) {
19
- this[colorProp] = colorProp === 'pointFill' ? this.color : COLOR[sIdx];
19
+ this[colorProp] = colorProp === 'pointFill' ? this.color : COLOR[(sIdx) % COLOR.length];
20
20
  }
21
21
  });
22
22
  this.type = 'line';
@@ -72,7 +72,7 @@ class Line {
72
72
  extent = this.extent.normal;
73
73
  }
74
74
 
75
- const getOpacity = colorStr => (colorStr.includes('rgba') ? Util.getOpacity(colorStr) : extent.opacity);
75
+ const getOpacity = colorStr => (colorStr?.includes('rgba') ? Util.getOpacity(colorStr) : extent.opacity);
76
76
  const mainColor = this.color;
77
77
  const mainColorOpacity = getOpacity(mainColor);
78
78
  const pointFillColor = this.pointFill;
@@ -29,10 +29,30 @@ const modules = {
29
29
  } else {
30
30
  this.legendBoxDOM.style.overflowX = 'hidden';
31
31
  this.legendBoxDOM.style.overflowY = 'auto';
32
+ this.legendBoxDOM.style.height = '100%';
32
33
  }
33
34
 
34
35
  this.legendDOM.appendChild(this.legendBoxDOM);
35
36
  this.wrapperDOM.appendChild(this.legendDOM);
37
+
38
+
39
+ if (this.options.legend.virtualScroll && !this.useTable) {
40
+ this.legendTopSpacer = document.createElement('div');
41
+ this.legendTopSpacer.className = 'ev-chart-legend--top-spacer';
42
+ this.legendTopSpacer.style.clear = 'both';
43
+ this.legendTopSpacer.style.opacity = 0;
44
+
45
+ this.legendBottomSpacer = document.createElement('div');
46
+ this.legendBottomSpacer.className = 'ev-chart-legend--bottom-spacer';
47
+ this.legendBottomSpacer.style.clear = 'both';
48
+ this.legendBottomSpacer.style.opacity = 0;
49
+
50
+ this.legendBoxDOM.appendChild(this.legendTopSpacer);
51
+ this.legendBoxDOM.appendChild(this.legendBottomSpacer);
52
+ this.updateVisibleRowCountFrameId = requestAnimationFrame(() => {
53
+ this.updateVisibleRowCount();
54
+ });
55
+ }
36
56
  },
37
57
 
38
58
  /**
@@ -75,6 +95,7 @@ const modules = {
75
95
  initLegend() {
76
96
  this.isHeatMapType = this.options.type === 'heatMap';
77
97
  this.useTable = !!this.options.legend?.table?.use && this.options.type !== 'heatmap' && this.options.type !== 'scatter';
98
+ this.legendItemHeight = 18;
78
99
 
79
100
  if (!this.isInitLegend) {
80
101
  this.createLegendLayout();
@@ -94,6 +115,86 @@ const modules = {
94
115
  this.isLegendMove = false;
95
116
  },
96
117
 
118
+ /**
119
+ * Calculate and update the number of rows and items per row that are visible
120
+ * within the legend container based on its dimensions and the layout of the legend.
121
+ * If the legend is positioned on the right or left, only one item per row is shown.
122
+ * Otherwise, the number of items per row is determined by dividing the container width
123
+ * by the item width.
124
+ *
125
+ * @returns {undefined}
126
+ */
127
+ updateVisibleRowCount() {
128
+ const isLeftOrRight = this.options.legend.position === 'right' || this.options.legend.position === 'left';
129
+ const legendBoxHeight = this.legendBoxDOM.clientHeight;
130
+ const legendBoxWidth = this.legendBoxDOM.clientWidth;
131
+
132
+ const itemWidth = Math.max(this.options.legend.width - 8, 1);
133
+ const useLegendSeriesCount = Object.values(this.seriesList)
134
+ .filter(series => series.showLegend !== false)
135
+ .length;
136
+
137
+ this.itemsPerRow = isLeftOrRight ? 1 : Math.floor(legendBoxWidth / itemWidth);
138
+ this.totalRowCount = Math.ceil(useLegendSeriesCount / this.itemsPerRow);
139
+ this.visibleRowCount = legendBoxHeight > this.legendItemHeight
140
+ ? Math.round(legendBoxHeight / this.legendItemHeight) + 1 : this.totalRowCount;
141
+ },
142
+
143
+ /**
144
+ * Calculate and set the start and end row indexes for visible items within the
145
+ * scrollable legend area. Determines the row range that should be displayed based
146
+ * on the current scroll position and the height of each legend item.
147
+ *
148
+ * @returns {undefined}
149
+ */
150
+ updateStartEndRowIndex() {
151
+ const index = Math.max(Math.floor(this.legendBoxDOM.scrollTop / this.legendItemHeight), 0);
152
+ this.startRowIndex = index > this.totalRowCount - 1 ? 0 : index;
153
+ this.endRowIndex = this.startRowIndex + this.visibleRowCount;
154
+ },
155
+
156
+ /**
157
+ * Render only the visible legend items in the legend container based on the
158
+ * calculated start and end row indexes. Removes existing legend items,
159
+ * adjusts spacer heights to enable smooth scrolling, and adds only the items
160
+ * within the visible range.
161
+ *
162
+ * @returns {undefined}
163
+ */
164
+ renderVisibleLegends() {
165
+ this.updateStartEndRowIndex();
166
+
167
+ const elementsToRemove = this.legendBoxDOM.querySelectorAll('.ev-chart-legend-container');
168
+ elementsToRemove.forEach(element => element.remove());
169
+
170
+ const totalScrollHeight = this.totalRowCount * this.legendItemHeight;
171
+ const top = this.startRowIndex * this.legendItemHeight;
172
+ const bottom = Math.max(
173
+ totalScrollHeight - this.visibleRowCount * this.legendItemHeight - top,
174
+ 0,
175
+ );
176
+ this.legendTopSpacer.style.height = `${top}px`;
177
+ this.legendBottomSpacer.style.height = `${bottom}px`;
178
+
179
+ const startIndex = this.startRowIndex * this.itemsPerRow;
180
+ const endIndex = this.endRowIndex * this.itemsPerRow;
181
+
182
+ const groups = this.data.groups.at(0);
183
+
184
+ let useLegendSeries = [];
185
+ if (groups) {
186
+ useLegendSeries = groups.slice().reverse()
187
+ .filter(sId => this.seriesList[sId].showLegend)
188
+ .map(sId => [sId, this.seriesList[sId]]);
189
+ } else {
190
+ useLegendSeries = Object.entries(this.seriesList)
191
+ .filter(([, series]) => series.showLegend);
192
+ }
193
+ useLegendSeries.slice(startIndex, endIndex).forEach(([, series]) => {
194
+ this.addLegend(series);
195
+ });
196
+ },
197
+
97
198
  /**
98
199
  * Add legend with group information to align each series properly.
99
200
  * Especially if a chart is stacked,
@@ -102,35 +203,75 @@ const modules = {
102
203
  * @returns {undefined}
103
204
  */
104
205
  addLegendList() {
105
- const groups = this.data.groups;
106
- const seriesList = this.seriesList;
206
+ const { groups } = this.data;
207
+ const { seriesList } = this;
107
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
+ }
218
+ } else {
219
+ this.addLegendForGroups(groups, seriesList, this.useTable);
220
+ this.addStandaloneLegends(seriesList, this.useTable);
221
+ }
222
+ },
223
+ /**
224
+ * Adds legends for each group in `groups` array, iterating through each series
225
+ * within the group in reverse order. This ensures the legends align with the series
226
+ * order as displayed in the chart. Only adds series with `showLegend` set to `true`.
227
+ *
228
+ * @param {Array} groups - Array of groups containing series identifiers.
229
+ * @param {Object} seriesList - Object containing all series, keyed by series ID.
230
+ * @param {boolean} useTable - Determines whether to add legends with additional values.
231
+ * @returns {undefined}
232
+ */
233
+ addLegendForGroups(groups, seriesList, useTable) {
108
234
  groups.forEach((group) => {
109
235
  group.slice().reverse().forEach((sId) => {
110
236
  const series = seriesList[sId];
111
-
112
237
  if (series && series.showLegend) {
113
- if (this.useTable) {
114
- this.addLegendWithValues(series);
115
- } else {
116
- this.addLegend(series);
117
- }
238
+ this.addLegendBasedOnType(series, useTable);
118
239
  }
119
240
  });
120
241
  });
121
-
242
+ },
243
+ /**
244
+ * Adds legends for series that are not part of any group. Iterates through each series
245
+ * in `seriesList` and only adds those that are not assigned to any group (based on `isExistGrp`)
246
+ * and have `showLegend` set to `true`.
247
+ *
248
+ * @param {Object} seriesList - Object containing all series, keyed by series ID.
249
+ * @param {boolean} useTable - Determines whether to add legends with additional values.
250
+ * @returns {undefined}
251
+ */
252
+ addStandaloneLegends(seriesList, useTable) {
122
253
  Object.values(seriesList).forEach((series) => {
123
- if (series.isExistGrp || !series.showLegend) {
124
- return;
125
- }
126
-
127
- if (this.useTable) {
128
- this.addLegendWithValues(series);
129
- } else {
130
- this.addLegend(series);
254
+ if (!series.isExistGrp && series.showLegend) {
255
+ this.addLegendBasedOnType(series, useTable);
131
256
  }
132
257
  });
133
258
  },
259
+ /**
260
+ * Adds a legend item for a specific series, determining whether to include additional
261
+ * values based on the `useTable` parameter. Calls `addLegendWithValues` if `useTable` is true,
262
+ * otherwise calls `addLegend`.
263
+ *
264
+ * @param {Object} series - Series object containing data to display in the legend.
265
+ * @param {boolean} useTable - Determines whether to add legends with additional values.
266
+ * @returns {undefined}
267
+ */
268
+ addLegendBasedOnType(series, useTable) {
269
+ if (useTable) {
270
+ this.addLegendWithValues(series);
271
+ } else {
272
+ this.addLegend(series);
273
+ }
274
+ },
134
275
 
135
276
  /**
136
277
  * Add Legend with Color Information
@@ -353,6 +494,11 @@ const modules = {
353
494
  this.legendBoxDOM.addEventListener('mouseover', this.onLegendBoxOver);
354
495
  this.legendBoxDOM.addEventListener('mouseleave', this.onLegendBoxLeave);
355
496
 
497
+ if (this.options.legend.virtualScroll && !this.useTable) {
498
+ this.legendBoxDOM.addEventListener('resize', this.updateVisibleRowCount);
499
+ this.legendBoxDOM.addEventListener('scroll', this.renderVisibleLegends.bind(this));
500
+ }
501
+
356
502
  this.initResizeEvent();
357
503
  },
358
504
 
@@ -573,6 +719,7 @@ const modules = {
573
719
 
574
720
  /**
575
721
  * To update legend, remove all of legendBoxDOM's children
722
+ * (except spacers when virtualScroll is enabled)
576
723
  *
577
724
  * @returns {undefined}
578
725
  */
@@ -589,12 +736,14 @@ const modules = {
589
736
  legendTableDOM.removeChild(legendTableDOM.firstChild);
590
737
  }
591
738
  this.setLegendColumnHeader();
739
+ } else if (this.options.legend.virtualScroll) {
740
+ this.updateVisibleRowCount();
741
+ this.renderVisibleLegends();
592
742
  } else {
593
743
  while (legendBoxDOM.hasChildNodes()) {
594
744
  legendBoxDOM.removeChild(legendBoxDOM.firstChild);
595
745
  }
596
746
  }
597
-
598
747
  this.seriesInfo.count = 0;
599
748
  },
600
749
 
@@ -606,10 +755,18 @@ const modules = {
606
755
  destroyLegend() {
607
756
  const legendDOM = this.legendDOM;
608
757
 
758
+ if (this.renderVisibleLegendsFrameId != null) {
759
+ cancelAnimationFrame(this.renderVisibleLegendsFrameId);
760
+ this.renderVisibleLegendsFrameId = null;
761
+ }
762
+ if (this.updateVisibleRowCountFrameId != null) {
763
+ cancelAnimationFrame(this.updateVisibleRowCountFrameId);
764
+ this.updateVisibleRowCountFrameId = null;
765
+ }
766
+
609
767
  if (!legendDOM) {
610
768
  return;
611
769
  }
612
-
613
770
  legendDOM.remove();
614
771
 
615
772
  this.legendDOM = null;
@@ -666,8 +823,6 @@ const modules = {
666
823
  nameDOM.setAttribute('title', series.name);
667
824
  nameDOM.dataset.type = 'name';
668
825
 
669
- this.legendDOM.style.padding = '5px 0 0 0';
670
-
671
826
  containerDOM.appendChild(colorDOM);
672
827
  containerDOM.appendChild(nameDOM);
673
828
 
@@ -677,12 +832,12 @@ const modules = {
677
832
  } else {
678
833
  containerDOM.style.width = '100%';
679
834
  }
680
- containerDOM.style.height = '18px';
835
+ containerDOM.style.height = `${this.legendItemHeight}px`;
681
836
  containerDOM.style.display = 'inline-block';
682
837
  containerDOM.style.overflow = 'hidden';
683
838
  containerDOM.dataset.type = 'container';
684
839
 
685
- this.legendBoxDOM.appendChild(containerDOM);
840
+ this.legendBoxDOM.insertBefore(containerDOM, this.legendBottomSpacer);
686
841
  if (series.show) {
687
842
  this.seriesInfo.count++;
688
843
  }
@@ -38,6 +38,7 @@ const DEFAULT_OPTIONS = {
38
38
  width: 140,
39
39
  height: 24,
40
40
  allowResize: false,
41
+ virtualScroll: false,
41
42
  table: {
42
43
  use: false,
43
44
  columns: {