evui 3.3.36 → 3.3.39

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.
Files changed (141) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +40 -40
  3. package/dist/evui.common.js +1907 -1832
  4. package/dist/evui.common.js.map +1 -1
  5. package/dist/evui.umd.js +1907 -1832
  6. package/dist/evui.umd.js.map +1 -1
  7. package/dist/evui.umd.min.js +1 -1
  8. package/dist/evui.umd.min.js.map +1 -1
  9. package/dist/img/{EVUI.7f3588fb.svg → EVUI.b82ee81a.svg} +292 -292
  10. package/dist/img/{icon_mysql.7ea26d5d.svg → icon_mysql.1085fdc9.svg} +78 -78
  11. package/dist/img/{icon_oracle.9009b108.svg → icon_oracle.0572d3ee.svg} +13 -13
  12. package/dist/img/{icon_postgresql.f8fffba9.svg → icon_postgresql.ee12bde8.svg} +58 -58
  13. package/package.json +61 -61
  14. package/src/common/emitter.js +20 -20
  15. package/src/common/utils.debounce.js +223 -223
  16. package/src/common/utils.js +134 -134
  17. package/src/common/utils.table.js +78 -78
  18. package/src/common/utils.throttle.js +83 -83
  19. package/src/common/utils.tree.js +18 -18
  20. package/src/components/button/Button.vue +198 -198
  21. package/src/components/button/index.js +7 -7
  22. package/src/components/buttonGroup/ButtonGroup.vue +11 -11
  23. package/src/components/buttonGroup/index.js +7 -7
  24. package/src/components/calendar/Calendar.vue +661 -661
  25. package/src/components/calendar/index.js +7 -7
  26. package/src/components/calendar/uses.js +1272 -1272
  27. package/src/components/chart/Chart.vue +189 -192
  28. package/src/components/chart/chart.core.js +870 -870
  29. package/src/components/chart/element/element.bar.js +524 -524
  30. package/src/components/chart/element/element.bar.time.js +156 -156
  31. package/src/components/chart/element/element.heatmap.js +533 -533
  32. package/src/components/chart/element/element.line.js +339 -339
  33. package/src/components/chart/element/element.pie.js +197 -197
  34. package/src/components/chart/element/element.scatter.js +184 -184
  35. package/src/components/chart/element/element.tip.js +550 -542
  36. package/src/components/chart/helpers/helpers.canvas.js +265 -265
  37. package/src/components/chart/helpers/helpers.constant.js +206 -206
  38. package/src/components/chart/helpers/helpers.util.js +346 -338
  39. package/src/components/chart/index.js +9 -9
  40. package/src/components/chart/model/index.js +4 -4
  41. package/src/components/chart/model/model.series.js +93 -93
  42. package/src/components/chart/model/model.store.js +977 -967
  43. package/src/components/chart/plugins/plugins.interaction.js +769 -769
  44. package/src/components/chart/plugins/plugins.legend.gradient.js +602 -602
  45. package/src/components/chart/plugins/plugins.legend.js +1155 -1151
  46. package/src/components/chart/plugins/plugins.pie.js +254 -254
  47. package/src/components/chart/plugins/plugins.title.js +56 -56
  48. package/src/components/chart/plugins/plugins.tooltip.js +692 -692
  49. package/src/components/chart/scale/scale.js +848 -848
  50. package/src/components/chart/scale/scale.linear.js +38 -38
  51. package/src/components/chart/scale/scale.logarithmic.js +128 -128
  52. package/src/components/chart/scale/scale.step.js +336 -336
  53. package/src/components/chart/scale/scale.time.category.js +277 -277
  54. package/src/components/chart/scale/scale.time.js +48 -48
  55. package/src/components/chart/style/chart.scss +312 -312
  56. package/src/components/chart/uses.js +264 -252
  57. package/src/components/checkbox/Checkbox.vue +200 -200
  58. package/src/components/checkbox/index.js +7 -7
  59. package/src/components/checkboxGroup/CheckboxGroup.vue +44 -44
  60. package/src/components/checkboxGroup/index.js +7 -7
  61. package/src/components/contextMenu/ContextMenu.vue +80 -80
  62. package/src/components/contextMenu/MenuList.vue +149 -149
  63. package/src/components/contextMenu/index.js +7 -7
  64. package/src/components/contextMenu/uses.js +203 -203
  65. package/src/components/datePicker/DatePicker.vue +437 -437
  66. package/src/components/datePicker/index.js +7 -7
  67. package/src/components/datePicker/uses.js +419 -419
  68. package/src/components/grid/Grid.vue +827 -827
  69. package/src/components/grid/grid.filter.window.vue +493 -493
  70. package/src/components/grid/grid.pagination.vue +75 -75
  71. package/src/components/grid/grid.summary.vue +265 -265
  72. package/src/components/grid/grid.toolbar.vue +26 -26
  73. package/src/components/grid/index.js +11 -11
  74. package/src/components/grid/style/grid.scss +263 -263
  75. package/src/components/grid/uses.js +1002 -1007
  76. package/src/components/icon/Icon.vue +49 -49
  77. package/src/components/icon/index.js +8 -8
  78. package/src/components/inputNumber/InputNumber.vue +212 -212
  79. package/src/components/inputNumber/index.js +7 -7
  80. package/src/components/inputNumber/uses.js +217 -217
  81. package/src/components/loading/Loading.vue +125 -125
  82. package/src/components/loading/index.js +7 -7
  83. package/src/components/menu/Menu.vue +68 -68
  84. package/src/components/menu/MenuItem.vue +187 -187
  85. package/src/components/menu/index.js +7 -7
  86. package/src/components/message/Message.vue +223 -223
  87. package/src/components/message/index.js +31 -31
  88. package/src/components/messageBox/MessageBox.vue +358 -358
  89. package/src/components/messageBox/index.js +22 -22
  90. package/src/components/notification/Notification.vue +316 -316
  91. package/src/components/notification/index.js +49 -49
  92. package/src/components/pagination/Pagination.vue +271 -271
  93. package/src/components/pagination/index.js +7 -7
  94. package/src/components/pagination/pageButton.vue +30 -30
  95. package/src/components/progress/Progress.vue +139 -139
  96. package/src/components/progress/index.js +7 -7
  97. package/src/components/radio/Radio.vue +159 -159
  98. package/src/components/radio/index.js +7 -7
  99. package/src/components/radioGroup/RadioGroup.vue +41 -41
  100. package/src/components/radioGroup/index.js +7 -7
  101. package/src/components/scheduler/Scheduler.vue +149 -149
  102. package/src/components/scheduler/index.js +7 -7
  103. package/src/components/scheduler/uses.js +183 -183
  104. package/src/components/select/Select.vue +440 -440
  105. package/src/components/select/index.js +7 -7
  106. package/src/components/select/uses.js +270 -270
  107. package/src/components/slider/Slider.vue +505 -505
  108. package/src/components/slider/index.js +7 -7
  109. package/src/components/slider/uses.js +390 -390
  110. package/src/components/tabPanel/TabPanel.vue +74 -74
  111. package/src/components/tabPanel/index.js +7 -7
  112. package/src/components/tabs/Tabs.vue +517 -517
  113. package/src/components/tabs/index.js +7 -7
  114. package/src/components/textField/TextField.vue +375 -375
  115. package/src/components/textField/index.js +7 -7
  116. package/src/components/timePicker/TimePicker.vue +352 -352
  117. package/src/components/timePicker/index.js +7 -7
  118. package/src/components/toggle/Toggle.vue +115 -115
  119. package/src/components/toggle/index.js +7 -7
  120. package/src/components/tree/Tree.vue +313 -313
  121. package/src/components/tree/TreeNode.vue +293 -293
  122. package/src/components/tree/index.js +7 -7
  123. package/src/components/treeGrid/TreeGrid.vue +758 -758
  124. package/src/components/treeGrid/TreeGridNode.vue +275 -275
  125. package/src/components/treeGrid/index.js +9 -9
  126. package/src/components/treeGrid/style/treeGrid.scss +261 -261
  127. package/src/components/treeGrid/treeGrid.toolbar.vue +26 -26
  128. package/src/components/treeGrid/uses.js +867 -867
  129. package/src/components/window/Window.vue +329 -329
  130. package/src/components/window/index.js +7 -7
  131. package/src/components/window/uses.js +899 -899
  132. package/src/directives/clickoutside.js +90 -90
  133. package/src/main.js +116 -116
  134. package/src/style/components/input.scss +108 -108
  135. package/src/style/functions.scss +3 -3
  136. package/src/style/index.scss +6 -6
  137. package/src/style/lib/fonts/EVUI.svg +292 -292
  138. package/src/style/lib/icon.css +888 -888
  139. package/src/style/mixins.scss +94 -94
  140. package/src/style/themes.scss +67 -67
  141. package/src/style/variables.scss +22 -22
