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.
- package/LICENSE +21 -21
- package/README.md +40 -40
- package/dist/evui.common.js +1907 -1832
- package/dist/evui.common.js.map +1 -1
- package/dist/evui.umd.js +1907 -1832
- package/dist/evui.umd.js.map +1 -1
- package/dist/evui.umd.min.js +1 -1
- package/dist/evui.umd.min.js.map +1 -1
- package/dist/img/{EVUI.7f3588fb.svg → EVUI.b82ee81a.svg} +292 -292
- package/dist/img/{icon_mysql.7ea26d5d.svg → icon_mysql.1085fdc9.svg} +78 -78
- package/dist/img/{icon_oracle.9009b108.svg → icon_oracle.0572d3ee.svg} +13 -13
- package/dist/img/{icon_postgresql.f8fffba9.svg → icon_postgresql.ee12bde8.svg} +58 -58
- package/package.json +61 -61
- package/src/common/emitter.js +20 -20
- package/src/common/utils.debounce.js +223 -223
- package/src/common/utils.js +134 -134
- package/src/common/utils.table.js +78 -78
- package/src/common/utils.throttle.js +83 -83
- package/src/common/utils.tree.js +18 -18
- package/src/components/button/Button.vue +198 -198
- package/src/components/button/index.js +7 -7
- package/src/components/buttonGroup/ButtonGroup.vue +11 -11
- package/src/components/buttonGroup/index.js +7 -7
- package/src/components/calendar/Calendar.vue +661 -661
- package/src/components/calendar/index.js +7 -7
- package/src/components/calendar/uses.js +1272 -1272
- package/src/components/chart/Chart.vue +189 -192
- package/src/components/chart/chart.core.js +870 -870
- package/src/components/chart/element/element.bar.js +524 -524
- package/src/components/chart/element/element.bar.time.js +156 -156
- package/src/components/chart/element/element.heatmap.js +533 -533
- package/src/components/chart/element/element.line.js +339 -339
- package/src/components/chart/element/element.pie.js +197 -197
- package/src/components/chart/element/element.scatter.js +184 -184
- package/src/components/chart/element/element.tip.js +550 -542
- package/src/components/chart/helpers/helpers.canvas.js +265 -265
- package/src/components/chart/helpers/helpers.constant.js +206 -206
- package/src/components/chart/helpers/helpers.util.js +346 -338
- package/src/components/chart/index.js +9 -9
- package/src/components/chart/model/index.js +4 -4
- package/src/components/chart/model/model.series.js +93 -93
- package/src/components/chart/model/model.store.js +977 -967
- package/src/components/chart/plugins/plugins.interaction.js +769 -769
- package/src/components/chart/plugins/plugins.legend.gradient.js +602 -602
- package/src/components/chart/plugins/plugins.legend.js +1155 -1151
- package/src/components/chart/plugins/plugins.pie.js +254 -254
- package/src/components/chart/plugins/plugins.title.js +56 -56
- package/src/components/chart/plugins/plugins.tooltip.js +692 -692
- package/src/components/chart/scale/scale.js +848 -848
- package/src/components/chart/scale/scale.linear.js +38 -38
- package/src/components/chart/scale/scale.logarithmic.js +128 -128
- package/src/components/chart/scale/scale.step.js +336 -336
- package/src/components/chart/scale/scale.time.category.js +277 -277
- package/src/components/chart/scale/scale.time.js +48 -48
- package/src/components/chart/style/chart.scss +312 -312
- package/src/components/chart/uses.js +264 -252
- package/src/components/checkbox/Checkbox.vue +200 -200
- package/src/components/checkbox/index.js +7 -7
- package/src/components/checkboxGroup/CheckboxGroup.vue +44 -44
- package/src/components/checkboxGroup/index.js +7 -7
- package/src/components/contextMenu/ContextMenu.vue +80 -80
- package/src/components/contextMenu/MenuList.vue +149 -149
- package/src/components/contextMenu/index.js +7 -7
- package/src/components/contextMenu/uses.js +203 -203
- package/src/components/datePicker/DatePicker.vue +437 -437
- package/src/components/datePicker/index.js +7 -7
- package/src/components/datePicker/uses.js +419 -419
- package/src/components/grid/Grid.vue +827 -827
- package/src/components/grid/grid.filter.window.vue +493 -493
- package/src/components/grid/grid.pagination.vue +75 -75
- package/src/components/grid/grid.summary.vue +265 -265
- package/src/components/grid/grid.toolbar.vue +26 -26
- package/src/components/grid/index.js +11 -11
- package/src/components/grid/style/grid.scss +263 -263
- package/src/components/grid/uses.js +1002 -1007
- package/src/components/icon/Icon.vue +49 -49
- package/src/components/icon/index.js +8 -8
- package/src/components/inputNumber/InputNumber.vue +212 -212
- package/src/components/inputNumber/index.js +7 -7
- package/src/components/inputNumber/uses.js +217 -217
- package/src/components/loading/Loading.vue +125 -125
- package/src/components/loading/index.js +7 -7
- package/src/components/menu/Menu.vue +68 -68
- package/src/components/menu/MenuItem.vue +187 -187
- package/src/components/menu/index.js +7 -7
- package/src/components/message/Message.vue +223 -223
- package/src/components/message/index.js +31 -31
- package/src/components/messageBox/MessageBox.vue +358 -358
- package/src/components/messageBox/index.js +22 -22
- package/src/components/notification/Notification.vue +316 -316
- package/src/components/notification/index.js +49 -49
- package/src/components/pagination/Pagination.vue +271 -271
- package/src/components/pagination/index.js +7 -7
- package/src/components/pagination/pageButton.vue +30 -30
- package/src/components/progress/Progress.vue +139 -139
- package/src/components/progress/index.js +7 -7
- package/src/components/radio/Radio.vue +159 -159
- package/src/components/radio/index.js +7 -7
- package/src/components/radioGroup/RadioGroup.vue +41 -41
- package/src/components/radioGroup/index.js +7 -7
- package/src/components/scheduler/Scheduler.vue +149 -149
- package/src/components/scheduler/index.js +7 -7
- package/src/components/scheduler/uses.js +183 -183
- package/src/components/select/Select.vue +440 -440
- package/src/components/select/index.js +7 -7
- package/src/components/select/uses.js +270 -270
- package/src/components/slider/Slider.vue +505 -505
- package/src/components/slider/index.js +7 -7
- package/src/components/slider/uses.js +390 -390
- package/src/components/tabPanel/TabPanel.vue +74 -74
- package/src/components/tabPanel/index.js +7 -7
- package/src/components/tabs/Tabs.vue +517 -517
- package/src/components/tabs/index.js +7 -7
- package/src/components/textField/TextField.vue +375 -375
- package/src/components/textField/index.js +7 -7
- package/src/components/timePicker/TimePicker.vue +352 -352
- package/src/components/timePicker/index.js +7 -7
- package/src/components/toggle/Toggle.vue +115 -115
- package/src/components/toggle/index.js +7 -7
- package/src/components/tree/Tree.vue +313 -313
- package/src/components/tree/TreeNode.vue +293 -293
- package/src/components/tree/index.js +7 -7
- package/src/components/treeGrid/TreeGrid.vue +758 -758
- package/src/components/treeGrid/TreeGridNode.vue +275 -275
- package/src/components/treeGrid/index.js +9 -9
- package/src/components/treeGrid/style/treeGrid.scss +261 -261
- package/src/components/treeGrid/treeGrid.toolbar.vue +26 -26
- package/src/components/treeGrid/uses.js +867 -867
- package/src/components/window/Window.vue +329 -329
- package/src/components/window/index.js +7 -7
- package/src/components/window/uses.js +899 -899
- package/src/directives/clickoutside.js +90 -90
- package/src/main.js +116 -116
- package/src/style/components/input.scss +108 -108
- package/src/style/functions.scss +3 -3
- package/src/style/index.scss +6 -6
- package/src/style/lib/fonts/EVUI.svg +292 -292
- package/src/style/lib/icon.css +888 -888
- package/src/style/mixins.scss +94 -94
- package/src/style/themes.scss +67 -67
- 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;
|