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,692 +1,692 @@
1
- import { convertToPercent } from '@/common/utils';
2
- import debounce from '@/common/utils.debounce';
3
- import Canvas from '../helpers/helpers.canvas';
4
- import Util from '../helpers/helpers.util';
5
-
6
- const TITLE_HEIGHT = 30;
7
- const TEXT_HEIGHT = 14;
8
- const LINE_SPACING = 8;
9
- const COLOR_MARGIN = 16;
10
- const VALUE_MARGIN = 50;
11
- const SCROLL_WIDTH = 17;
12
- let fontStyle = 'normal normal lighter 14px Roboto';
13
-
14
- const modules = {
15
- /**
16
- * Create tooltip DOM
17
- *
18
- * @returns {undefined}
19
- */
20
- createTooltipDOM() {
21
- this.tooltipDOM = document.createElement('div');
22
- this.tooltipDOM.className = 'ev-chart-tooltip';
23
-
24
- this.tooltipHeaderDOM = document.createElement('div');
25
- this.tooltipHeaderDOM.className = 'ev-chart-tooltip-header';
26
-
27
- this.tooltipBodyDOM = document.createElement('div');
28
- this.tooltipBodyDOM.className = 'ev-chart-tooltip-body';
29
-
30
- this.tooltipCanvas = document.createElement('canvas');
31
- this.tooltipCanvas.className = 'ev-chart-tooltip-canvas';
32
- this.tooltipCtx = this.tooltipCanvas.getContext('2d');
33
-
34
- this.tooltipDOM.style.display = 'none';
35
- this.setFontFamily();
36
-
37
- this.tooltipBodyDOM.appendChild(this.tooltipCanvas);
38
- this.tooltipDOM.appendChild(this.tooltipHeaderDOM);
39
- this.tooltipDOM.appendChild(this.tooltipBodyDOM);
40
-
41
- document.body.appendChild(this.tooltipDOM);
42
-
43
- if (this.options.tooltip.debouncedHide) {
44
- this.hideTooltipDOM = debounce(() => {
45
- this.tooltipDOM.style.display = 'none';
46
- }, 200);
47
- } else {
48
- this.hideTooltipDOM = () => {
49
- this.tooltipDOM.style.display = 'none';
50
- };
51
- }
52
- },
53
-
54
- /**
55
- * set tooltip's font style
56
- */
57
- setFontFamily() {
58
- const fontFamily = this.options?.tooltip?.fontFamily ?? 'Roboto';
59
- fontStyle = `normal normal lighter 14px ${fontFamily}`;
60
- this.tooltipHeaderDOM.style.fontFamily = fontFamily;
61
- },
62
-
63
- /**
64
- * Set tooltip DOM's position and style
65
- * @param {object} hitInfo value and mouse position touched
66
- * @param {object} e mousemove callback
67
- *
68
- * @returns {object} tooltip layout information
69
- */
70
- setTooltipLayoutPosition(hitInfo, e) {
71
- const ctx = this.tooltipCtx;
72
- const mouseX = e.pageX;
73
- const mouseY = e.pageY;
74
- const items = hitInfo.items;
75
- const [maxSeries, maxValue] = hitInfo.maxTip;
76
- const seriesKeys = Object.keys(items);
77
- const seriesLen = seriesKeys.length;
78
- const boxPadding = { t: 8, b: 8, r: 20, l: 16 };
79
- const opt = this.options.tooltip;
80
-
81
-
82
- // calculate and decide width of canvas El(contentsWidth)
83
- ctx.save();
84
- ctx.font = fontStyle;
85
- const isHorizontal = !!this.options.horizontal;
86
- const label = isHorizontal ? items[hitInfo.hitId]?.data?.y : items[hitInfo.hitId]?.data?.x;
87
- const tooltipValue = label?.length > maxSeries.length ? label : maxSeries;
88
- const nw = Math.round(ctx.measureText(tooltipValue).width);
89
- const vw = Math.round(ctx.measureText(maxValue).width);
90
- const expectedContentsWidth = nw + vw + boxPadding.l + boxPadding.r
91
- + COLOR_MARGIN + VALUE_MARGIN + SCROLL_WIDTH;
92
- const contentsWidth = expectedContentsWidth > opt.maxWidth
93
- ? opt.maxWidth
94
- : expectedContentsWidth;
95
-
96
-
97
- // Calculate height of canvas El(tooltip body El) with wrapped line count
98
- let textLineCnt = opt.textOverflow === 'wrap' ? 0 : seriesLen;
99
-
100
- if (opt.textOverflow === 'wrap') {
101
- const seriesNameSpaceWidth = opt.maxWidth - (Math.round(ctx.measureText(maxValue).width)
102
- + boxPadding.l + boxPadding.r + COLOR_MARGIN + VALUE_MARGIN + SCROLL_WIDTH);
103
-
104
- // count wrap line
105
- const seriesNames = Object.values(items).map(s => s.name);
106
- seriesNames.forEach((name) => {
107
- if (ctx.measureText(name).width > seriesNameSpaceWidth) {
108
- let line = '';
109
- for (let jx = 0; jx < name.length; jx++) {
110
- const char = name[jx];
111
- const temp = `${line}${char}`;
112
- if (ctx.measureText(temp).width > seriesNameSpaceWidth) {
113
- line = char;
114
- textLineCnt += 1;
115
- } else {
116
- line = temp;
117
- }
118
- }
119
- }
120
- textLineCnt += 1;
121
- });
122
- ctx.restore();
123
- }
124
-
125
-
126
- // Calculate height of canvas El(tooltip body El) with useScrollbar, maxHeight option
127
- const expectedContentsHeight = boxPadding.t
128
- + (textLineCnt * TEXT_HEIGHT)
129
- + (seriesLen * LINE_SPACING)
130
- + boxPadding.b;
131
-
132
- let contentsHeight;
133
- if (opt.useScrollbar && expectedContentsHeight > opt.maxHeight) {
134
- this.tooltipBodyDOM.style.overflowY = 'auto';
135
- contentsHeight = opt.maxHeight;
136
- } else {
137
- this.tooltipBodyDOM.style.overflowY = 'hidden';
138
- contentsHeight = expectedContentsHeight;
139
- }
140
-
141
- // set width / height to all DOM elements (canvas, tooltip(wrapper), header, body)
142
- this.tooltipCanvas.width = contentsWidth * this.pixelRatio;
143
- this.tooltipCanvas.height = expectedContentsHeight * this.pixelRatio;
144
- this.tooltipCanvas.style.width = `${contentsWidth}px`;
145
- this.tooltipCanvas.style.height = `${expectedContentsHeight}px`;
146
- this.tooltipHeaderDOM.style.width = `${contentsWidth}px`;
147
- this.tooltipHeaderDOM.style.height = opt.textOverflow === 'wrap' ? 'auto' : `${TITLE_HEIGHT}px`;
148
- this.tooltipDOM.style.height = 'auto';
149
- this.tooltipBodyDOM.style.height = `${contentsHeight + 6}px`;
150
-
151
-
152
- // set tooltipDOM's positions
153
- const bodyWidth = document.body.clientWidth;
154
- const bodyHeight = document.body.clientHeight;
155
- const distanceMouseAndTooltip = 20;
156
- const maximumPosX = bodyWidth - contentsWidth - distanceMouseAndTooltip;
157
- const maximumPosY = bodyHeight - (TITLE_HEIGHT + contentsHeight) - distanceMouseAndTooltip;
158
- const expectedPosX = mouseX + distanceMouseAndTooltip;
159
- const expectedPosY = mouseY + distanceMouseAndTooltip;
160
- const reversedPosX = mouseX - contentsWidth - distanceMouseAndTooltip;
161
- const reversedPosY = mouseY - (TITLE_HEIGHT + contentsHeight) - distanceMouseAndTooltip;
162
- this.tooltipDOM.style.left = expectedPosX > maximumPosX
163
- ? `${reversedPosX}px`
164
- : `${expectedPosX}px`;
165
- this.tooltipDOM.style.top = expectedPosY > maximumPosY
166
- ? `${reversedPosY}px`
167
- : `${expectedPosY}px`;
168
- },
169
-
170
- /**
171
- * Draw tooltip canvas
172
- * @param {object} hitInfo mousemove callback
173
- * @param {object} context tooltip canvas context
174
- *
175
- * @returns {undefined}
176
- */
177
- drawTooltip(hitInfo, context) {
178
- const ctx = context;
179
- const items = hitInfo.items;
180
- const sId = hitInfo.hitId;
181
- const hitItem = items[sId].data;
182
- const hitAxis = items[sId].axis;
183
- const [, maxValue] = hitInfo.maxTip;
184
- const seriesKeys = this.alignSeriesList(Object.keys(items));
185
- const boxPadding = { t: 8, b: 8, r: 20, l: 16 };
186
- const isHorizontal = this.options.horizontal;
187
- const opt = this.options.tooltip;
188
- const titleFormatter = opt.formatter?.title;
189
-
190
- // draw tooltip Title(axis label) and add style class for wrap line about too much long label.
191
- if (this.axesX.length && this.axesY.length) {
192
- if (titleFormatter) {
193
- this.tooltipHeaderDOM.textContent = titleFormatter({
194
- x: hitItem.x,
195
- y: hitItem.y,
196
- });
197
- } else {
198
- this.tooltipHeaderDOM.textContent = this.options.horizontal
199
- ? this.axesY[hitAxis.y].getLabelFormat(hitItem.y)
200
- : this.axesX[hitAxis.x].getLabelFormat(hitItem.x);
201
- }
202
- }
203
-
204
- if (opt.textOverflow) {
205
- this.tooltipHeaderDOM.classList.add(`ev-chart-tooltip-header--${opt.textOverflow}`);
206
- }
207
-
208
- // draw tooltip contents (series, value combination)
209
- let x = 2;
210
- let y = 2;
211
-
212
- x += Util.aliasPixel(x);
213
- y += Util.aliasPixel(y);
214
-
215
- ctx.save();
216
- ctx.scale(this.pixelRatio, this.pixelRatio);
217
-
218
- if (this.tooltipBodyDOM.style.overflowY === 'auto') {
219
- boxPadding.r += SCROLL_WIDTH;
220
- }
221
-
222
- x += boxPadding.l;
223
- y += boxPadding.t;
224
-
225
- ctx.font = fontStyle;
226
-
227
- const seriesList = [];
228
- seriesKeys.forEach((seriesName) => {
229
- seriesList.push({
230
- data: items[seriesName].data,
231
- color: items[seriesName].color,
232
- name: items[seriesName].name,
233
- });
234
- });
235
-
236
- if (opt.sortByValue) {
237
- seriesList.sort((a, b) => {
238
- let prev = a.data.o;
239
- let next = b.data.o;
240
-
241
- if (prev === null || prev === undefined) {
242
- prev = isHorizontal ? a.data.x : a.data.y;
243
- }
244
-
245
- if (next === null || next === undefined) {
246
- next = isHorizontal ? b.data.x : b.data.y;
247
- }
248
- return next - prev;
249
- });
250
- }
251
-
252
- this.setTooltipDOMStyle(opt);
253
-
254
- let textLineCnt = 1;
255
- for (let ix = 0; ix < seriesList.length; ix++) {
256
- const gdata = seriesList[ix].data;
257
- const color = seriesList[ix].color;
258
- const name = seriesList[ix].name;
259
- const valueText = gdata.formatted;
260
-
261
- let itemX = x + 4;
262
- let itemY = y + (textLineCnt * TEXT_HEIGHT);
263
- itemX += Util.aliasPixel(itemX);
264
- itemY += Util.aliasPixel(itemY);
265
-
266
- ctx.beginPath();
267
-
268
- if (typeof color !== 'string') {
269
- ctx.fillStyle = Canvas.createGradient(
270
- ctx,
271
- isHorizontal,
272
- { x: itemX - 4, y: itemY, w: 12, h: -12 },
273
- color,
274
- );
275
- } else {
276
- ctx.fillStyle = color;
277
- }
278
-
279
- // 1. Draw series color
280
- ctx.fillRect(itemX - 4, itemY - 12, 12, 12);
281
- ctx.fillStyle = opt.fontColor;
282
-
283
- // 2. Draw series name
284
- ctx.textBaseline = 'Bottom';
285
- const seriesNameSpaceWidth = opt.maxWidth - Math.round(ctx.measureText(maxValue).width)
286
- - boxPadding.l - boxPadding.r - COLOR_MARGIN - VALUE_MARGIN;
287
- const xPos = itemX + COLOR_MARGIN;
288
- const yPos = itemY;
289
-
290
- if (seriesNameSpaceWidth > ctx.measureText(name).width) { // draw normally
291
- ctx.fillText(name, xPos, yPos);
292
- } else if (opt.textOverflow === 'wrap') { // draw with wrap
293
- let line = '';
294
- let yPosWithWrap = yPos;
295
-
296
- for (let jx = 0; jx < name.length; jx++) {
297
- const char = name[jx];
298
- const temp = `${line}${char}`;
299
-
300
- if (ctx.measureText(temp).width > seriesNameSpaceWidth) {
301
- ctx.fillText(line, xPos, yPosWithWrap);
302
- line = char;
303
- textLineCnt += 1;
304
- yPosWithWrap += TEXT_HEIGHT;
305
- } else {
306
- line = temp;
307
- }
308
- }
309
- ctx.fillText(line, xPos, yPosWithWrap);
310
- } else { // draw with ellipsis
311
- const shortSeriesName = Util.truncateLabelWithEllipsis(name, seriesNameSpaceWidth, ctx);
312
- ctx.fillText(shortSeriesName, xPos, yPos);
313
- }
314
-
315
- ctx.save();
316
-
317
- // 3. Draw value
318
- ctx.textAlign = 'right';
319
- ctx.fillText(valueText, this.tooltipDOM.offsetWidth - boxPadding.r, itemY);
320
- ctx.restore();
321
- ctx.closePath();
322
-
323
- // 4. add lineSpacing
324
- y += LINE_SPACING;
325
- textLineCnt += 1;
326
- }
327
-
328
- ctx.restore();
329
- },
330
-
331
- /**
332
- * Draw tooltip canvas for heatmap
333
- * @param {object} hitInfo mousemove callback
334
- * @param {object} context tooltip canvas context
335
- *
336
- * @returns {undefined}
337
- */
338
- drawToolTipForHeatMap(hitInfo, context) {
339
- const ctx = context;
340
- const items = hitInfo.items;
341
- const sId = hitInfo.hitId;
342
- const hitItem = items[sId].data;
343
- const hitAxis = items[sId].axis;
344
- const hitColor = items[sId].color;
345
- const boxPadding = { t: 8, b: 8, r: 20, l: 16 };
346
- const isHorizontal = this.options.horizontal;
347
- const opt = this.options.tooltip;
348
- const titleFormatter = opt.formatter?.title;
349
- const series = Object.values(this.seriesList)[0];
350
-
351
- let isShow = false;
352
- const { colorState, isGradient } = series;
353
- if (isGradient) {
354
- const { min, max } = series.valueOpt;
355
- const ratio = convertToPercent(hitItem.o - min, max - min);
356
- const { start, end } = colorState[0];
357
- isShow = (start <= ratio && ratio <= end) || hitItem.o === -1;
358
- } else {
359
- isShow = colorState.find(({ id }) => id === hitItem.cId)?.show;
360
- }
361
-
362
- if (!isShow) {
363
- this.tooltipClear();
364
- return;
365
- }
366
-
367
- // draw tooltip Title(axis label) and add style class for wrap line about too much long label.
368
- if (this.axesX.length) {
369
- if (titleFormatter) {
370
- this.tooltipHeaderDOM.textContent = titleFormatter({
371
- x: hitItem.x,
372
- y: hitItem.y,
373
- });
374
- } else {
375
- this.tooltipHeaderDOM.textContent = this.axesX[hitAxis.x].getLabelFormat(hitItem.x);
376
- }
377
- }
378
-
379
- if (opt.textOverflow) {
380
- this.tooltipHeaderDOM.classList.add(`ev-chart-tooltip-header--${opt.textOverflow}`);
381
- }
382
-
383
- this.setTooltipDOMStyle(opt);
384
-
385
- // draw tooltip contents (series, value combination)
386
- ctx.save();
387
- ctx.scale(this.pixelRatio, this.pixelRatio);
388
-
389
- if (this.tooltipBodyDOM.style.overflowY === 'auto') {
390
- boxPadding.r += SCROLL_WIDTH;
391
- }
392
-
393
- const itemX = boxPadding.l + 2;
394
- const itemY = boxPadding.t + TEXT_HEIGHT + 2;
395
- const valueText = hitItem.formatted;
396
-
397
- ctx.font = fontStyle;
398
-
399
- ctx.beginPath();
400
-
401
- if (typeof hitColor !== 'string') {
402
- ctx.fillStyle = Canvas.createGradient(
403
- ctx,
404
- isHorizontal,
405
- { x: itemX, y: itemY, w: 12, h: -12 },
406
- hitColor,
407
- );
408
- } else {
409
- ctx.fillStyle = hitColor;
410
- }
411
-
412
- // 1. Draw value color
413
- ctx.fillRect(itemX - 4, itemY - 12, 12, 12);
414
- ctx.fillStyle = opt.fontColor;
415
-
416
- // 2. Draw value y names
417
- ctx.textBaseline = 'Bottom';
418
- if (this.axesY.length) {
419
- ctx.fillText(this.axesY[hitAxis.y].getLabelFormat(hitItem.y), itemX + COLOR_MARGIN, itemY);
420
- }
421
-
422
- // 3. Draw value
423
- ctx.textAlign = 'right';
424
- ctx.fillText(valueText, this.tooltipDOM.offsetWidth - boxPadding.r, itemY);
425
- ctx.closePath();
426
- },
427
-
428
- /**
429
- *
430
- * @param hitInfo
431
- * @param context
432
- */
433
- drawTooltipForScatter(hitInfo, context) {
434
- const ctx = context;
435
- const items = hitInfo.items;
436
- const [, maxValue] = hitInfo.maxTip;
437
- const seriesKeys = this.alignSeriesList(Object.keys(items));
438
- const boxPadding = { t: 8, b: 8, r: 8, l: 8 };
439
- const opt = this.options.tooltip;
440
-
441
- let x = 2;
442
- let y = 2;
443
-
444
- x += Util.aliasPixel(x);
445
- y += Util.aliasPixel(y);
446
-
447
- ctx.save();
448
- ctx.scale(this.pixelRatio, this.pixelRatio);
449
-
450
- if (this.tooltipBodyDOM.style.overflowY === 'auto') {
451
- boxPadding.r += SCROLL_WIDTH;
452
- }
453
-
454
- x += boxPadding.l;
455
- y += boxPadding.t;
456
-
457
- ctx.font = fontStyle;
458
-
459
- const seriesList = [];
460
- seriesKeys.forEach((seriesName) => {
461
- seriesList.push({
462
- data: items[seriesName].data,
463
- color: items[seriesName].color,
464
- name: items[seriesName].name,
465
- });
466
- });
467
-
468
- if (opt.sortByValue) {
469
- seriesList.sort((a, b) => {
470
- let prev = a.data.o;
471
- let next = b.data.o;
472
-
473
- if (prev === null || prev === undefined) {
474
- prev = a.data.y;
475
- }
476
-
477
- if (next === null || next === undefined) {
478
- next = b.data.y;
479
- }
480
-
481
- return next - prev;
482
- });
483
- }
484
-
485
- this.setTooltipDOMStyle(opt);
486
-
487
- let textLineCnt = 1;
488
- for (let ix = 0; ix < seriesList.length; ix++) {
489
- const gdata = seriesList[ix].data;
490
- const color = seriesList[ix].color;
491
- const name = seriesList[ix].name;
492
- const valueText = gdata.formatted;
493
-
494
- let itemX = x + 4;
495
- let itemY = y + (textLineCnt * TEXT_HEIGHT);
496
- itemX += Util.aliasPixel(itemX);
497
- itemY += Util.aliasPixel(itemY);
498
-
499
- ctx.beginPath();
500
-
501
- if (typeof color !== 'string') {
502
- ctx.fillStyle = Canvas.createGradient(
503
- ctx,
504
- false,
505
- { x: itemX - 4, y: itemY, w: 12, h: -12 },
506
- color,
507
- );
508
- } else {
509
- ctx.fillStyle = color;
510
- }
511
-
512
- // 1. Draw series color
513
- ctx.fillRect(itemX - 4, itemY - 12, 12, 12);
514
- ctx.fillStyle = opt.fontColor;
515
-
516
- // 2. Draw series name
517
- ctx.textBaseline = 'Bottom';
518
- const seriesNameSpaceWidth = opt.maxWidth - Math.round(ctx.measureText(maxValue).width)
519
- - boxPadding.l - boxPadding.r - COLOR_MARGIN - VALUE_MARGIN;
520
- const xPos = itemX + COLOR_MARGIN;
521
- const yPos = itemY;
522
-
523
- if (seriesNameSpaceWidth > ctx.measureText(name).width) { // draw normally
524
- ctx.fillText(name, xPos, yPos);
525
- } else if (opt.textOverflow === 'wrap') { // draw with wrap
526
- let line = '';
527
- let yPosWithWrap = yPos;
528
-
529
- for (let jx = 0; jx < name.length; jx++) {
530
- const char = name[jx];
531
- const temp = `${line}${char}`;
532
-
533
- if (ctx.measureText(temp).width > seriesNameSpaceWidth) {
534
- ctx.fillText(line, xPos, yPosWithWrap);
535
- line = char;
536
- textLineCnt += 1;
537
- yPosWithWrap += TEXT_HEIGHT;
538
- } else {
539
- line = temp;
540
- }
541
- }
542
- ctx.fillText(line, xPos, yPosWithWrap);
543
- } else { // draw with ellipsis
544
- const shortSeriesName = Util.truncateLabelWithEllipsis(name, seriesNameSpaceWidth, ctx);
545
- ctx.fillText(shortSeriesName, xPos, yPos);
546
- }
547
-
548
- ctx.save();
549
-
550
- // 3. Draw value
551
- ctx.textAlign = 'right';
552
- ctx.fillText(valueText, this.tooltipDOM.offsetWidth - boxPadding.r, itemY);
553
- ctx.restore();
554
- ctx.closePath();
555
-
556
- // 4. add lineSpacing
557
- y += LINE_SPACING;
558
- textLineCnt += 1;
559
- }
560
-
561
- ctx.restore();
562
- },
563
-
564
- /**
565
- * set style properties on tooltip DOM
566
- * @param tooltipOptions
567
- */
568
- setTooltipDOMStyle(tooltipOptions) {
569
- this.tooltipDOM.style.overflowY = 'hidden';
570
- this.tooltipDOM.style.backgroundColor = tooltipOptions.backgroundColor;
571
- this.tooltipDOM.style.border = `1px solid ${tooltipOptions.borderColor}`;
572
- this.tooltipDOM.style.color = tooltipOptions.fontColor;
573
-
574
- if (tooltipOptions.useShadow) {
575
- const shadowColor = `rgba(0, 0, 0, ${tooltipOptions.shadowOpacity})`;
576
- this.tooltipDOM.style.boxShadow = `2px 2px 2px ${shadowColor}`;
577
- }
578
-
579
- this.tooltipDOM.style.display = 'block';
580
- },
581
-
582
- /**
583
- * Draw graph item highlight
584
- * @param {object} hitInfo mousemove callback
585
- * @param {object} ctx overlayCanvas context
586
- *
587
- * @returns {undefined}
588
- */
589
- drawItemsHighlight(hitInfo, ctx) {
590
- Object.keys(hitInfo.items).forEach((sId) => {
591
- const series = this.seriesList[sId];
592
- series.itemHighlight(hitInfo.items[sId], ctx);
593
-
594
- if (Util.isDoughnutHole(series.type)) {
595
- this.drawDoughnutHole(ctx);
596
- }
597
- });
598
- },
599
-
600
- /**
601
- * Draw chart indicator with mousemove
602
- * @param {object} offset mousemove callback
603
- * @param {string} color indicator color
604
- *
605
- * @returns {undefined}
606
- */
607
- drawIndicator(offset, color) {
608
- const ctx = this.overlayCtx;
609
- const [offsetX, offsetY] = offset;
610
- const graphPos = {
611
- x1: this.chartRect.x1 + this.labelOffset.left,
612
- x2: this.chartRect.x2 - this.labelOffset.right,
613
- y1: this.chartRect.y1 + this.labelOffset.top,
614
- y2: this.chartRect.y2 - this.labelOffset.bottom,
615
- };
616
- const mouseXIp = 1; // mouseInterpolation
617
- const mouseYIp = 10;
618
-
619
- if (offsetX >= (graphPos.x1 - mouseXIp) && offsetX <= (graphPos.x2 + mouseXIp)
620
- && offsetY >= (graphPos.y1 - mouseYIp) && offsetY <= (graphPos.y2 + mouseYIp)) {
621
- if (this.options.horizontal) {
622
- ctx.beginPath();
623
- ctx.save();
624
- ctx.strokeStyle = color;
625
- ctx.lineWidth = 1;
626
- ctx.moveTo(graphPos.x1, offsetY + 0.5);
627
- ctx.lineTo(graphPos.x2, offsetY + 0.5);
628
- ctx.stroke();
629
- ctx.restore();
630
- ctx.closePath();
631
- } else {
632
- ctx.beginPath();
633
- ctx.save();
634
- ctx.strokeStyle = color;
635
- ctx.lineWidth = 1;
636
- ctx.moveTo(offsetX + 0.5, graphPos.y1);
637
- ctx.lineTo(offsetX + 0.5, graphPos.y2);
638
- ctx.stroke();
639
- ctx.restore();
640
- ctx.closePath();
641
- }
642
- }
643
- },
644
-
645
- /**
646
- * Clear tooltip canvas
647
- *
648
- * @returns {undefined}
649
- */
650
- tooltipClear() {
651
- this.clearRectRatio = (this.pixelRatio < 1) ? this.pixelRatio : 1;
652
-
653
- this.tooltipCtx.clearRect(0, 0, this.tooltipCanvas.width / this.clearRectRatio,
654
- this.tooltipCanvas.height / this.clearRectRatio);
655
-
656
- this.tooltipDOM.style.display = 'none';
657
- },
658
-
659
- /**
660
- * Order series list by groups
661
- * @param {array} sKeys series list that is hit by mouse cursor. (not all of series)
662
- *
663
- * @returns {array} ordered series list by groups
664
- */
665
- alignSeriesList(sKeys) {
666
- const groups = this.data.groups;
667
- const seriesList = this.seriesList;
668
- const result = [];
669
-
670
- groups.forEach((group) => {
671
- group.slice().reverse().forEach((sId) => {
672
- const series = seriesList[sId];
673
-
674
- if (series && series.showLegend && sKeys.includes(sId)) {
675
- result.push(sId);
676
- }
677
- });
678
- });
679
-
680
- Object.keys(seriesList).forEach((sId) => {
681
- const series = seriesList[sId];
682
-
683
- if (!series.isExistGrp && series.showLegend && sKeys.includes(sId)) {
684
- result.push(sId);
685
- }
686
- });
687
-
688
- return result;
689
- },
690
- };
691
-
692
- export default modules;
1
+ import { convertToPercent } from '@/common/utils';
2
+ import debounce from '@/common/utils.debounce';
3
+ import Canvas from '../helpers/helpers.canvas';
4
+ import Util from '../helpers/helpers.util';
5
+
6
+ const TITLE_HEIGHT = 30;
7
+ const TEXT_HEIGHT = 14;
8
+ const LINE_SPACING = 8;
9
+ const COLOR_MARGIN = 16;
10
+ const VALUE_MARGIN = 50;
11
+ const SCROLL_WIDTH = 17;
12
+ let fontStyle = 'normal normal lighter 14px Roboto';
13
+
14
+ const modules = {
15
+ /**
16
+ * Create tooltip DOM
17
+ *
18
+ * @returns {undefined}
19
+ */
20
+ createTooltipDOM() {
21
+ this.tooltipDOM = document.createElement('div');
22
+ this.tooltipDOM.className = 'ev-chart-tooltip';
23
+
24
+ this.tooltipHeaderDOM = document.createElement('div');
25
+ this.tooltipHeaderDOM.className = 'ev-chart-tooltip-header';
26
+
27
+ this.tooltipBodyDOM = document.createElement('div');
28
+ this.tooltipBodyDOM.className = 'ev-chart-tooltip-body';
29
+
30
+ this.tooltipCanvas = document.createElement('canvas');
31
+ this.tooltipCanvas.className = 'ev-chart-tooltip-canvas';
32
+ this.tooltipCtx = this.tooltipCanvas.getContext('2d');
33
+
34
+ this.tooltipDOM.style.display = 'none';
35
+ this.setFontFamily();
36
+
37
+ this.tooltipBodyDOM.appendChild(this.tooltipCanvas);
38
+ this.tooltipDOM.appendChild(this.tooltipHeaderDOM);
39
+ this.tooltipDOM.appendChild(this.tooltipBodyDOM);
40
+
41
+ document.body.appendChild(this.tooltipDOM);
42
+
43
+ if (this.options.tooltip.debouncedHide) {
44
+ this.hideTooltipDOM = debounce(() => {
45
+ this.tooltipDOM.style.display = 'none';
46
+ }, 200);
47
+ } else {
48
+ this.hideTooltipDOM = () => {
49
+ this.tooltipDOM.style.display = 'none';
50
+ };
51
+ }
52
+ },
53
+
54
+ /**
55
+ * set tooltip's font style
56
+ */
57
+ setFontFamily() {
58
+ const fontFamily = this.options?.tooltip?.fontFamily ?? 'Roboto';
59
+ fontStyle = `normal normal lighter 14px ${fontFamily}`;
60
+ this.tooltipHeaderDOM.style.fontFamily = fontFamily;
61
+ },
62
+
63
+ /**
64
+ * Set tooltip DOM's position and style
65
+ * @param {object} hitInfo value and mouse position touched
66
+ * @param {object} e mousemove callback
67
+ *
68
+ * @returns {object} tooltip layout information
69
+ */
70
+ setTooltipLayoutPosition(hitInfo, e) {
71
+ const ctx = this.tooltipCtx;
72
+ const mouseX = e.pageX;
73
+ const mouseY = e.pageY;
74
+ const items = hitInfo.items;
75
+ const [maxSeries, maxValue] = hitInfo.maxTip;
76
+ const seriesKeys = Object.keys(items);
77
+ const seriesLen = seriesKeys.length;
78
+ const boxPadding = { t: 8, b: 8, r: 20, l: 16 };
79
+ const opt = this.options.tooltip;
80
+
81
+
82
+ // calculate and decide width of canvas El(contentsWidth)
83
+ ctx.save();
84
+ ctx.font = fontStyle;
85
+ const isHorizontal = !!this.options.horizontal;
86
+ const label = isHorizontal ? items[hitInfo.hitId]?.data?.y : items[hitInfo.hitId]?.data?.x;
87
+ const tooltipValue = label?.length > maxSeries.length ? label : maxSeries;
88
+ const nw = Math.round(ctx.measureText(tooltipValue).width);
89
+ const vw = Math.round(ctx.measureText(maxValue).width);
90
+ const expectedContentsWidth = nw + vw + boxPadding.l + boxPadding.r
91
+ + COLOR_MARGIN + VALUE_MARGIN + SCROLL_WIDTH;
92
+ const contentsWidth = expectedContentsWidth > opt.maxWidth
93
+ ? opt.maxWidth
94
+ : expectedContentsWidth;
95
+
96
+
97
+ // Calculate height of canvas El(tooltip body El) with wrapped line count
98
+ let textLineCnt = opt.textOverflow === 'wrap' ? 0 : seriesLen;
99
+
100
+ if (opt.textOverflow === 'wrap') {
101
+ const seriesNameSpaceWidth = opt.maxWidth - (Math.round(ctx.measureText(maxValue).width)
102
+ + boxPadding.l + boxPadding.r + COLOR_MARGIN + VALUE_MARGIN + SCROLL_WIDTH);
103
+
104
+ // count wrap line
105
+ const seriesNames = Object.values(items).map(s => s.name);
106
+ seriesNames.forEach((name) => {
107
+ if (ctx.measureText(name).width > seriesNameSpaceWidth) {
108
+ let line = '';
109
+ for (let jx = 0; jx < name.length; jx++) {
110
+ const char = name[jx];
111
+ const temp = `${line}${char}`;
112
+ if (ctx.measureText(temp).width > seriesNameSpaceWidth) {
113
+ line = char;
114
+ textLineCnt += 1;
115
+ } else {
116
+ line = temp;
117
+ }
118
+ }
119
+ }
120
+ textLineCnt += 1;
121
+ });
122
+ ctx.restore();
123
+ }
124
+
125
+
126
+ // Calculate height of canvas El(tooltip body El) with useScrollbar, maxHeight option
127
+ const expectedContentsHeight = boxPadding.t
128
+ + (textLineCnt * TEXT_HEIGHT)
129
+ + (seriesLen * LINE_SPACING)
130
+ + boxPadding.b;
131
+
132
+ let contentsHeight;
133
+ if (opt.useScrollbar && expectedContentsHeight > opt.maxHeight) {
134
+ this.tooltipBodyDOM.style.overflowY = 'auto';
135
+ contentsHeight = opt.maxHeight;
136
+ } else {
137
+ this.tooltipBodyDOM.style.overflowY = 'hidden';
138
+ contentsHeight = expectedContentsHeight;
139
+ }
140
+
141
+ // set width / height to all DOM elements (canvas, tooltip(wrapper), header, body)
142
+ this.tooltipCanvas.width = contentsWidth * this.pixelRatio;
143
+ this.tooltipCanvas.height = expectedContentsHeight * this.pixelRatio;
144
+ this.tooltipCanvas.style.width = `${contentsWidth}px`;
145
+ this.tooltipCanvas.style.height = `${expectedContentsHeight}px`;
146
+ this.tooltipHeaderDOM.style.width = `${contentsWidth}px`;
147
+ this.tooltipHeaderDOM.style.height = opt.textOverflow === 'wrap' ? 'auto' : `${TITLE_HEIGHT}px`;
148
+ this.tooltipDOM.style.height = 'auto';
149
+ this.tooltipBodyDOM.style.height = `${contentsHeight + 6}px`;
150
+
151
+
152
+ // set tooltipDOM's positions
153
+ const bodyWidth = document.body.clientWidth;
154
+ const bodyHeight = document.body.clientHeight;
155
+ const distanceMouseAndTooltip = 20;
156
+ const maximumPosX = bodyWidth - contentsWidth - distanceMouseAndTooltip;
157
+ const maximumPosY = bodyHeight - (TITLE_HEIGHT + contentsHeight) - distanceMouseAndTooltip;
158
+ const expectedPosX = mouseX + distanceMouseAndTooltip;
159
+ const expectedPosY = mouseY + distanceMouseAndTooltip;
160
+ const reversedPosX = mouseX - contentsWidth - distanceMouseAndTooltip;
161
+ const reversedPosY = mouseY - (TITLE_HEIGHT + contentsHeight) - distanceMouseAndTooltip;
162
+ this.tooltipDOM.style.left = expectedPosX > maximumPosX
163
+ ? `${reversedPosX}px`
164
+ : `${expectedPosX}px`;
165
+ this.tooltipDOM.style.top = expectedPosY > maximumPosY
166
+ ? `${reversedPosY}px`
167
+ : `${expectedPosY}px`;
168
+ },
169
+
170
+ /**
171
+ * Draw tooltip canvas
172
+ * @param {object} hitInfo mousemove callback
173
+ * @param {object} context tooltip canvas context
174
+ *
175
+ * @returns {undefined}
176
+ */
177
+ drawTooltip(hitInfo, context) {
178
+ const ctx = context;
179
+ const items = hitInfo.items;
180
+ const sId = hitInfo.hitId;
181
+ const hitItem = items[sId].data;
182
+ const hitAxis = items[sId].axis;
183
+ const [, maxValue] = hitInfo.maxTip;
184
+ const seriesKeys = this.alignSeriesList(Object.keys(items));
185
+ const boxPadding = { t: 8, b: 8, r: 20, l: 16 };
186
+ const isHorizontal = this.options.horizontal;
187
+ const opt = this.options.tooltip;
188
+ const titleFormatter = opt.formatter?.title;
189
+
190
+ // draw tooltip Title(axis label) and add style class for wrap line about too much long label.
191
+ if (this.axesX.length && this.axesY.length) {
192
+ if (titleFormatter) {
193
+ this.tooltipHeaderDOM.textContent = titleFormatter({
194
+ x: hitItem.x,
195
+ y: hitItem.y,
196
+ });
197
+ } else {
198
+ this.tooltipHeaderDOM.textContent = this.options.horizontal
199
+ ? this.axesY[hitAxis.y].getLabelFormat(hitItem.y)
200
+ : this.axesX[hitAxis.x].getLabelFormat(hitItem.x);
201
+ }
202
+ }
203
+
204
+ if (opt.textOverflow) {
205
+ this.tooltipHeaderDOM.classList.add(`ev-chart-tooltip-header--${opt.textOverflow}`);
206
+ }
207
+
208
+ // draw tooltip contents (series, value combination)
209
+ let x = 2;
210
+ let y = 2;
211
+
212
+ x += Util.aliasPixel(x);
213
+ y += Util.aliasPixel(y);
214
+
215
+ ctx.save();
216
+ ctx.scale(this.pixelRatio, this.pixelRatio);
217
+
218
+ if (this.tooltipBodyDOM.style.overflowY === 'auto') {
219
+ boxPadding.r += SCROLL_WIDTH;
220
+ }
221
+
222
+ x += boxPadding.l;
223
+ y += boxPadding.t;
224
+
225
+ ctx.font = fontStyle;
226
+
227
+ const seriesList = [];
228
+ seriesKeys.forEach((seriesName) => {
229
+ seriesList.push({
230
+ data: items[seriesName].data,
231
+ color: items[seriesName].color,
232
+ name: items[seriesName].name,
233
+ });
234
+ });
235
+
236
+ if (opt.sortByValue) {
237
+ seriesList.sort((a, b) => {
238
+ let prev = a.data.o;
239
+ let next = b.data.o;
240
+
241
+ if (prev === null || prev === undefined) {
242
+ prev = isHorizontal ? a.data.x : a.data.y;
243
+ }
244
+
245
+ if (next === null || next === undefined) {
246
+ next = isHorizontal ? b.data.x : b.data.y;
247
+ }
248
+ return next - prev;
249
+ });
250
+ }
251
+
252
+ this.setTooltipDOMStyle(opt);
253
+
254
+ let textLineCnt = 1;
255
+ for (let ix = 0; ix < seriesList.length; ix++) {
256
+ const gdata = seriesList[ix].data;
257
+ const color = seriesList[ix].color;
258
+ const name = seriesList[ix].name;
259
+ const valueText = gdata.formatted;
260
+
261
+ let itemX = x + 4;
262
+ let itemY = y + (textLineCnt * TEXT_HEIGHT);
263
+ itemX += Util.aliasPixel(itemX);
264
+ itemY += Util.aliasPixel(itemY);
265
+
266
+ ctx.beginPath();
267
+
268
+ if (typeof color !== 'string') {
269
+ ctx.fillStyle = Canvas.createGradient(
270
+ ctx,
271
+ isHorizontal,
272
+ { x: itemX - 4, y: itemY, w: 12, h: -12 },
273
+ color,
274
+ );
275
+ } else {
276
+ ctx.fillStyle = color;
277
+ }
278
+
279
+ // 1. Draw series color
280
+ ctx.fillRect(itemX - 4, itemY - 12, 12, 12);
281
+ ctx.fillStyle = opt.fontColor;
282
+
283
+ // 2. Draw series name
284
+ ctx.textBaseline = 'Bottom';
285
+ const seriesNameSpaceWidth = opt.maxWidth - Math.round(ctx.measureText(maxValue).width)
286
+ - boxPadding.l - boxPadding.r - COLOR_MARGIN - VALUE_MARGIN;
287
+ const xPos = itemX + COLOR_MARGIN;
288
+ const yPos = itemY;
289
+
290
+ if (seriesNameSpaceWidth > ctx.measureText(name).width) { // draw normally
291
+ ctx.fillText(name, xPos, yPos);
292
+ } else if (opt.textOverflow === 'wrap') { // draw with wrap
293
+ let line = '';
294
+ let yPosWithWrap = yPos;
295
+
296
+ for (let jx = 0; jx < name.length; jx++) {
297
+ const char = name[jx];
298
+ const temp = `${line}${char}`;
299
+
300
+ if (ctx.measureText(temp).width > seriesNameSpaceWidth) {
301
+ ctx.fillText(line, xPos, yPosWithWrap);
302
+ line = char;
303
+ textLineCnt += 1;
304
+ yPosWithWrap += TEXT_HEIGHT;
305
+ } else {
306
+ line = temp;
307
+ }
308
+ }
309
+ ctx.fillText(line, xPos, yPosWithWrap);
310
+ } else { // draw with ellipsis
311
+ const shortSeriesName = Util.truncateLabelWithEllipsis(name, seriesNameSpaceWidth, ctx);
312
+ ctx.fillText(shortSeriesName, xPos, yPos);
313
+ }
314
+
315
+ ctx.save();
316
+
317
+ // 3. Draw value
318
+ ctx.textAlign = 'right';
319
+ ctx.fillText(valueText, this.tooltipDOM.offsetWidth - boxPadding.r, itemY);
320
+ ctx.restore();
321
+ ctx.closePath();
322
+
323
+ // 4. add lineSpacing
324
+ y += LINE_SPACING;
325
+ textLineCnt += 1;
326
+ }
327
+
328
+ ctx.restore();
329
+ },
330
+
331
+ /**
332
+ * Draw tooltip canvas for heatmap
333
+ * @param {object} hitInfo mousemove callback
334
+ * @param {object} context tooltip canvas context
335
+ *
336
+ * @returns {undefined}
337
+ */
338
+ drawToolTipForHeatMap(hitInfo, context) {
339
+ const ctx = context;
340
+ const items = hitInfo.items;
341
+ const sId = hitInfo.hitId;
342
+ const hitItem = items[sId].data;
343
+ const hitAxis = items[sId].axis;
344
+ const hitColor = items[sId].color;
345
+ const boxPadding = { t: 8, b: 8, r: 20, l: 16 };
346
+ const isHorizontal = this.options.horizontal;
347
+ const opt = this.options.tooltip;
348
+ const titleFormatter = opt.formatter?.title;
349
+ const series = Object.values(this.seriesList)[0];
350
+
351
+ let isShow = false;
352
+ const { colorState, isGradient } = series;
353
+ if (isGradient) {
354
+ const { min, max } = series.valueOpt;
355
+ const ratio = convertToPercent(hitItem.o - min, max - min);
356
+ const { start, end } = colorState[0];
357
+ isShow = (start <= ratio && ratio <= end) || hitItem.o === -1;
358
+ } else {
359
+ isShow = colorState.find(({ id }) => id === hitItem.cId)?.show;
360
+ }
361
+
362
+ if (!isShow) {
363
+ this.tooltipClear();
364
+ return;
365
+ }
366
+
367
+ // draw tooltip Title(axis label) and add style class for wrap line about too much long label.
368
+ if (this.axesX.length) {
369
+ if (titleFormatter) {
370
+ this.tooltipHeaderDOM.textContent = titleFormatter({
371
+ x: hitItem.x,
372
+ y: hitItem.y,
373
+ });
374
+ } else {
375
+ this.tooltipHeaderDOM.textContent = this.axesX[hitAxis.x].getLabelFormat(hitItem.x);
376
+ }
377
+ }
378
+
379
+ if (opt.textOverflow) {
380
+ this.tooltipHeaderDOM.classList.add(`ev-chart-tooltip-header--${opt.textOverflow}`);
381
+ }
382
+
383
+ this.setTooltipDOMStyle(opt);
384
+
385
+ // draw tooltip contents (series, value combination)
386
+ ctx.save();
387
+ ctx.scale(this.pixelRatio, this.pixelRatio);
388
+
389
+ if (this.tooltipBodyDOM.style.overflowY === 'auto') {
390
+ boxPadding.r += SCROLL_WIDTH;
391
+ }
392
+
393
+ const itemX = boxPadding.l + 2;
394
+ const itemY = boxPadding.t + TEXT_HEIGHT + 2;
395
+ const valueText = hitItem.formatted;
396
+
397
+ ctx.font = fontStyle;
398
+
399
+ ctx.beginPath();
400
+
401
+ if (typeof hitColor !== 'string') {
402
+ ctx.fillStyle = Canvas.createGradient(
403
+ ctx,
404
+ isHorizontal,
405
+ { x: itemX, y: itemY, w: 12, h: -12 },
406
+ hitColor,
407
+ );
408
+ } else {
409
+ ctx.fillStyle = hitColor;
410
+ }
411
+
412
+ // 1. Draw value color
413
+ ctx.fillRect(itemX - 4, itemY - 12, 12, 12);
414
+ ctx.fillStyle = opt.fontColor;
415
+
416
+ // 2. Draw value y names
417
+ ctx.textBaseline = 'Bottom';
418
+ if (this.axesY.length) {
419
+ ctx.fillText(this.axesY[hitAxis.y].getLabelFormat(hitItem.y), itemX + COLOR_MARGIN, itemY);
420
+ }
421
+
422
+ // 3. Draw value
423
+ ctx.textAlign = 'right';
424
+ ctx.fillText(valueText, this.tooltipDOM.offsetWidth - boxPadding.r, itemY);
425
+ ctx.closePath();
426
+ },
427
+
428
+ /**
429
+ *
430
+ * @param hitInfo
431
+ * @param context
432
+ */
433
+ drawTooltipForScatter(hitInfo, context) {
434
+ const ctx = context;
435
+ const items = hitInfo.items;
436
+ const [, maxValue] = hitInfo.maxTip;
437
+ const seriesKeys = this.alignSeriesList(Object.keys(items));
438
+ const boxPadding = { t: 8, b: 8, r: 8, l: 8 };
439
+ const opt = this.options.tooltip;
440
+
441
+ let x = 2;
442
+ let y = 2;
443
+
444
+ x += Util.aliasPixel(x);
445
+ y += Util.aliasPixel(y);
446
+
447
+ ctx.save();
448
+ ctx.scale(this.pixelRatio, this.pixelRatio);
449
+
450
+ if (this.tooltipBodyDOM.style.overflowY === 'auto') {
451
+ boxPadding.r += SCROLL_WIDTH;
452
+ }
453
+
454
+ x += boxPadding.l;
455
+ y += boxPadding.t;
456
+
457
+ ctx.font = fontStyle;
458
+
459
+ const seriesList = [];
460
+ seriesKeys.forEach((seriesName) => {
461
+ seriesList.push({
462
+ data: items[seriesName].data,
463
+ color: items[seriesName].color,
464
+ name: items[seriesName].name,
465
+ });
466
+ });
467
+
468
+ if (opt.sortByValue) {
469
+ seriesList.sort((a, b) => {
470
+ let prev = a.data.o;
471
+ let next = b.data.o;
472
+
473
+ if (prev === null || prev === undefined) {
474
+ prev = a.data.y;
475
+ }
476
+
477
+ if (next === null || next === undefined) {
478
+ next = b.data.y;
479
+ }
480
+
481
+ return next - prev;
482
+ });
483
+ }
484
+
485
+ this.setTooltipDOMStyle(opt);
486
+
487
+ let textLineCnt = 1;
488
+ for (let ix = 0; ix < seriesList.length; ix++) {
489
+ const gdata = seriesList[ix].data;
490
+ const color = seriesList[ix].color;
491
+ const name = seriesList[ix].name;
492
+ const valueText = gdata.formatted;
493
+
494
+ let itemX = x + 4;
495
+ let itemY = y + (textLineCnt * TEXT_HEIGHT);
496
+ itemX += Util.aliasPixel(itemX);
497
+ itemY += Util.aliasPixel(itemY);
498
+
499
+ ctx.beginPath();
500
+
501
+ if (typeof color !== 'string') {
502
+ ctx.fillStyle = Canvas.createGradient(
503
+ ctx,
504
+ false,
505
+ { x: itemX - 4, y: itemY, w: 12, h: -12 },
506
+ color,
507
+ );
508
+ } else {
509
+ ctx.fillStyle = color;
510
+ }
511
+
512
+ // 1. Draw series color
513
+ ctx.fillRect(itemX - 4, itemY - 12, 12, 12);
514
+ ctx.fillStyle = opt.fontColor;
515
+
516
+ // 2. Draw series name
517
+ ctx.textBaseline = 'Bottom';
518
+ const seriesNameSpaceWidth = opt.maxWidth - Math.round(ctx.measureText(maxValue).width)
519
+ - boxPadding.l - boxPadding.r - COLOR_MARGIN - VALUE_MARGIN;
520
+ const xPos = itemX + COLOR_MARGIN;
521
+ const yPos = itemY;
522
+
523
+ if (seriesNameSpaceWidth > ctx.measureText(name).width) { // draw normally
524
+ ctx.fillText(name, xPos, yPos);
525
+ } else if (opt.textOverflow === 'wrap') { // draw with wrap
526
+ let line = '';
527
+ let yPosWithWrap = yPos;
528
+
529
+ for (let jx = 0; jx < name.length; jx++) {
530
+ const char = name[jx];
531
+ const temp = `${line}${char}`;
532
+
533
+ if (ctx.measureText(temp).width > seriesNameSpaceWidth) {
534
+ ctx.fillText(line, xPos, yPosWithWrap);
535
+ line = char;
536
+ textLineCnt += 1;
537
+ yPosWithWrap += TEXT_HEIGHT;
538
+ } else {
539
+ line = temp;
540
+ }
541
+ }
542
+ ctx.fillText(line, xPos, yPosWithWrap);
543
+ } else { // draw with ellipsis
544
+ const shortSeriesName = Util.truncateLabelWithEllipsis(name, seriesNameSpaceWidth, ctx);
545
+ ctx.fillText(shortSeriesName, xPos, yPos);
546
+ }
547
+
548
+ ctx.save();
549
+
550
+ // 3. Draw value
551
+ ctx.textAlign = 'right';
552
+ ctx.fillText(valueText, this.tooltipDOM.offsetWidth - boxPadding.r, itemY);
553
+ ctx.restore();
554
+ ctx.closePath();
555
+
556
+ // 4. add lineSpacing
557
+ y += LINE_SPACING;
558
+ textLineCnt += 1;
559
+ }
560
+
561
+ ctx.restore();
562
+ },
563
+
564
+ /**
565
+ * set style properties on tooltip DOM
566
+ * @param tooltipOptions
567
+ */
568
+ setTooltipDOMStyle(tooltipOptions) {
569
+ this.tooltipDOM.style.overflowY = 'hidden';
570
+ this.tooltipDOM.style.backgroundColor = tooltipOptions.backgroundColor;
571
+ this.tooltipDOM.style.border = `1px solid ${tooltipOptions.borderColor}`;
572
+ this.tooltipDOM.style.color = tooltipOptions.fontColor;
573
+
574
+ if (tooltipOptions.useShadow) {
575
+ const shadowColor = `rgba(0, 0, 0, ${tooltipOptions.shadowOpacity})`;
576
+ this.tooltipDOM.style.boxShadow = `2px 2px 2px ${shadowColor}`;
577
+ }
578
+
579
+ this.tooltipDOM.style.display = 'block';
580
+ },
581
+
582
+ /**
583
+ * Draw graph item highlight
584
+ * @param {object} hitInfo mousemove callback
585
+ * @param {object} ctx overlayCanvas context
586
+ *
587
+ * @returns {undefined}
588
+ */
589
+ drawItemsHighlight(hitInfo, ctx) {
590
+ Object.keys(hitInfo.items).forEach((sId) => {
591
+ const series = this.seriesList[sId];
592
+ series.itemHighlight(hitInfo.items[sId], ctx);
593
+
594
+ if (Util.isDoughnutHole(series.type)) {
595
+ this.drawDoughnutHole(ctx);
596
+ }
597
+ });
598
+ },
599
+
600
+ /**
601
+ * Draw chart indicator with mousemove
602
+ * @param {object} offset mousemove callback
603
+ * @param {string} color indicator color
604
+ *
605
+ * @returns {undefined}
606
+ */
607
+ drawIndicator(offset, color) {
608
+ const ctx = this.overlayCtx;
609
+ const [offsetX, offsetY] = offset;
610
+ const graphPos = {
611
+ x1: this.chartRect.x1 + this.labelOffset.left,
612
+ x2: this.chartRect.x2 - this.labelOffset.right,
613
+ y1: this.chartRect.y1 + this.labelOffset.top,
614
+ y2: this.chartRect.y2 - this.labelOffset.bottom,
615
+ };
616
+ const mouseXIp = 1; // mouseInterpolation
617
+ const mouseYIp = 10;
618
+
619
+ if (offsetX >= (graphPos.x1 - mouseXIp) && offsetX <= (graphPos.x2 + mouseXIp)
620
+ && offsetY >= (graphPos.y1 - mouseYIp) && offsetY <= (graphPos.y2 + mouseYIp)) {
621
+ if (this.options.horizontal) {
622
+ ctx.beginPath();
623
+ ctx.save();
624
+ ctx.strokeStyle = color;
625
+ ctx.lineWidth = 1;
626
+ ctx.moveTo(graphPos.x1, offsetY + 0.5);
627
+ ctx.lineTo(graphPos.x2, offsetY + 0.5);
628
+ ctx.stroke();
629
+ ctx.restore();
630
+ ctx.closePath();
631
+ } else {
632
+ ctx.beginPath();
633
+ ctx.save();
634
+ ctx.strokeStyle = color;
635
+ ctx.lineWidth = 1;
636
+ ctx.moveTo(offsetX + 0.5, graphPos.y1);
637
+ ctx.lineTo(offsetX + 0.5, graphPos.y2);
638
+ ctx.stroke();
639
+ ctx.restore();
640
+ ctx.closePath();
641
+ }
642
+ }
643
+ },
644
+
645
+ /**
646
+ * Clear tooltip canvas
647
+ *
648
+ * @returns {undefined}
649
+ */
650
+ tooltipClear() {
651
+ this.clearRectRatio = (this.pixelRatio < 1) ? this.pixelRatio : 1;
652
+
653
+ this.tooltipCtx.clearRect(0, 0, this.tooltipCanvas.width / this.clearRectRatio,
654
+ this.tooltipCanvas.height / this.clearRectRatio);
655
+
656
+ this.tooltipDOM.style.display = 'none';
657
+ },
658
+
659
+ /**
660
+ * Order series list by groups
661
+ * @param {array} sKeys series list that is hit by mouse cursor. (not all of series)
662
+ *
663
+ * @returns {array} ordered series list by groups
664
+ */
665
+ alignSeriesList(sKeys) {
666
+ const groups = this.data.groups;
667
+ const seriesList = this.seriesList;
668
+ const result = [];
669
+
670
+ groups.forEach((group) => {
671
+ group.slice().reverse().forEach((sId) => {
672
+ const series = seriesList[sId];
673
+
674
+ if (series && series.showLegend && sKeys.includes(sId)) {
675
+ result.push(sId);
676
+ }
677
+ });
678
+ });
679
+
680
+ Object.keys(seriesList).forEach((sId) => {
681
+ const series = seriesList[sId];
682
+
683
+ if (!series.isExistGrp && series.showLegend && sKeys.includes(sId)) {
684
+ result.push(sId);
685
+ }
686
+ });
687
+
688
+ return result;
689
+ },
690
+ };
691
+
692
+ export default modules;