@@ -1,1151 +1,1155 @@
1
- import Util from '../helpers/helpers.util';
2
-
3
- const modules = {
4
- /**
5
- * Create legend DOM
6
- *
7
- * @returns {undefined}
8
- */
9
- createLegendLayout() {
10
- this.legendDOM = document.createElement('div');
11
- this.legendDOM.className = 'ev-chart-legend';
12
- this.legendBoxDOM = document.createElement('div');
13
- this.legendBoxDOM.className = 'ev-chart-legend-box';
14
-
15
- if (this.options?.legend?.allowResize) {
16
- this.resizeDOM = document.createElement('div');
17
- this.resizeDOM.className = 'ev-chart-resize-bar';
18
- this.ghostDOM = document.createElement('div');
19
- this.ghostDOM.className = 'ev-chart-resize-ghost';
20
- this.wrapperDOM.appendChild(this.resizeDOM);
21
- }
22
-
23
- if (this.useTable) {
24
- this.legendTableDOM = document.createElement('table');
25
- this.legendTableDOM.className = 'ev-chart-legend--table';
26
- this.setLegendColumnHeader();
27
- this.legendBoxDOM.appendChild(this.legendTableDOM);
28
- this.legendDOM.style.overflow = 'auto';
29
- } else {
30
- this.legendBoxDOM.style.overflowX = 'hidden';
31
- this.legendBoxDOM.style.overflowY = 'auto';
32
- }
33
-
34
- this.legendDOM.appendChild(this.legendBoxDOM);
35
- this.wrapperDOM.appendChild(this.legendDOM);
36
- },
37
-
38
- /**
39
- * Create and append Table Header DOM
40
- * Only chartOption > legend > table > use : true
41
- *
42
- * @returns {undefined}
43
- */
44
- setLegendColumnHeader() {
45
- const tableOpt = this.options.legend?.table;
46
- const columns = tableOpt.columns;
47
- const columnKeyList = ['color', ...Object.keys(columns)];
48
-
49
- columnKeyList.forEach((key) => {
50
- const columnNameDOM = document.createElement('th');
51
- columnNameDOM.className = 'ev-chart-legend--table__column-name';
52
-
53
- if (columns[key]?.use || key === 'color' || key === 'name') {
54
- const columnOpt = columns[key];
55
- const keyText = columnOpt?.title ?? '';
56
-
57
- columnNameDOM.textContent = keyText;
58
- columnNameDOM.setAttribute('title', keyText);
59
- columnNameDOM.dataset.type = keyText;
60
-
61
- Util.setDOMStyle(columnNameDOM, tableOpt?.style?.header);
62
-
63
- this.legendTableDOM.append(columnNameDOM);
64
- }
65
- });
66
- },
67
-
68
- /**
69
- * Initialize legend
70
- * If there was no initialization, create DOM and set default layout.
71
- * It not, there will already be set layout, so add a legend for each series with group
72
- *
73
- * @returns {undefined}
74
- */
75
- initLegend() {
76
- this.isHeatMapType = this.options.type === 'heatMap';
77
- this.useTable = !!this.options.legend?.table?.use && this.options.type !== 'heatmap' && this.options.type !== 'scatter';
78
-
79
- if (!this.isInitLegend) {
80
- this.createLegendLayout();
81
- }
82
-
83
- if (this.isHeatMapType) {
84
- this.initEventForColorLegend();
85
- this.addColorLegendList();
86
- } else {
87
- this.initEvent();
88
- this.addLegendList();
89
- }
90
-
91
- this.initResizeEvent();
92
-
93
- this.isInitLegend = true;
94
- this.isLegendMove = false;
95
- },
96
-
97
- /**
98
- * Add legend with group information to align each series properly.
99
- * Especially if a chart is stacked,
100
- * legends have to align with series ordering as we can see in chart.
101
- *
102
- * @returns {undefined}
103
- */
104
- addLegendList() {
105
- const groups = this.data.groups;
106
- const seriesList = this.seriesList;
107
-
108
- groups.forEach((group) => {
109
- group.slice().reverse().forEach((sId) => {
110
- const series = seriesList[sId];
111
-
112
- if (series && series.showLegend) {
113
- if (this.useTable) {
114
- this.addLegendWithValues(series);
115
- } else {
116
- this.addLegend(series);
117
- }
118
- }
119
- });
120
- });
121
-
122
- 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);
131
- }
132
- });
133
- },
134
-
135
- /**
136
- * Add Legend with Color Information
137
- * Only Heatmap chart
138
- *
139
- * @returns {undefined}
140
- */
141
- addColorLegendList() {
142
- const seriesList = this.seriesList;
143
-
144
- Object.values(seriesList).forEach((series) => {
145
- if (!series.isExistGrp && series.showLegend) {
146
- const { colorState, valueOpt } = series;
147
- const { min, max, interval, existError, decimalPoint } = valueOpt;
148
- const length = colorState.length;
149
- const endIndex = existError ? length - 2 : length - 1;
150
- for (let index = 0; index < length; index++) {
151
- const colorItem = colorState[index];
152
- const minValue = min + (interval * index);
153
- let maxValue = minValue + interval;
154
- if (index < endIndex) {
155
- maxValue -= (0.1 ** decimalPoint);
156
- } else {
157
- maxValue = max + (0.1 ** decimalPoint);
158
- }
159
-
160
- let name = `${minValue.toFixed(decimalPoint)} - ${maxValue.toFixed(decimalPoint)}`;
161
- if (min === undefined || max === undefined) {
162
- if (index === 0) {
163
- name = '0';
164
- } else {
165
- break;
166
- }
167
- } else if (existError && index === endIndex + 1) {
168
- name = 'error';
169
- } else if (minValue > max) {
170
- break;
171
- } else if (interval <= 1 && decimalPoint === 0) {
172
- name = minValue;
173
- }
174
-
175
- this.addLegend({
176
- cId: colorItem.id,
177
- color: colorItem.color,
178
- name,
179
- });
180
- }
181
- }
182
- });
183
- },
184
-
185
- /**
186
- * Get Container DOM by Event Object
187
- * @param e Event
188
- *
189
- * @returns {Element}
190
- */
191
- getContainerDOM(e) {
192
- let targetDOM = null;
193
- const type = e.target.dataset.type;
194
-
195
- const childTypes = ['name', 'color', 'min', 'max', 'avg', 'total', 'last'];
196
-
197
- if (type === 'container') {
198
- targetDOM = e.target;
199
- } else if (childTypes.includes(type)) {
200
- targetDOM = e.target.parentElement;
201
-
202
- if (!targetDOM?.series) {
203
- targetDOM = targetDOM.parentElement;
204
- }
205
- }
206
-
207
- return targetDOM;
208
- },
209
-
210
- /**
211
- * Initialize legend event
212
- *
213
- * @returns {undefined}
214
- */
215
- initEvent() {
216
- if (this.isInitLegend) {
217
- return;
218
- }
219
-
220
- const classList = {
221
- container: `ev-chart-legend${this.useTable ? '--table__container' : '-container'}`,
222
- color: `ev-chart-legend${this.useTable ? '--table__color' : '-color'}`,
223
- name: `ev-chart-legend${this.useTable ? '--table__name' : '-name'}`,
224
- value: `ev-chart-legend${this.useTable ? '--table__value' : '-value'}`,
225
- };
226
-
227
- /**
228
- * callback for legendBoxDOM to show/hide clicked series
229
- *
230
- * @returns {undefined}
231
- */
232
- this.onLegendBoxClick = (e) => {
233
- const opt = this.options.legend;
234
-
235
- const targetDOM = this.getContainerDOM(e);
236
- if (!targetDOM) {
237
- return;
238
- }
239
-
240
- const series = targetDOM?.series;
241
-
242
- const colorDOM = targetDOM?.getElementsByClassName(classList.color)[0];
243
- const nameDOM = targetDOM?.getElementsByClassName(classList.name)[0];
244
- const valueDOMList = targetDOM?.getElementsByClassName(classList.value);
245
-
246
- const isActive = !targetDOM?.className.includes('inactive');
247
- if (isActive && this.seriesInfo.count === 1) {
248
- return;
249
- }
250
-
251
- if (!colorDOM || !nameDOM) {
252
- return;
253
- }
254
-
255
- if (isActive) {
256
- this.seriesInfo.count--;
257
-
258
- const inactiveColor = opt.inactive;
259
- colorDOM.style.backgroundColor = inactiveColor;
260
- colorDOM.style.borderColor = inactiveColor;
261
- nameDOM.style.color = inactiveColor;
262
- valueDOMList?.forEach((dom) => {
263
- dom.style.color = inactiveColor;
264
- });
265
- } else {
266
- this.seriesInfo.count++;
267
-
268
- let seriesColor;
269
- if (typeof series.color !== 'string') {
270
- seriesColor = series.color[series.color.length - 1][1];
271
- } else {
272
- seriesColor = series.color;
273
- }
274
-
275
- if (series.type === 'line' && series.fill) {
276
- colorDOM.style.height = '8px';
277
- colorDOM.style.backgroundColor = `${seriesColor}80`;
278
- colorDOM.style.border = `1px solid ${seriesColor}`;
279
- } else {
280
- colorDOM.style.backgroundColor = seriesColor;
281
- }
282
-
283
- nameDOM.style.color = opt.color;
284
- valueDOMList?.forEach((dom) => {
285
- const style = opt.table?.columns[dom.dataset.type]?.style;
286
- dom.style.color = style?.color ? style.color : opt.color;
287
- });
288
- }
289
-
290
- series.show = !series.show;
291
- targetDOM.classList.toggle('inactive');
292
-
293
- this.update({
294
- updateSeries: false,
295
- updateSelTip: { update: true, keepDomain: true },
296
- });
297
- };
298
-
299
- /**
300
- * callback for legendBoxDOM hovering
301
- *
302
- * @returns {undefined}
303
- */
304
- this.onLegendBoxOver = (e) => {
305
- const targetDOM = this.getContainerDOM(e);
306
- if (!targetDOM) {
307
- return;
308
- }
309
-
310
- const targetId = targetDOM?.series?.sId;
311
- const legendHitInfo = { sId: targetId, type: this.options.type };
312
-
313
- this.update({
314
- updateSeries: false,
315
- updateSelTip: { update: false, keepDomain: false },
316
- hitInfo: {
317
- legend: legendHitInfo,
318
- },
319
- });
320
- };
321
-
322
- /**
323
- * callback for mouseleave event on legendBoxDOM
324
- *
325
- * @returns {undefined}
326
- */
327
- this.onLegendBoxLeave = () => {
328
- this.update({
329
- updateSeries: false,
330
- updateSelTip: { update: false, keepDomain: false },
331
- hitInfo: {
332
- legend: null,
333
- },
334
- });
335
- };
336
-
337
- this.legendBoxDOM.addEventListener('click', this.onLegendBoxClick);
338
- this.legendBoxDOM.addEventListener('mouseover', this.onLegendBoxOver);
339
- this.legendBoxDOM.addEventListener('mouseleave', this.onLegendBoxLeave);
340
-
341
- this.initResizeEvent();
342
- },
343
-
344
- /**
345
- * Init Event on Color Legend
346
- * Only Heatmap
347
- */
348
- initEventForColorLegend() {
349
- if (this.isInitLegend) {
350
- return;
351
- }
352
-
353
- /**
354
- * callback for legendBoxDOM to show/hide clicked series
355
- *
356
- * @returns {undefined}
357
- */
358
- this.onLegendBoxClick = (e) => {
359
- const opt = this.options.legend;
360
- const series = Object.values(this.seriesList)[0];
361
-
362
- const targetDOM = this.getContainerDOM(e);
363
- if (!targetDOM) {
364
- return;
365
- }
366
-
367
- const colorDOM = targetDOM?.getElementsByClassName('ev-chart-legend-color')[0];
368
- const nameDOM = targetDOM?.getElementsByClassName('ev-chart-legend-name')[0];
369
- const targetId = targetDOM?.series?.cId;
370
- const isActive = !colorDOM?.className.includes('inactive');
371
- const activeCount = series.colorState.filter(colorItem => colorItem.show).length;
372
-
373
- if (isActive && activeCount === 1) {
374
- return;
375
- }
376
-
377
- if (!colorDOM || !nameDOM) {
378
- return;
379
- }
380
-
381
- if (isActive) {
382
- colorDOM.style.backgroundColor = opt.inactive;
383
- colorDOM.style.borderColor = opt.inactive;
384
- nameDOM.style.color = opt.inactive;
385
- } else {
386
- colorDOM.style.backgroundColor = targetDOM?.series?.color;
387
- nameDOM.style.color = opt.color;
388
- }
389
-
390
- const targetIndex = series.colorState.findIndex(colorItem => colorItem.id === targetId);
391
- if (targetIndex > -1) {
392
- series.colorState[targetIndex].show = !isActive;
393
- }
394
-
395
- colorDOM.classList.toggle('inactive');
396
- nameDOM.classList.toggle('inactive');
397
-
398
- this.update({
399
- updateSeries: false,
400
- updateSelTip: { update: true, keepDomain: true },
401
- });
402
- };
403
-
404
- /**
405
- * callback for legendBoxDOM hovering
406
- *
407
- * @returns {undefined}
408
- */
409
- this.onLegendBoxOver = (e) => {
410
- const series = Object.values(this.seriesList)?.[0];
411
-
412
- const targetDOM = this.getContainerDOM(e);
413
- if (!targetDOM) {
414
- return;
415
- }
416
-
417
- const targetId = targetDOM?.series?.cId;
418
-
419
- series.colorState.forEach((colorItem) => {
420
- colorItem.state = colorItem.id === targetId ? 'highlight' : 'downplay';
421
- });
422
-
423
- this.update({
424
- updateSeries: false,
425
- updateSelTip: { update: false, keepDomain: false },
426
- });
427
- };
428
-
429
- /**
430
- * callback for mouseleave event on legendBoxDOM
431
- *
432
- * @returns {undefined}
433
- */
434
- this.onLegendBoxLeave = () => {
435
- const series = Object.values(this.seriesList)[0];
436
- series.colorState.forEach((item) => {
437
- item.state = 'normal';
438
- });
439
-
440
- this.update({
441
- updateSeries: false,
442
- updateSelTip: { update: false, keepDomain: false },
443
- });
444
- };
445
-
446
- this.legendBoxDOM.addEventListener('click', this.onLegendBoxClick);
447
- this.legendBoxDOM.addEventListener('mouseover', this.onLegendBoxOver);
448
- this.legendBoxDOM.addEventListener('mouseleave', this.onLegendBoxLeave);
449
-
450
- this.initResizeEvent();
451
- },
452
-
453
- initResizeEvent() {
454
- /**
455
- * callback for resizeDOM click event
456
- * 1. hide resizeDOM
457
- * 2. show ghost DOM on same position with hidden resizeDOM
458
- *
459
- * @returns {undefined}
460
- */
461
- this.onResizeMouseDown = (e) => {
462
- e.stopPropagation();
463
- e.preventDefault();
464
-
465
- const opt = this.options;
466
- const pos = opt.legend.position;
467
- const title = opt.title.show ? opt.title.height : 0;
468
-
469
- const ghostDOM = this.ghostDOM;
470
- this.resizeDOM.style.display = 'none';
471
- this.wrapperDOM.appendChild(ghostDOM);
472
-
473
- // mouse down 시, resizeDOM의 위치를 기반으로 ghostDOM의 위치를 세팅
474
- if (pos === 'left' || pos === 'right') {
475
- ghostDOM.style.top = `${title}px`;
476
- ghostDOM.style.left = this.resizeDOM.style.left;
477
- ghostDOM.style.right = this.resizeDOM.style.right;
478
- ghostDOM.style.height = this.resizeDOM.style.height;
479
- } else {
480
- ghostDOM.classList.add('horizontal');
481
-
482
- if (pos === 'top') {
483
- ghostDOM.style.top = this.resizeDOM.style.top;
484
- } else if (pos === 'bottom') {
485
- ghostDOM.style.bottom = this.resizeDOM.style.bottom;
486
- }
487
- }
488
-
489
- this.wrapperDOM.addEventListener('mousemove', this.mouseMove, false);
490
- this.wrapperDOM.addEventListener('mouseup', this.mouseUp, false);
491
- };
492
-
493
- if (this.resizeDOM) {
494
- this.resizeDOM.addEventListener('mousedown', this.onResizeMouseDown);
495
- this.mouseMove = this.onMouseMove.bind(this); // resizing function
496
- this.mouseUp = this.onMouseUp.bind(this); // resizing function
497
- }
498
- },
499
-
500
- /**
501
- * To update legend, reset all process.
502
- *
503
- * @returns {undefined}
504
- */
505
- updateLegend() {
506
- this.resetLegend();
507
-
508
- if (this.isHeatMapType) {
509
- this.addColorLegendList();
510
- } else {
511
- this.addLegendList();
512
- }
513
- },
514
-
515
- /**
516
- * To update value text on legend table
517
- * Only chartOption > legend > table > use : true
518
- *
519
- * @returns {undefined}
520
- */
521
- updateLegendTableValues() {
522
- const columns = this.options?.legend?.table?.columns;
523
- const aggregations = this.getAggregations();
524
- const rowDOMList = this.legendBoxDOM?.getElementsByClassName('ev-chart-legend--table__row');
525
-
526
- rowDOMList.forEach((row) => {
527
- const valueDOMList = row?.getElementsByClassName('ev-chart-legend--table__value');
528
-
529
- valueDOMList.forEach((dom) => {
530
- const key = dom.dataset.type;
531
- if (key === 'name') {
532
- return;
533
- }
534
-
535
- const seriesId = row.series.sId;
536
- const value = +aggregations?.[seriesId]?.[key];
537
- dom.textContent = this.getFormattedValue(columns[key], value);
538
- });
539
- });
540
- },
541
-
542
- /**
543
- * Force Update Legend. Remove and Create
544
- *
545
- * @returns {undefined}
546
- */
547
- forceUpdateLegend() {
548
- this.destroyLegend();
549
- this.initLegend();
550
- },
551
-
552
- /**
553
- * To update legend, remove all of legendBoxDOM's children
554
- *
555
- * @returns {undefined}
556
- */
557
- resetLegend() {
558
- const legendBoxDOM = this.legendBoxDOM;
559
-
560
- if (!legendBoxDOM) {
561
- return;
562
- }
563
-
564
- while (legendBoxDOM.hasChildNodes()) {
565
- legendBoxDOM.removeChild(legendBoxDOM.firstChild);
566
- }
567
-
568
- this.seriesInfo.count = 0;
569
- },
570
-
571
- /**
572
- * To update legend, remove all of legendBoxDOM's children
573
- *
574
- * @returns {undefined}
575
- */
576
- destroyLegend() {
577
- const legendDOM = this.legendDOM;
578
-
579
- if (!legendDOM) {
580
- return;
581
- }
582
-
583
- legendDOM.remove();
584
-
585
- this.legendDOM = null;
586
- this.legendBoxDOM = null;
587
- this.resizeDOM = null;
588
- this.isInitLegend = false;
589
- this.seriesInfo.count = 0;
590
- },
591
-
592
- /**
593
- * Create DOM for each series
594
- *
595
- * @returns {undefined}
596
- */
597
- addLegend(series) {
598
- const opt = this.options.legend;
599
- const containerDOM = document.createElement('div');
600
- const colorDOM = document.createElement('span');
601
- const nameDOM = document.createElement('div');
602
-
603
- containerDOM.className = `ev-chart-legend-container ${!series.show ? ' inactive' : ''}`;
604
- containerDOM.series = series;
605
-
606
- colorDOM.className = 'ev-chart-legend-color';
607
-
608
- if (series.type === 'line' && series.point && !series.fill) {
609
- colorDOM.className += ' ev-chart-legend-color--point-line';
610
- }
611
-
612
- nameDOM.className = 'ev-chart-legend-name';
613
-
614
- // set series color
615
- let seriesColor;
616
- if (!series.show) {
617
- seriesColor = opt.inactive;
618
- } else if (typeof series.color !== 'string') {
619
- seriesColor = series.color[series.color.length - 1][1];
620
- } else {
621
- seriesColor = series.color;
622
- }
623
-
624
- if (series.type === 'line' && series.fill) {
625
- colorDOM.style.height = '8px';
626
- colorDOM.style.backgroundColor = series.show ? `${seriesColor}80` : opt.inactive;
627
- colorDOM.style.border = `1px solid ${seriesColor}`;
628
- } else {
629
- colorDOM.style.backgroundColor = seriesColor;
630
- }
631
-
632
- colorDOM.dataset.type = 'color';
633
- nameDOM.style.color = opt.color;
634
- nameDOM.textContent = series.name;
635
- nameDOM.setAttribute('title', series.name);
636
- nameDOM.dataset.type = 'name';
637
-
638
- this.legendDOM.style.padding = '5px 0 0 0';
639
-
640
- containerDOM.appendChild(colorDOM);
641
- containerDOM.appendChild(nameDOM);
642
-
643
- if (opt.position === 'top' || opt.position === 'bottom') {
644
- containerDOM.style.width = `${opt.width - 8}px`;
645
- containerDOM.style.margin = '0 4px';
646
- } else {
647
- containerDOM.style.width = '100%';
648
- }
649
- containerDOM.style.height = '18px';
650
- containerDOM.style.display = 'inline-block';
651
- containerDOM.style.overflow = 'hidden';
652
- containerDOM.dataset.type = 'container';
653
-
654
- this.legendBoxDOM.appendChild(containerDOM);
655
- if (series.show) {
656
- this.seriesInfo.count++;
657
- }
658
- },
659
-
660
- /**
661
- * Add Legend Items With aggregation Values
662
- * Only chartOption > legend > table > use : true
663
- * @param series
664
- */
665
- addLegendWithValues(series) {
666
- const opt = this.options.legend;
667
- const columns = opt?.table?.columns;
668
-
669
- const aggregations = this.getAggregations()?.[series?.sId];
670
- if (!aggregations || !columns) {
671
- return;
672
- }
673
-
674
- // create row
675
- const rowDOM = document.createElement('tr');
676
- rowDOM.className = `ev-chart-legend--table__row ${!series.show ? ' inactive' : ''}`;
677
- Util.setDOMStyle(rowDOM, opt.table?.style?.row);
678
- rowDOM.series = series;
679
- rowDOM.dataset.type = 'container';
680
-
681
- // create td - color
682
- const colorWrapperDOM = document.createElement('td');
683
- colorWrapperDOM.className = 'ev-chart-legend--table__color-wrapper';
684
- colorWrapperDOM.dataset.type = 'color';
685
-
686
- const colorDOM = document.createElement('div');
687
- colorDOM.className = 'ev-chart-legend--table__color';
688
- colorDOM.dataset.type = 'color';
689
-
690
- // set series color
691
- let seriesColor;
692
- if (!series.show) {
693
- seriesColor = opt.inactive;
694
- } else if (typeof series.color !== 'string') {
695
- seriesColor = series.color[series.color.length - 1][1];
696
- } else {
697
- seriesColor = series.color;
698
- }
699
-
700
- switch (series.type) {
701
- case 'line': {
702
- if (series.fill) {
703
- colorDOM.style.backgroundColor = `${seriesColor}80`;
704
- colorDOM.style.border = `1px solid ${seriesColor}`;
705
- } else {
706
- if (series.point) {
707
- colorDOM.className += ' ev-chart-legend--table__color--point-line';
708
- }
709
-
710
- colorDOM.className += ' ev-chart-legend--table__color--line';
711
- colorDOM.style.backgroundColor = seriesColor;
712
- }
713
- break;
714
- }
715
-
716
- case 'bar':
717
- case 'pie':
718
- default: {
719
- colorDOM.style.height = '10px';
720
- colorDOM.style.backgroundColor = seriesColor;
721
- break;
722
- }
723
- }
724
-
725
- if (series.type === 'line' && series.fill) {
726
- colorDOM.style.height = '8px';
727
- colorDOM.style.backgroundColor = series.show ? `${seriesColor}80` : opt.inactive;
728
- colorDOM.style.border = `1px solid ${seriesColor}`;
729
- } else {
730
- colorDOM.style.backgroundColor = seriesColor;
731
- }
732
-
733
- colorWrapperDOM.appendChild(colorDOM);
734
- rowDOM.appendChild(colorWrapperDOM);
735
-
736
- // create td - name
737
- const nameDOM = document.createElement('td');
738
- nameDOM.className = 'ev-chart-legend--table__name';
739
- nameDOM.style.color = series.show ? opt.color : opt.inactive;
740
- nameDOM.textContent = series.name;
741
- nameDOM.setAttribute('title', series.name);
742
- nameDOM.dataset.type = 'name';
743
- Util.setDOMStyle(nameDOM, columns?.name?.style);
744
-
745
- if (!series.show) {
746
- nameDOM.style.color = opt.inactive;
747
- }
748
-
749
- rowDOM.appendChild(nameDOM);
750
-
751
- // create td - values
752
- const columnKeyList = Object.keys(columns);
753
- columnKeyList?.forEach((key) => {
754
- if (key === 'name') {
755
- return;
756
- }
757
-
758
- if (columns[key].use) {
759
- const formattedTxt = this.getFormattedValue(columns[key], +aggregations[key]);
760
- const valueDOM = document.createElement('td');
761
- valueDOM.className = 'ev-chart-legend--table__value';
762
- valueDOM.style.color = series.show ? opt.color : opt.inactive;
763
- valueDOM.textContent = formattedTxt;
764
- valueDOM.dataset.type = key.toString();
765
- Util.setDOMStyle(valueDOM, columns[key]?.style);
766
-
767
- if (!series.show) {
768
- valueDOM.style.color = opt.inactive;
769
- }
770
-
771
- rowDOM.appendChild(valueDOM);
772
- }
773
- });
774
-
775
- this.legendTableDOM.appendChild(rowDOM);
776
- if (series.show) {
777
- this.seriesInfo.count++;
778
- }
779
- },
780
-
781
- /**
782
- * Set legend components position by option
783
- *
784
- * @returns {undefined}
785
- */
786
- setLegendPosition() {
787
- const opt = this.options;
788
- const position = opt?.legend?.position;
789
- const wrapperStyle = this.wrapperDOM?.style;
790
- const legendStyle = this.legendDOM?.style;
791
- const boxStyle = this.legendBoxDOM?.style;
792
- const resizeStyle = this.resizeDOM?.style;
793
-
794
- let chartRect;
795
- let legendPad;
796
- const title = opt?.title?.show ? opt?.title?.height : 0;
797
- const positionTop = title + opt?.legend?.height;
798
- const { top = 0, bottom = 0, left = 0, right = 0 } = opt?.legend?.padding ?? {};
799
-
800
- if (!wrapperStyle || !legendStyle) {
801
- return;
802
- }
803
-
804
- boxStyle.padding = `${top}px ${right}px ${bottom}px ${left}px`;
805
-
806
- switch (position) {
807
- case 'top':
808
- wrapperStyle.padding = `${positionTop}px 0 0 0`;
809
- chartRect = this.chartDOM.getBoundingClientRect();
810
- legendPad = parseInt(legendStyle.paddingTop) + parseInt(legendStyle.paddingBottom);
811
-
812
- boxStyle.width = '100%';
813
- boxStyle.height = `${opt.legend.height - legendPad}px`;
814
-
815
- legendStyle.top = `${title}px`;
816
- legendStyle.right = '';
817
- legendStyle.bottom = '';
818
- legendStyle.left = '';
819
-
820
- legendStyle.width = `${chartRect.width}px`;
821
- legendStyle.height = `${opt.legend.height + (resizeStyle ? 4 : 0)}px`; // 4 resize bar size
822
-
823
- if (resizeStyle) {
824
- resizeStyle.top = `${positionTop}px`;
825
- resizeStyle.right = '';
826
- resizeStyle.bottom = '';
827
- resizeStyle.left = '';
828
-
829
- resizeStyle.width = `${chartRect.width}px`;
830
- resizeStyle.height = '4px';
831
- resizeStyle.cursor = 'row-resize';
832
- }
833
- break;
834
- case 'right':
835
- wrapperStyle.padding = `${title}px ${opt.legend.width}px 0 0`;
836
- chartRect = this.chartDOM.getBoundingClientRect();
837
-
838
- boxStyle.width = `${opt.legend.width - 10}px`; // legendDOM left padding
839
- boxStyle.maxHeight = `${chartRect.height}px`;
840
-
841
- legendStyle.paddingLeft = '10px';
842
- legendStyle.top = `${title}px`;
843
- legendStyle.right = '0px';
844
- legendStyle.bottom = '';
845
- legendStyle.left = '';
846
-
847
- legendStyle.width = `${opt.legend.width}px`;
848
- legendStyle.height = `${chartRect.height}px`;
849
-
850
- if (resizeStyle) {
851
- resizeStyle.top = `${title}px`;
852
- resizeStyle.right = `${opt.legend.width}px`;
853
- resizeStyle.bottom = '';
854
- resizeStyle.left = '';
855
-
856
- resizeStyle.width = '4px';
857
- resizeStyle.height = `${chartRect.height}px`;
858
- resizeStyle.cursor = 'col-resize';
859
- }
860
- break;
861
- case 'bottom':
862
- wrapperStyle.padding = `${title}px 0 ${opt.legend.height}px 0`;
863
- chartRect = this.chartDOM.getBoundingClientRect();
864
- legendPad = parseInt(legendStyle.paddingTop) + parseInt(legendStyle.paddingBottom);
865
-
866
- boxStyle.width = '100%';
867
- boxStyle.height = `${opt.legend.height - legendPad}px`;
868
-
869
- legendStyle.top = '';
870
- legendStyle.right = '';
871
- legendStyle.bottom = '0px';
872
- legendStyle.left = '0px';
873
-
874
- legendStyle.width = `${chartRect.width}px`;
875
- legendStyle.height = `${opt.legend.height + (resizeStyle ? 4 : 0)}px`; // 4 resize bar size
876
-
877
- if (resizeStyle) {
878
- resizeStyle.top = '';
879
- resizeStyle.right = '';
880
- resizeStyle.bottom = `${opt.legend.height}px`;
881
- resizeStyle.left = '';
882
-
883
- resizeStyle.width = `${chartRect.width}px`;
884
- resizeStyle.height = '4px';
885
- resizeStyle.cursor = 'row-resize';
886
- }
887
- break;
888
- case 'left':
889
- wrapperStyle.padding = `${title}px 0 0 ${opt.legend.width}px`;
890
- chartRect = this.chartDOM.getBoundingClientRect();
891
-
892
- boxStyle.width = `${opt.legend.width}px`;
893
- boxStyle.maxHeight = `${chartRect.height}px`;
894
- boxStyle.display = 'absolute';
895
- boxStyle.bottom = '0px';
896
-
897
- legendStyle.top = `${title}px`;
898
- legendStyle.right = '';
899
- legendStyle.bottom = '';
900
- legendStyle.left = '0px';
901
-
902
- legendStyle.width = `${opt.legend.width}px`;
903
- legendStyle.height = `${chartRect.height}px`;
904
-
905
- if (resizeStyle) {
906
- resizeStyle.top = `${title}px`;
907
- resizeStyle.right = '';
908
- resizeStyle.bottom = '';
909
- resizeStyle.left = `${opt.legend.width}px`;
910
-
911
- resizeStyle.width = '4px';
912
- resizeStyle.height = `${chartRect.height}px`;
913
- resizeStyle.cursor = 'col-resize';
914
- }
915
- break;
916
- default:
917
- break;
918
- }
919
- },
920
-
921
- /**
922
- * Update legend components size
923
- *
924
- * @returns {undefined}
925
- */
926
- updateLegendContainerSize() {
927
- if (!this.options || !this.legendBoxDOM) {
928
- return;
929
- }
930
-
931
- const opt = this.options?.legend;
932
- const container = this.legendBoxDOM.getElementsByClassName('ev-chart-legend-container');
933
-
934
- if (!container) {
935
- return;
936
- }
937
-
938
- for (let ix = 0; ix < container.length; ix++) {
939
- if (opt.position === 'top' || opt.position === 'bottom') {
940
- container[ix].style.width = `${opt.width - 8}px`;
941
- container[ix].style.margin = '0 4px';
942
- } else {
943
- container[ix].style.width = '100%';
944
- }
945
- }
946
- },
947
-
948
- /**
949
- * When user moves resizeDOM, this function will change css
950
- *
951
- * @returns {undefined}
952
- */
953
- onMouseMove(e) {
954
- e.stopPropagation();
955
- e.preventDefault();
956
-
957
- const offset = this.wrapperDOM.getBoundingClientRect();
958
- const offsetWidth = this.wrapperDOM.offsetWidth;
959
- const offsetHeight = this.wrapperDOM.offsetHeight;
960
-
961
- const titleHeight = this.options.title.show ? this.options.title.height : 0;
962
- const position = this.options.legend.position;
963
-
964
- const chartMinWidth = 150;
965
- const chartMinHeight = 70;
966
-
967
- const legendMinWidth = 120;
968
- const legendMinHeight = 20;
969
-
970
- let move;
971
-
972
- switch (position) {
973
- case 'left':
974
- move = e.clientX - offset.left;
975
- if (move < legendMinWidth) {
976
- move = legendMinWidth;
977
- } else if (move > offsetWidth - chartMinWidth) {
978
- move = offsetWidth - chartMinWidth;
979
- }
980
- this.ghostDOM.style.left = `${move}px`;
981
- break;
982
- case 'right':
983
- move = e.clientX - offset.left;
984
- if (move < chartMinWidth) {
985
- move = chartMinWidth;
986
- } else if (move > offsetWidth - legendMinWidth) {
987
- move = offsetWidth - legendMinWidth;
988
- }
989
- this.ghostDOM.style.left = `${move}px`;
990
- break;
991
- case 'top':
992
- move = e.clientY - offset.top;
993
- if (move < legendMinHeight + titleHeight) {
994
- move = legendMinHeight + titleHeight;
995
- } else if (move > offsetHeight - chartMinHeight) {
996
- move = offsetHeight - chartMinHeight;
997
- }
998
- this.ghostDOM.style.top = `${move}px`;
999
- break;
1000
- case 'bottom':
1001
- move = e.clientY - offset.top;
1002
- if (move < chartMinHeight + titleHeight) {
1003
- move = chartMinHeight + titleHeight;
1004
- } else if (move > offsetHeight - legendMinHeight) {
1005
- move = offsetHeight - legendMinHeight;
1006
- }
1007
- this.ghostDOM.style.bottom = `${this.wrapperDOM.offsetHeight - move}px`;
1008
- break;
1009
- default:
1010
- break;
1011
- }
1012
-
1013
- this.isLegendMove = true;
1014
- },
1015
-
1016
- /**
1017
- * callback for mouseup on ghostDOM, this function will change legend and chart size.
1018
- *
1019
- * @returns {undefined}
1020
- */
1021
- onMouseUp(e) {
1022
- e.stopPropagation();
1023
- e.preventDefault();
1024
-
1025
- this.wrapperDOM.removeEventListener('mousemove', this.mouseMove, false);
1026
- this.wrapperDOM.removeEventListener('mouseup', this.mouseUp, false);
1027
-
1028
- const opt = this.options;
1029
- const pos = opt.legend.position;
1030
- const resizeDOMStyle = this.resizeDOM.style;
1031
- const legendDOMStyle = this.legendDOM.style;
1032
- const boxDOMStyle = this.legendBoxDOM.style;
1033
- const ghostDOMStyle = this.ghostDOM.style;
1034
- const wrapperDOMStyle = this.wrapperDOM.style;
1035
-
1036
- const title = opt.title.show ? opt.title.height : 0;
1037
- const padding = +this.legendDOM.style.paddingLeft.replace('px', '');
1038
- let move;
1039
- if (this.isLegendMove) {
1040
- switch (pos) {
1041
- case 'top':
1042
- resizeDOMStyle.top = ghostDOMStyle.top;
1043
- move = +ghostDOMStyle.top.replace('px', '');
1044
- legendDOMStyle.height = `${move - title}px`;
1045
- boxDOMStyle.height = `${move - title - 4}px`;
1046
- opt.legend.height = move - title - 4;
1047
- wrapperDOMStyle.padding = `${move}px 0 0 0`;
1048
- break;
1049
- case 'right':
1050
- resizeDOMStyle.left = ghostDOMStyle.left;
1051
- move = +ghostDOMStyle.left.replace('px', '');
1052
- legendDOMStyle.width = `${(this.wrapperDOM.offsetWidth - move - 4)}px`;
1053
- boxDOMStyle.width = `${(this.wrapperDOM.offsetWidth - move - 4 - padding)}px`;
1054
- opt.legend.width = this.wrapperDOM.offsetWidth - move - 4;
1055
- wrapperDOMStyle.padding = `${title}px ${this.wrapperDOM.offsetWidth - move}px 0 0`;
1056
- break;
1057
- case 'bottom':
1058
- resizeDOMStyle.bottom = ghostDOMStyle.bottom;
1059
- move = this.wrapperDOM.offsetHeight - (+ghostDOMStyle.bottom.replace('px', ''));
1060
- legendDOMStyle.height = `${this.wrapperDOM.offsetHeight - move}px`;
1061
- boxDOMStyle.height = `${move - title - 4}px`;
1062
- opt.legend.height = this.wrapperDOM.offsetHeight - move;
1063
- wrapperDOMStyle.padding = `${title}px 0 ${this.wrapperDOM.offsetHeight - move}px 0`;
1064
- break;
1065
- case 'left':
1066
- resizeDOMStyle.left = ghostDOMStyle.left;
1067
- move = +ghostDOMStyle.left.replace('px', '');
1068
- legendDOMStyle.width = `${move}px`;
1069
- boxDOMStyle.width = `${move}px`;
1070
- opt.legend.width = move;
1071
- wrapperDOMStyle.padding = `${title}px 0 0 ${move - 4}px`;
1072
- break;
1073
- default:
1074
- break;
1075
- }
1076
- }
1077
-
1078
- resizeDOMStyle.display = 'block';
1079
- this.ghostDOM.remove();
1080
-
1081
- if (this.isLegendMove) {
1082
- this.render();
1083
- this.isLegendMove = false;
1084
- }
1085
- },
1086
-
1087
- /**
1088
- * Show legend components by manipulating css
1089
- *
1090
- * @returns {undefined}
1091
- */
1092
- showLegend() {
1093
- if (this.resizeDOM) {
1094
- this.resizeDOM.style.display = 'block';
1095
- }
1096
-
1097
- if (this.legendDOM) {
1098
- this.legendDOM.style.display = 'block';
1099
- }
1100
- },
1101
-
1102
- /**
1103
- * Hide legend components by manipulating css
1104
- *
1105
- * @returns {undefined}
1106
- */
1107
- hideLegend() {
1108
- const opt = this.options;
1109
- const wrapperStyle = this.wrapperDOM?.style;
1110
- const resizeStyle = this.resizeDOM?.style;
1111
- const legendStyle = this.legendDOM?.style;
1112
- const title = opt?.title?.show ? opt?.title?.height : 0;
1113
-
1114
- if (!legendStyle || !wrapperStyle) {
1115
- return;
1116
- }
1117
-
1118
- if (resizeStyle) {
1119
- resizeStyle.display = 'none';
1120
- }
1121
-
1122
- legendStyle.display = 'none';
1123
- legendStyle.width = '0';
1124
- legendStyle.height = '0';
1125
- wrapperStyle.padding = `${title}px 0 0 0`;
1126
- },
1127
-
1128
- /**
1129
- * Get formatted value by formatter function
1130
- * Only chartOption > legend > table > use : true
1131
- * @param formatter
1132
- * @param decimalPoint
1133
- * @param value
1134
- * @returns {string}
1135
- */
1136
- getFormattedValue({ formatter, decimalPoint }, value) {
1137
- let formattedTxt;
1138
- if (formatter) {
1139
- formattedTxt = formatter(value);
1140
- }
1141
-
1142
- if (!formatter || typeof formattedTxt !== 'string') {
1143
- formattedTxt = Util.labelSignFormat(value, decimalPoint);
1144
- }
1145
-
1146
- return formattedTxt;
1147
- },
1148
-
1149
- };
1150
-
1151
- export default modules;
1
+ import Util from '../helpers/helpers.util';
2
+
3
+ const modules = {
4
+ /**
5
+ * Create legend DOM
6
+ *
7
+ * @returns {undefined}
8
+ */
9
+ createLegendLayout() {
10
+ this.legendDOM = document.createElement('div');
11
+ this.legendDOM.className = 'ev-chart-legend';
12
+ this.legendBoxDOM = document.createElement('div');
13
+ this.legendBoxDOM.className = 'ev-chart-legend-box';
14
+
15
+ if (this.options?.legend?.allowResize) {
16
+ this.resizeDOM = document.createElement('div');
17
+ this.resizeDOM.className = 'ev-chart-resize-bar';
18
+ this.ghostDOM = document.createElement('div');
19
+ this.ghostDOM.className = 'ev-chart-resize-ghost';
20
+ this.wrapperDOM.appendChild(this.resizeDOM);
21
+ }
22
+
23
+ if (this.useTable) {
24
+ this.legendTableDOM = document.createElement('table');
25
+ this.legendTableDOM.className = 'ev-chart-legend--table';
26
+ this.setLegendColumnHeader();
27
+ this.legendBoxDOM.appendChild(this.legendTableDOM);
28
+ this.legendDOM.style.overflow = 'auto';
29
+ } else {
30
+ this.legendBoxDOM.style.overflowX = 'hidden';
31
+ this.legendBoxDOM.style.overflowY = 'auto';
32
+ }
33
+
34
+ this.legendDOM.appendChild(this.legendBoxDOM);
35
+ this.wrapperDOM.appendChild(this.legendDOM);
36
+ },
37
+
38
+ /**
39
+ * Create and append Table Header DOM
40
+ * Only chartOption > legend > table > use : true
41
+ *
42
+ * @returns {undefined}
43
+ */
44
+ setLegendColumnHeader() {
45
+ const tableOpt = this.options.legend?.table;
46
+ const columns = tableOpt.columns;
47
+ const columnKeyList = ['color', ...Object.keys(columns)];
48
+
49
+ columnKeyList.forEach((key) => {
50
+ const columnNameDOM = document.createElement('th');
51
+ columnNameDOM.className = 'ev-chart-legend--table__column-name';
52
+
53
+ if (columns[key]?.use || key === 'color' || key === 'name') {
54
+ const columnOpt = columns[key];
55
+ const keyText = columnOpt?.title ?? '';
56
+
57
+ columnNameDOM.textContent = keyText;
58
+ columnNameDOM.setAttribute('title', keyText);
59
+ columnNameDOM.dataset.type = keyText;
60
+
61
+ Util.setDOMStyle(columnNameDOM, tableOpt?.style?.header);
62
+
63
+ this.legendTableDOM.append(columnNameDOM);
64
+ }
65
+ });
66
+ },
67
+
68
+ /**
69
+ * Initialize legend
70
+ * If there was no initialization, create DOM and set default layout.
71
+ * It not, there will already be set layout, so add a legend for each series with group
72
+ *
73
+ * @returns {undefined}
74
+ */
75
+ initLegend() {
76
+ this.isHeatMapType = this.options.type === 'heatMap';
77
+ this.useTable = !!this.options.legend?.table?.use && this.options.type !== 'heatmap' && this.options.type !== 'scatter';
78
+
79
+ if (!this.isInitLegend) {
80
+ this.createLegendLayout();
81
+ }
82
+
83
+ if (this.isHeatMapType) {
84
+ this.initEventForColorLegend();
85
+ this.addColorLegendList();
86
+ } else {
87
+ this.initEvent();
88
+ this.addLegendList();
89
+ }
90
+
91
+ this.initResizeEvent();
92
+
93
+ this.isInitLegend = true;
94
+ this.isLegendMove = false;
95
+ },
96
+
97
+ /**
98
+ * Add legend with group information to align each series properly.
99
+ * Especially if a chart is stacked,
100
+ * legends have to align with series ordering as we can see in chart.
101
+ *
102
+ * @returns {undefined}
103
+ */
104
+ addLegendList() {
105
+ const groups = this.data.groups;
106
+ const seriesList = this.seriesList;
107
+
108
+ groups.forEach((group) => {
109
+ group.slice().reverse().forEach((sId) => {
110
+ const series = seriesList[sId];
111
+
112
+ if (series && series.showLegend) {
113
+ if (this.useTable) {
114
+ this.addLegendWithValues(series);
115
+ } else {
116
+ this.addLegend(series);
117
+ }
118
+ }
119
+ });
120
+ });
121
+
122
+ 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);
131
+ }
132
+ });
133
+ },
134
+
135
+ /**
136
+ * Add Legend with Color Information
137
+ * Only Heatmap chart
138
+ *
139
+ * @returns {undefined}
140
+ */
141
+ addColorLegendList() {
142
+ const seriesList = this.seriesList;
143
+
144
+ Object.values(seriesList).forEach((series) => {
145
+ if (!series.isExistGrp && series.showLegend) {
146
+ const { colorState, valueOpt } = series;
147
+ const { min, max, interval, existError, decimalPoint } = valueOpt;
148
+ const length = colorState.length;
149
+ const endIndex = existError ? length - 2 : length - 1;
150
+ for (let index = 0; index < length; index++) {
151
+ const colorItem = colorState[index];
152
+ const minValue = min + (interval * index);
153
+ let maxValue = minValue + interval;
154
+ if (index < endIndex) {
155
+ maxValue -= (0.1 ** decimalPoint);
156
+ } else {
157
+ maxValue = max + (0.1 ** decimalPoint);
158
+ }
159
+
160
+ let name = `${minValue.toFixed(decimalPoint)} - ${maxValue.toFixed(decimalPoint)}`;
161
+ if (min === undefined || max === undefined) {
162
+ if (index === 0) {
163
+ name = '0';
164
+ } else {
165
+ break;
166
+ }
167
+ } else if (existError && index === endIndex + 1) {
168
+ name = 'error';
169
+ } else if (minValue > max) {
170
+ break;
171
+ } else if (interval <= 1 && decimalPoint === 0) {
172
+ name = minValue;
173
+ }
174
+
175
+ this.addLegend({
176
+ cId: colorItem.id,
177
+ color: colorItem.color,
178
+ name,
179
+ });
180
+ }
181
+ }
182
+ });
183
+ },
184
+
185
+ /**
186
+ * Get Container DOM by Event Object
187
+ * @param e Event
188
+ *
189
+ * @returns {Element}
190
+ */
191
+ getContainerDOM(e) {
192
+ let targetDOM = null;
193
+ const type = e.target.dataset.type;
194
+
195
+ const childTypes = ['name', 'color', 'min', 'max', 'avg', 'total', 'last'];
196
+
197
+ if (type === 'container') {
198
+ targetDOM = e.target;
199
+ } else if (childTypes.includes(type)) {
200
+ targetDOM = e.target.parentElement;
201
+
202
+ if (!targetDOM?.series) {
203
+ targetDOM = targetDOM.parentElement;
204
+ }
205
+ }
206
+
207
+ return targetDOM;
208
+ },
209
+
210
+ /**
211
+ * Initialize legend event
212
+ *
213
+ * @returns {undefined}
214
+ */
215
+ initEvent() {
216
+ if (this.isInitLegend) {
217
+ return;
218
+ }
219
+
220
+ const classList = {
221
+ container: `ev-chart-legend${this.useTable ? '--table__container' : '-container'}`,
222
+ color: `ev-chart-legend${this.useTable ? '--table__color' : '-color'}`,
223
+ name: `ev-chart-legend${this.useTable ? '--table__name' : '-name'}`,
224
+ value: `ev-chart-legend${this.useTable ? '--table__value' : '-value'}`,
225
+ };
226
+
227
+ /**
228
+ * callback for legendBoxDOM to show/hide clicked series
229
+ *
230
+ * @returns {undefined}
231
+ */
232
+ this.onLegendBoxClick = (e) => {
233
+ const opt = this.options.legend;
234
+
235
+ const targetDOM = this.getContainerDOM(e);
236
+ if (!targetDOM) {
237
+ return;
238
+ }
239
+
240
+ const series = targetDOM?.series;
241
+
242
+ const colorDOM = targetDOM?.getElementsByClassName(classList.color)[0];
243
+ const nameDOM = targetDOM?.getElementsByClassName(classList.name)[0];
244
+ const valueDOMList = targetDOM?.getElementsByClassName(classList.value);
245
+
246
+ const isActive = !targetDOM?.className.includes('inactive');
247
+ if (isActive && this.seriesInfo.count === 1) {
248
+ return;
249
+ }
250
+
251
+ if (!colorDOM || !nameDOM) {
252
+ return;
253
+ }
254
+
255
+ if (isActive) {
256
+ this.seriesInfo.count--;
257
+
258
+ const inactiveColor = opt.inactive;
259
+ colorDOM.style.backgroundColor = inactiveColor;
260
+ colorDOM.style.borderColor = inactiveColor;
261
+ nameDOM.style.color = inactiveColor;
262
+ valueDOMList?.forEach((dom) => {
263
+ dom.style.color = inactiveColor;
264
+ });
265
+ } else {
266
+ this.seriesInfo.count++;
267
+
268
+ let seriesColor;
269
+ if (typeof series.color !== 'string') {
270
+ seriesColor = series.color[series.color.length - 1][1];
271
+ } else {
272
+ seriesColor = series.color;
273
+ }
274
+
275
+ if (series.type === 'line' && series.fill) {
276
+ colorDOM.style.height = '8px';
277
+ colorDOM.style.backgroundColor = `${seriesColor}80`;
278
+ colorDOM.style.border = `1px solid ${seriesColor}`;
279
+ } else {
280
+ colorDOM.style.backgroundColor = seriesColor;
281
+ }
282
+
283
+ nameDOM.style.color = opt.color;
284
+ valueDOMList?.forEach((dom) => {
285
+ const style = opt.table?.columns[dom.dataset.type]?.style;
286
+ dom.style.color = style?.color ? style.color : opt.color;
287
+ });
288
+ }
289
+
290
+ series.show = !series.show;
291
+ targetDOM.classList.toggle('inactive');
292
+
293
+ this.update({
294
+ updateSeries: false,
295
+ updateSelTip: { update: true, keepDomain: true },
296
+ });
297
+ };
298
+
299
+ /**
300
+ * callback for legendBoxDOM hovering
301
+ *
302
+ * @returns {undefined}
303
+ */
304
+ this.onLegendBoxOver = (e) => {
305
+ const targetDOM = this.getContainerDOM(e);
306
+ if (!targetDOM) {
307
+ return;
308
+ }
309
+
310
+ const targetId = targetDOM?.series?.sId;
311
+ const legendHitInfo = { sId: targetId, type: this.options.type };
312
+
313
+ this.update({
314
+ updateSeries: false,
315
+ updateSelTip: { update: false, keepDomain: false },
316
+ hitInfo: {
317
+ legend: legendHitInfo,
318
+ },
319
+ });
320
+ };
321
+
322
+ /**
323
+ * callback for mouseleave event on legendBoxDOM
324
+ *
325
+ * @returns {undefined}
326
+ */
327
+ this.onLegendBoxLeave = () => {
328
+ this.update({
329
+ updateSeries: false,
330
+ updateSelTip: { update: false, keepDomain: false },
331
+ hitInfo: {
332
+ legend: null,
333
+ },
334
+ });
335
+ };
336
+
337
+ this.legendBoxDOM.addEventListener('click', this.onLegendBoxClick);
338
+ this.legendBoxDOM.addEventListener('mouseover', this.onLegendBoxOver);
339
+ this.legendBoxDOM.addEventListener('mouseleave', this.onLegendBoxLeave);
340
+
341
+ this.initResizeEvent();
342
+ },
343
+
344
+ /**
345
+ * Init Event on Color Legend
346
+ * Only Heatmap
347
+ */
348
+ initEventForColorLegend() {
349
+ if (this.isInitLegend) {
350
+ return;
351
+ }
352
+
353
+ /**
354
+ * callback for legendBoxDOM to show/hide clicked series
355
+ *
356
+ * @returns {undefined}
357
+ */
358
+ this.onLegendBoxClick = (e) => {
359
+ const opt = this.options.legend;
360
+ const series = Object.values(this.seriesList)[0];
361
+
362
+ const targetDOM = this.getContainerDOM(e);
363
+ if (!targetDOM) {
364
+ return;
365
+ }
366
+
367
+ const colorDOM = targetDOM?.getElementsByClassName('ev-chart-legend-color')[0];
368
+ const nameDOM = targetDOM?.getElementsByClassName('ev-chart-legend-name')[0];
369
+ const targetId = targetDOM?.series?.cId;
370
+ const isActive = !colorDOM?.className.includes('inactive');
371
+ const activeCount = series.colorState.filter(colorItem => colorItem.show).length;
372
+
373
+ if (isActive && activeCount === 1) {
374
+ return;
375
+ }
376
+
377
+ if (!colorDOM || !nameDOM) {
378
+ return;
379
+ }
380
+
381
+ if (isActive) {
382
+ colorDOM.style.backgroundColor = opt.inactive;
383
+ colorDOM.style.borderColor = opt.inactive;
384
+ nameDOM.style.color = opt.inactive;
385
+ } else {
386
+ colorDOM.style.backgroundColor = targetDOM?.series?.color;
387
+ nameDOM.style.color = opt.color;
388
+ }
389
+
390
+ const targetIndex = series.colorState.findIndex(colorItem => colorItem.id === targetId);
391
+ if (targetIndex > -1) {
392
+ series.colorState[targetIndex].show = !isActive;
393
+ }
394
+
395
+ colorDOM.classList.toggle('inactive');
396
+ nameDOM.classList.toggle('inactive');
397
+
398
+ this.update({
399
+ updateSeries: false,
400
+ updateSelTip: { update: true, keepDomain: true },
401
+ });
402
+ };
403
+
404
+ /**
405
+ * callback for legendBoxDOM hovering
406
+ *
407
+ * @returns {undefined}
408
+ */
409
+ this.onLegendBoxOver = (e) => {
410
+ const series = Object.values(this.seriesList)?.[0];
411
+
412
+ const targetDOM = this.getContainerDOM(e);
413
+ if (!targetDOM) {
414
+ return;
415
+ }
416
+
417
+ const targetId = targetDOM?.series?.cId;
418
+
419
+ series.colorState.forEach((colorItem) => {
420
+ colorItem.state = colorItem.id === targetId ? 'highlight' : 'downplay';
421
+ });
422
+
423
+ this.update({
424
+ updateSeries: false,
425
+ updateSelTip: { update: false, keepDomain: false },
426
+ });
427
+ };
428
+
429
+ /**
430
+ * callback for mouseleave event on legendBoxDOM
431
+ *
432
+ * @returns {undefined}
433
+ */
434
+ this.onLegendBoxLeave = () => {
435
+ const series = Object.values(this.seriesList)[0];
436
+ series.colorState.forEach((item) => {
437
+ item.state = 'normal';
438
+ });
439
+
440
+ this.update({
441
+ updateSeries: false,
442
+ updateSelTip: { update: false, keepDomain: false },
443
+ });
444
+ };
445
+
446
+ this.legendBoxDOM.addEventListener('click', this.onLegendBoxClick);
447
+ this.legendBoxDOM.addEventListener('mouseover', this.onLegendBoxOver);
448
+ this.legendBoxDOM.addEventListener('mouseleave', this.onLegendBoxLeave);
449
+
450
+ this.initResizeEvent();
451
+ },
452
+
453
+ initResizeEvent() {
454
+ /**
455
+ * callback for resizeDOM click event
456
+ * 1. hide resizeDOM
457
+ * 2. show ghost DOM on same position with hidden resizeDOM
458
+ *
459
+ * @returns {undefined}
460
+ */
461
+ this.onResizeMouseDown = (e) => {
462
+ e.stopPropagation();
463
+ e.preventDefault();
464
+
465
+ const opt = this.options;
466
+ const pos = opt.legend.position;
467
+ const title = opt.title.show ? opt.title.height : 0;
468
+
469
+ const ghostDOM = this.ghostDOM;
470
+ this.resizeDOM.style.display = 'none';
471
+ this.wrapperDOM.appendChild(ghostDOM);
472
+
473
+ // mouse down 시, resizeDOM의 위치를 기반으로 ghostDOM의 위치를 세팅
474
+ if (pos === 'left' || pos === 'right') {
475
+ ghostDOM.style.top = `${title}px`;
476
+ ghostDOM.style.left = this.resizeDOM.style.left;
477
+ ghostDOM.style.right = this.resizeDOM.style.right;
478
+ ghostDOM.style.height = this.resizeDOM.style.height;
479
+ } else {
480
+ ghostDOM.classList.add('horizontal');
481
+
482
+ if (pos === 'top') {
483
+ ghostDOM.style.top = this.resizeDOM.style.top;
484
+ } else if (pos === 'bottom') {
485
+ ghostDOM.style.bottom = this.resizeDOM.style.bottom;
486
+ }
487
+ }
488
+
489
+ this.wrapperDOM.addEventListener('mousemove', this.mouseMove, false);
490
+ this.wrapperDOM.addEventListener('mouseup', this.mouseUp, false);
491
+ };
492
+
493
+ if (this.resizeDOM) {
494
+ this.resizeDOM.addEventListener('mousedown', this.onResizeMouseDown);
495
+ this.mouseMove = this.onMouseMove.bind(this); // resizing function
496
+ this.mouseUp = this.onMouseUp.bind(this); // resizing function
497
+ }
498
+ },
499
+
500
+ /**
501
+ * To update legend, reset all process.
502
+ *
503
+ * @returns {undefined}
504
+ */
505
+ updateLegend() {
506
+ this.resetLegend();
507
+
508
+ if (this.isHeatMapType) {
509
+ this.addColorLegendList();
510
+ } else {
511
+ this.addLegendList();
512
+ }
513
+ },
514
+
515
+ /**
516
+ * To update value text on legend table
517
+ * Only chartOption > legend > table > use : true
518
+ *
519
+ * @returns {undefined}
520
+ */
521
+ updateLegendTableValues() {
522
+ const columns = this.options?.legend?.table?.columns;
523
+ const aggregations = this.getAggregations();
524
+ const rowDOMList = this.legendBoxDOM?.getElementsByClassName('ev-chart-legend--table__row');
525
+
526
+ rowDOMList.forEach((row) => {
527
+ const valueDOMList = row?.getElementsByClassName('ev-chart-legend--table__value');
528
+
529
+ valueDOMList.forEach((dom) => {
530
+ const key = dom.dataset.type;
531
+ if (key === 'name') {
532
+ return;
533
+ }
534
+
535
+ const seriesId = row.series.sId;
536
+ const value = aggregations?.[seriesId]?.[key];
537
+ dom.textContent = this.getFormattedValue(columns[key], value);
538
+ });
539
+ });
540
+ },
541
+
542
+ /**
543
+ * Force Update Legend. Remove and Create
544
+ *
545
+ * @returns {undefined}
546
+ */
547
+ forceUpdateLegend() {
548
+ this.destroyLegend();
549
+ this.initLegend();
550
+ },
551
+
552
+ /**
553
+ * To update legend, remove all of legendBoxDOM's children
554
+ *
555
+ * @returns {undefined}
556
+ */
557
+ resetLegend() {
558
+ const legendBoxDOM = this.legendBoxDOM;
559
+
560
+ if (!legendBoxDOM) {
561
+ return;
562
+ }
563
+
564
+ while (legendBoxDOM.hasChildNodes()) {
565
+ legendBoxDOM.removeChild(legendBoxDOM.firstChild);
566
+ }
567
+
568
+ this.seriesInfo.count = 0;
569
+ },
570
+
571
+ /**
572
+ * To update legend, remove all of legendBoxDOM's children
573
+ *
574
+ * @returns {undefined}
575
+ */
576
+ destroyLegend() {
577
+ const legendDOM = this.legendDOM;
578
+
579
+ if (!legendDOM) {
580
+ return;
581
+ }
582
+
583
+ legendDOM.remove();
584
+
585
+ this.legendDOM = null;
586
+ this.legendBoxDOM = null;
587
+ this.resizeDOM = null;
588
+ this.isInitLegend = false;
589
+ this.seriesInfo.count = 0;
590
+ },
591
+
592
+ /**
593
+ * Create DOM for each series
594
+ *
595
+ * @returns {undefined}
596
+ */
597
+ addLegend(series) {
598
+ const opt = this.options.legend;
599
+ const containerDOM = document.createElement('div');
600
+ const colorDOM = document.createElement('span');
601
+ const nameDOM = document.createElement('div');
602
+
603
+ containerDOM.className = `ev-chart-legend-container ${!series.show ? ' inactive' : ''}`;
604
+ containerDOM.series = series;
605
+
606
+ colorDOM.className = 'ev-chart-legend-color';
607
+
608
+ if (series.type === 'line' && series.point && !series.fill) {
609
+ colorDOM.className += ' ev-chart-legend-color--point-line';
610
+ }
611
+
612
+ nameDOM.className = 'ev-chart-legend-name';
613
+
614
+ // set series color
615
+ let seriesColor;
616
+ if (!series.show) {
617
+ seriesColor = opt.inactive;
618
+ } else if (typeof series.color !== 'string') {
619
+ seriesColor = series.color[series.color.length - 1][1];
620
+ } else {
621
+ seriesColor = series.color;
622
+ }
623
+
624
+ if (series.type === 'line' && series.fill) {
625
+ colorDOM.style.height = '8px';
626
+ colorDOM.style.backgroundColor = series.show ? `${seriesColor}80` : opt.inactive;
627
+ colorDOM.style.border = `1px solid ${seriesColor}`;
628
+ } else {
629
+ colorDOM.style.backgroundColor = seriesColor;
630
+ }
631
+
632
+ colorDOM.dataset.type = 'color';
633
+ nameDOM.style.color = opt.color;
634
+ nameDOM.textContent = series.name;
635
+ nameDOM.setAttribute('title', series.name);
636
+ nameDOM.dataset.type = 'name';
637
+
638
+ this.legendDOM.style.padding = '5px 0 0 0';
639
+
640
+ containerDOM.appendChild(colorDOM);
641
+ containerDOM.appendChild(nameDOM);
642
+
643
+ if (opt.position === 'top' || opt.position === 'bottom') {
644
+ containerDOM.style.width = `${opt.width - 8}px`;
645
+ containerDOM.style.margin = '0 4px';
646
+ } else {
647
+ containerDOM.style.width = '100%';
648
+ }
649
+ containerDOM.style.height = '18px';
650
+ containerDOM.style.display = 'inline-block';
651
+ containerDOM.style.overflow = 'hidden';
652
+ containerDOM.dataset.type = 'container';
653
+
654
+ this.legendBoxDOM.appendChild(containerDOM);
655
+ if (series.show) {
656
+ this.seriesInfo.count++;
657
+ }
658
+ },
659
+
660
+ /**
661
+ * Add Legend Items With aggregation Values
662
+ * Only chartOption > legend > table > use : true
663
+ * @param series
664
+ */
665
+ addLegendWithValues(series) {
666
+ const opt = this.options.legend;
667
+ const columns = opt?.table?.columns;
668
+
669
+ const aggregations = this.getAggregations()?.[series?.sId];
670
+ if (!aggregations || !columns) {
671
+ return;
672
+ }
673
+
674
+ // create row
675
+ const rowDOM = document.createElement('tr');
676
+ rowDOM.className = `ev-chart-legend--table__row ${!series.show ? ' inactive' : ''}`;
677
+ Util.setDOMStyle(rowDOM, opt.table?.style?.row);
678
+ rowDOM.series = series;
679
+ rowDOM.dataset.type = 'container';
680
+
681
+ // create td - color
682
+ const colorWrapperDOM = document.createElement('td');
683
+ colorWrapperDOM.className = 'ev-chart-legend--table__color-wrapper';
684
+ colorWrapperDOM.dataset.type = 'color';
685
+
686
+ const colorDOM = document.createElement('div');
687
+ colorDOM.className = 'ev-chart-legend--table__color';
688
+ colorDOM.dataset.type = 'color';
689
+
690
+ // set series color
691
+ let seriesColor;
692
+ if (!series.show) {
693
+ seriesColor = opt.inactive;
694
+ } else if (typeof series.color !== 'string') {
695
+ seriesColor = series.color[series.color.length - 1][1];
696
+ } else {
697
+ seriesColor = series.color;
698
+ }
699
+
700
+ switch (series.type) {
701
+ case 'line': {
702
+ if (series.fill) {
703
+ colorDOM.style.backgroundColor = `${seriesColor}80`;
704
+ colorDOM.style.border = `1px solid ${seriesColor}`;
705
+ } else {
706
+ if (series.point) {
707
+ colorDOM.className += ' ev-chart-legend--table__color--point-line';
708
+ }
709
+
710
+ colorDOM.className += ' ev-chart-legend--table__color--line';
711
+ colorDOM.style.backgroundColor = seriesColor;
712
+ }
713
+ break;
714
+ }
715
+
716
+ case 'bar':
717
+ case 'pie':
718
+ default: {
719
+ colorDOM.style.height = '10px';
720
+ colorDOM.style.backgroundColor = seriesColor;
721
+ break;
722
+ }
723
+ }
724
+
725
+ if (series.type === 'line' && series.fill) {
726
+ colorDOM.style.height = '8px';
727
+ colorDOM.style.backgroundColor = series.show ? `${seriesColor}80` : opt.inactive;
728
+ colorDOM.style.border = `1px solid ${seriesColor}`;
729
+ } else {
730
+ colorDOM.style.backgroundColor = seriesColor;
731
+ }
732
+
733
+ colorWrapperDOM.appendChild(colorDOM);
734
+ rowDOM.appendChild(colorWrapperDOM);
735
+
736
+ // create td - name
737
+ const nameDOM = document.createElement('td');
738
+ nameDOM.className = 'ev-chart-legend--table__name';
739
+ nameDOM.style.color = series.show ? opt.color : opt.inactive;
740
+ nameDOM.textContent = series.name;
741
+ nameDOM.setAttribute('title', series.name);
742
+ nameDOM.dataset.type = 'name';
743
+ Util.setDOMStyle(nameDOM, columns?.name?.style);
744
+
745
+ if (!series.show) {
746
+ nameDOM.style.color = opt.inactive;
747
+ }
748
+
749
+ rowDOM.appendChild(nameDOM);
750
+
751
+ // create td - values
752
+ const columnKeyList = Object.keys(columns);
753
+ columnKeyList?.forEach((key) => {
754
+ if (key === 'name') {
755
+ return;
756
+ }
757
+
758
+ if (columns[key].use) {
759
+ const formattedTxt = this.getFormattedValue(columns[key], aggregations[key]);
760
+ const valueDOM = document.createElement('td');
761
+ valueDOM.className = 'ev-chart-legend--table__value';
762
+ valueDOM.style.color = series.show ? opt.color : opt.inactive;
763
+ valueDOM.textContent = formattedTxt;
764
+ valueDOM.dataset.type = key.toString();
765
+ Util.setDOMStyle(valueDOM, columns[key]?.style);
766
+
767
+ if (!series.show) {
768
+ valueDOM.style.color = opt.inactive;
769
+ }
770
+
771
+ rowDOM.appendChild(valueDOM);
772
+ }
773
+ });
774
+
775
+ this.legendTableDOM.appendChild(rowDOM);
776
+ if (series.show) {
777
+ this.seriesInfo.count++;
778
+ }
779
+ },
780
+
781
+ /**
782
+ * Set legend components position by option
783
+ *
784
+ * @returns {undefined}
785
+ */
786
+ setLegendPosition() {
787
+ const opt = this.options;
788
+ const position = opt?.legend?.position;
789
+ const wrapperStyle = this.wrapperDOM?.style;
790
+ const legendStyle = this.legendDOM?.style;
791
+ const boxStyle = this.legendBoxDOM?.style;
792
+ const resizeStyle = this.resizeDOM?.style;
793
+
794
+ let chartRect;
795
+ let legendPad;
796
+ const title = opt?.title?.show ? opt?.title?.height : 0;
797
+ const positionTop = title + opt?.legend?.height;
798
+ const { top = 0, bottom = 0, left = 0, right = 0 } = opt?.legend?.padding ?? {};
799
+
800
+ if (!wrapperStyle || !legendStyle) {
801
+ return;
802
+ }
803
+
804
+ boxStyle.padding = `${top}px ${right}px ${bottom}px ${left}px`;
805
+
806
+ switch (position) {
807
+ case 'top':
808
+ wrapperStyle.padding = `${positionTop}px 0 0 0`;
809
+ chartRect = this.chartDOM.getBoundingClientRect();
810
+ legendPad = parseInt(legendStyle.paddingTop) + parseInt(legendStyle.paddingBottom);
811
+
812
+ boxStyle.width = '100%';
813
+ boxStyle.height = `${opt.legend.height - legendPad}px`;
814
+
815
+ legendStyle.top = `${title}px`;
816
+ legendStyle.right = '';
817
+ legendStyle.bottom = '';
818
+ legendStyle.left = '';
819
+
820
+ legendStyle.width = `${chartRect.width}px`;
821
+ legendStyle.height = `${opt.legend.height + (resizeStyle ? 4 : 0)}px`; // 4 resize bar size
822
+
823
+ if (resizeStyle) {
824
+ resizeStyle.top = `${positionTop}px`;
825
+ resizeStyle.right = '';
826
+ resizeStyle.bottom = '';
827
+ resizeStyle.left = '';
828
+
829
+ resizeStyle.width = `${chartRect.width}px`;
830
+ resizeStyle.height = '4px';
831
+ resizeStyle.cursor = 'row-resize';
832
+ }
833
+ break;
834
+ case 'right':
835
+ wrapperStyle.padding = `${title}px ${opt.legend.width}px 0 0`;
836
+ chartRect = this.chartDOM.getBoundingClientRect();
837
+
838
+ boxStyle.width = `${opt.legend.width - 10}px`; // legendDOM left padding
839
+ boxStyle.maxHeight = `${chartRect.height}px`;
840
+
841
+ legendStyle.paddingLeft = '10px';
842
+ legendStyle.top = `${title}px`;
843
+ legendStyle.right = '0px';
844
+ legendStyle.bottom = '';
845
+ legendStyle.left = '';
846
+
847
+ legendStyle.width = `${opt.legend.width}px`;
848
+ legendStyle.height = `${chartRect.height}px`;
849
+
850
+ if (resizeStyle) {
851
+ resizeStyle.top = `${title}px`;
852
+ resizeStyle.right = `${opt.legend.width}px`;
853
+ resizeStyle.bottom = '';
854
+ resizeStyle.left = '';
855
+
856
+ resizeStyle.width = '4px';
857
+ resizeStyle.height = `${chartRect.height}px`;
858
+ resizeStyle.cursor = 'col-resize';
859
+ }
860
+ break;
861
+ case 'bottom':
862
+ wrapperStyle.padding = `${title}px 0 ${opt.legend.height}px 0`;
863
+ chartRect = this.chartDOM.getBoundingClientRect();
864
+ legendPad = parseInt(legendStyle.paddingTop) + parseInt(legendStyle.paddingBottom);
865
+
866
+ boxStyle.width = '100%';
867
+ boxStyle.height = `${opt.legend.height - legendPad}px`;
868
+
869
+ legendStyle.top = '';
870
+ legendStyle.right = '';
871
+ legendStyle.bottom = '0px';
872
+ legendStyle.left = '0px';
873
+
874
+ legendStyle.width = `${chartRect.width}px`;
875
+ legendStyle.height = `${opt.legend.height + (resizeStyle ? 4 : 0)}px`; // 4 resize bar size
876
+
877
+ if (resizeStyle) {
878
+ resizeStyle.top = '';
879
+ resizeStyle.right = '';
880
+ resizeStyle.bottom = `${opt.legend.height}px`;
881
+ resizeStyle.left = '';
882
+
883
+ resizeStyle.width = `${chartRect.width}px`;
884
+ resizeStyle.height = '4px';
885
+ resizeStyle.cursor = 'row-resize';
886
+ }
887
+ break;
888
+ case 'left':
889
+ wrapperStyle.padding = `${title}px 0 0 ${opt.legend.width}px`;
890
+ chartRect = this.chartDOM.getBoundingClientRect();
891
+
892
+ boxStyle.width = `${opt.legend.width}px`;
893
+ boxStyle.maxHeight = `${chartRect.height}px`;
894
+ boxStyle.display = 'absolute';
895
+ boxStyle.bottom = '0px';
896
+
897
+ legendStyle.top = `${title}px`;
898
+ legendStyle.right = '';
899
+ legendStyle.bottom = '';
900
+ legendStyle.left = '0px';
901
+
902
+ legendStyle.width = `${opt.legend.width}px`;
903
+ legendStyle.height = `${chartRect.height}px`;
904
+
905
+ if (resizeStyle) {
906
+ resizeStyle.top = `${title}px`;
907
+ resizeStyle.right = '';
908
+ resizeStyle.bottom = '';
909
+ resizeStyle.left = `${opt.legend.width}px`;
910
+
911
+ resizeStyle.width = '4px';
912
+ resizeStyle.height = `${chartRect.height}px`;
913
+ resizeStyle.cursor = 'col-resize';
914
+ }
915
+ break;
916
+ default:
917
+ break;
918
+ }
919
+ },
920
+
921
+ /**
922
+ * Update legend components size
923
+ *
924
+ * @returns {undefined}
925
+ */
926
+ updateLegendContainerSize() {
927
+ if (!this.options || !this.legendBoxDOM) {
928
+ return;
929
+ }
930
+
931
+ const opt = this.options?.legend;
932
+ const container = this.legendBoxDOM.getElementsByClassName('ev-chart-legend-container');
933
+
934
+ if (!container) {
935
+ return;
936
+ }
937
+
938
+ for (let ix = 0; ix < container.length; ix++) {
939
+ if (opt.position === 'top' || opt.position === 'bottom') {
940
+ container[ix].style.width = `${opt.width - 8}px`;
941
+ container[ix].style.margin = '0 4px';
942
+ } else {
943
+ container[ix].style.width = '100%';
944
+ }
945
+ }
946
+ },
947
+
948
+ /**
949
+ * When user moves resizeDOM, this function will change css
950
+ *
951
+ * @returns {undefined}
952
+ */
953
+ onMouseMove(e) {
954
+ e.stopPropagation();
955
+ e.preventDefault();
956
+
957
+ const offset = this.wrapperDOM.getBoundingClientRect();
958
+ const offsetWidth = this.wrapperDOM.offsetWidth;
959
+ const offsetHeight = this.wrapperDOM.offsetHeight;
960
+
961
+ const titleHeight = this.options.title.show ? this.options.title.height : 0;
962
+ const position = this.options.legend.position;
963
+
964
+ const chartMinWidth = 150;
965
+ const chartMinHeight = 70;
966
+
967
+ const legendMinWidth = 120;
968
+ const legendMinHeight = 20;
969
+
970
+ let move;
971
+
972
+ switch (position) {
973
+ case 'left':
974
+ move = e.clientX - offset.left;
975
+ if (move < legendMinWidth) {
976
+ move = legendMinWidth;
977
+ } else if (move > offsetWidth - chartMinWidth) {
978
+ move = offsetWidth - chartMinWidth;
979
+ }
980
+ this.ghostDOM.style.left = `${move}px`;
981
+ break;
982
+ case 'right':
983
+ move = e.clientX - offset.left;
984
+ if (move < chartMinWidth) {
985
+ move = chartMinWidth;
986
+ } else if (move > offsetWidth - legendMinWidth) {
987
+ move = offsetWidth - legendMinWidth;
988
+ }
989
+ this.ghostDOM.style.left = `${move}px`;
990
+ break;
991
+ case 'top':
992
+ move = e.clientY - offset.top;
993
+ if (move < legendMinHeight + titleHeight) {
994
+ move = legendMinHeight + titleHeight;
995
+ } else if (move > offsetHeight - chartMinHeight) {
996
+ move = offsetHeight - chartMinHeight;
997
+ }
998
+ this.ghostDOM.style.top = `${move}px`;
999
+ break;
1000
+ case 'bottom':
1001
+ move = e.clientY - offset.top;
1002
+ if (move < chartMinHeight + titleHeight) {
1003
+ move = chartMinHeight + titleHeight;
1004
+ } else if (move > offsetHeight - legendMinHeight) {
1005
+ move = offsetHeight - legendMinHeight;
1006
+ }
1007
+ this.ghostDOM.style.bottom = `${this.wrapperDOM.offsetHeight - move}px`;
1008
+ break;
1009
+ default:
1010
+ break;
1011
+ }
1012
+
1013
+ this.isLegendMove = true;
1014
+ },
1015
+
1016
+ /**
1017
+ * callback for mouseup on ghostDOM, this function will change legend and chart size.
1018
+ *
1019
+ * @returns {undefined}
1020
+ */
1021
+ onMouseUp(e) {
1022
+ e.stopPropagation();
1023
+ e.preventDefault();
1024
+
1025
+ this.wrapperDOM.removeEventListener('mousemove', this.mouseMove, false);
1026
+ this.wrapperDOM.removeEventListener('mouseup', this.mouseUp, false);
1027
+
1028
+ const opt = this.options;
1029
+ const pos = opt.legend.position;
1030
+ const resizeDOMStyle = this.resizeDOM.style;
1031
+ const legendDOMStyle = this.legendDOM.style;
1032
+ const boxDOMStyle = this.legendBoxDOM.style;
1033
+ const ghostDOMStyle = this.ghostDOM.style;
1034
+ const wrapperDOMStyle = this.wrapperDOM.style;
1035
+
1036
+ const title = opt.title.show ? opt.title.height : 0;
1037
+ const padding = +this.legendDOM.style.paddingLeft.replace('px', '');
1038
+ let move;
1039
+ if (this.isLegendMove) {
1040
+ switch (pos) {
1041
+ case 'top':
1042
+ resizeDOMStyle.top = ghostDOMStyle.top;
1043
+ move = +ghostDOMStyle.top.replace('px', '');
1044
+ legendDOMStyle.height = `${move - title}px`;
1045
+ boxDOMStyle.height = `${move - title - 4}px`;
1046
+ opt.legend.height = move - title - 4;
1047
+ wrapperDOMStyle.padding = `${move}px 0 0 0`;
1048
+ break;
1049
+ case 'right':
1050
+ resizeDOMStyle.left = ghostDOMStyle.left;
1051
+ move = +ghostDOMStyle.left.replace('px', '');
1052
+ legendDOMStyle.width = `${(this.wrapperDOM.offsetWidth - move - 4)}px`;
1053
+ boxDOMStyle.width = `${(this.wrapperDOM.offsetWidth - move - 4 - padding)}px`;
1054
+ opt.legend.width = this.wrapperDOM.offsetWidth - move - 4;
1055
+ wrapperDOMStyle.padding = `${title}px ${this.wrapperDOM.offsetWidth - move}px 0 0`;
1056
+ break;
1057
+ case 'bottom':
1058
+ resizeDOMStyle.bottom = ghostDOMStyle.bottom;
1059
+ move = this.wrapperDOM.offsetHeight - (+ghostDOMStyle.bottom.replace('px', ''));
1060
+ legendDOMStyle.height = `${this.wrapperDOM.offsetHeight - move}px`;
1061
+ boxDOMStyle.height = `${move - title - 4}px`;
1062
+ opt.legend.height = this.wrapperDOM.offsetHeight - move;
1063
+ wrapperDOMStyle.padding = `${title}px 0 ${this.wrapperDOM.offsetHeight - move}px 0`;
1064
+ break;
1065
+ case 'left':
1066
+ resizeDOMStyle.left = ghostDOMStyle.left;
1067
+ move = +ghostDOMStyle.left.replace('px', '');
1068
+ legendDOMStyle.width = `${move}px`;
1069
+ boxDOMStyle.width = `${move}px`;
1070
+ opt.legend.width = move;
1071
+ wrapperDOMStyle.padding = `${title}px 0 0 ${move - 4}px`;
1072
+ break;
1073
+ default:
1074
+ break;
1075
+ }
1076
+ }
1077
+
1078
+ resizeDOMStyle.display = 'block';
1079
+ this.ghostDOM.remove();
1080
+
1081
+ if (this.isLegendMove) {
1082
+ this.render();
1083
+ this.isLegendMove = false;
1084
+ }
1085
+ },
1086
+
1087
+ /**
1088
+ * Show legend components by manipulating css
1089
+ *
1090
+ * @returns {undefined}
1091
+ */
1092
+ showLegend() {
1093
+ if (this.resizeDOM) {
1094
+ this.resizeDOM.style.display = 'block';
1095
+ }
1096
+
1097
+ if (this.legendDOM) {
1098
+ this.legendDOM.style.display = 'block';
1099
+ }
1100
+ },
1101
+
1102
+ /**
1103
+ * Hide legend components by manipulating css
1104
+ *
1105
+ * @returns {undefined}
1106
+ */
1107
+ hideLegend() {
1108
+ const opt = this.options;
1109
+ const wrapperStyle = this.wrapperDOM?.style;
1110
+ const resizeStyle = this.resizeDOM?.style;
1111
+ const legendStyle = this.legendDOM?.style;
1112
+ const title = opt?.title?.show ? opt?.title?.height : 0;
1113
+
1114
+ if (!legendStyle || !wrapperStyle) {
1115
+ return;
1116
+ }
1117
+
1118
+ if (resizeStyle) {
1119
+ resizeStyle.display = 'none';
1120
+ }
1121
+
1122
+ legendStyle.display = 'none';
1123
+ legendStyle.width = '0';
1124
+ legendStyle.height = '0';
1125
+ wrapperStyle.padding = `${title}px 0 0 0`;
1126
+ },
1127
+
1128
+ /**
1129
+ * Get formatted value by formatter function
1130
+ * Only chartOption > legend > table > use : true
1131
+ * @param formatter
1132
+ * @param decimalPoint
1133
+ * @param value
1134
+ * @returns {string}
1135
+ */
1136
+ getFormattedValue({ formatter, decimalPoint }, value) {
1137
+ if (value === undefined || value === null) {
1138
+ return 'Null';
1139
+ }
1140
+
1141
+ let formattedTxt;
1142
+ if (formatter) {
1143
+ formattedTxt = formatter(+value);
1144
+ }
1145
+
1146
+ if (!formatter || typeof formattedTxt !== 'string') {
1147
+ formattedTxt = Util.labelSignFormat(+value, decimalPoint);
1148
+ }
1149
+
1150
+ return formattedTxt;
1151
+ },
1152
+
1153
+ };
1154
+
1155
+ export default modules;