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,848 +1,848 @@
1
- import Canvas from '@/components/chart/helpers/helpers.canvas';
2
- import { defaultsDeep } from 'lodash-es';
3
- import {
4
- AXIS_OPTION,
5
- AXIS_UNITS,
6
- PLOT_LINE_OPTION,
7
- PLOT_LINE_LABEL_OPTION,
8
- PLOT_BAND_OPTION,
9
- } from '../helpers/helpers.constant';
10
- import Util from '../helpers/helpers.util';
11
-
12
- class Scale {
13
- constructor(type, opt, ctx, options) {
14
- const merged = defaultsDeep({}, opt, AXIS_OPTION);
15
- Object.keys(merged).forEach((key) => {
16
- this[key] = merged[key];
17
- });
18
-
19
- this.type = type;
20
- this.ctx = ctx;
21
- this.units = AXIS_UNITS[this.type];
22
- this.options = options;
23
-
24
- if (!this.position) {
25
- this.position = type === 'x' ? 'bottom' : 'left';
26
- }
27
- }
28
-
29
- /**
30
- * Calculate axis's min/max label steps
31
- * @param {string} type axis direction ('x', 'y')
32
- * @param {object} chartRect chart size information
33
- * @param {object} labelOffset chart label offset information
34
- * @param {number} tickSize label's size
35
- *
36
- * @returns {object} label range
37
- */
38
- calculateLabelRange(type, chartRect, labelOffset, tickSize) {
39
- let chartSize;
40
- let axisOffset;
41
- let bufferedTickSize;
42
-
43
- if (type === 'x') {
44
- chartSize = chartRect.chartWidth;
45
- bufferedTickSize = Math.floor(tickSize * 1.1);
46
- axisOffset = [labelOffset.left, labelOffset.right];
47
- } else {
48
- chartSize = chartRect.chartHeight;
49
- axisOffset = [labelOffset.top, labelOffset.bottom];
50
- bufferedTickSize = tickSize + (Math.floor(chartSize * 0.1));
51
- }
52
-
53
- const drawRange = chartSize - (axisOffset[0] + axisOffset[1]);
54
- const minSteps = 1;
55
- const maxSteps = Math.max(Math.floor(drawRange / bufferedTickSize) - 1, 1);
56
-
57
- return {
58
- min: minSteps,
59
- max: maxSteps,
60
- };
61
- }
62
-
63
- /**
64
- * Calculate min/max value, label and size information for axis
65
- * @param {object} minMax min/max information
66
- *
67
- * @returns {object} min/max value and label
68
- */
69
- calculateScaleRange(minMax) {
70
- let maxValue;
71
- let minValue;
72
-
73
- if (this.range?.length === 2) {
74
- maxValue = this.range[1];
75
- minValue = this.range[0];
76
- } else {
77
- maxValue = minMax.max;
78
- minValue = minMax.min;
79
- }
80
-
81
- if (this.autoScaleRatio) {
82
- maxValue = Math.ceil(maxValue * (this.autoScaleRatio + 1));
83
- }
84
-
85
- if (this.startToZero) {
86
- minValue = 0;
87
- }
88
-
89
- if (maxValue === minValue) {
90
- maxValue += 1;
91
- }
92
-
93
- const minLabel = this.getLabelFormat(minValue);
94
- const maxLabel = this.getLabelFormat(maxValue);
95
-
96
- return {
97
- min: minValue,
98
- max: maxValue,
99
- minLabel,
100
- maxLabel,
101
- size: Util.calcTextSize(maxLabel, Util.getLabelStyle(this.labelStyle)),
102
- };
103
- }
104
-
105
- /**
106
- * With range information, calculate how many labels in axis
107
- * @param {object} range min/max information
108
- *
109
- * @returns {object} steps, interval, min/max graph value
110
- */
111
- calculateSteps(range) {
112
- const { maxValue, minValue } = range;
113
- let { maxSteps } = range;
114
-
115
- let interval = this.getInterval(range);
116
- let increase = minValue;
117
- let numberOfSteps;
118
-
119
- while (increase < maxValue) {
120
- increase += interval;
121
- }
122
-
123
- const graphMax = increase > maxValue ? maxValue : increase;
124
- const graphMin = minValue;
125
- const graphRange = graphMax - graphMin;
126
-
127
- numberOfSteps = Math.round(graphRange / interval);
128
-
129
- if (maxValue === 1) {
130
- if (!this.decimalPoint) {
131
- interval = 1;
132
- numberOfSteps = 1;
133
- maxSteps = 1;
134
- } else if (maxSteps > 2) {
135
- interval = 0.2;
136
- numberOfSteps = 5;
137
- maxSteps = 5;
138
- } else {
139
- interval = 0.5;
140
- numberOfSteps = 2;
141
- maxSteps = 2;
142
- }
143
- }
144
-
145
- while (numberOfSteps > maxSteps) {
146
- interval *= 2;
147
- numberOfSteps = Math.round(graphRange / interval);
148
- interval = Math.ceil(graphRange / numberOfSteps);
149
- }
150
-
151
- if (graphMax - graphMin > (numberOfSteps * interval)) {
152
- interval = Math.ceil((graphMax - graphMin) / numberOfSteps);
153
- }
154
-
155
- return {
156
- steps: numberOfSteps,
157
- interval,
158
- graphMin,
159
- graphMax,
160
- };
161
- }
162
-
163
- /**
164
- * Draw Axis Title
165
- *
166
- * @param {object} chartRect min/max information
167
- * @param {object} labelOffset label offset information
168
- *
169
- * @returns {undefined}
170
- */
171
- drawAxisTitle(chartRect, labelOffset) {
172
- const titleOpt = this.title;
173
-
174
- if (!titleOpt?.use || isNaN(titleOpt.fontSize)) {
175
- return;
176
- }
177
-
178
- const ctx = this.ctx;
179
- ctx.save();
180
- ctx.font = Util.getLabelStyle(titleOpt);
181
- ctx.fillStyle = titleOpt.color;
182
- ctx.textAlign = titleOpt.textAlign;
183
-
184
- const axisLinePosition = {
185
- xLeft: chartRect.x1 + labelOffset.left,
186
- xRight: chartRect.x2 - labelOffset.right,
187
- yTop: chartRect.y1,
188
- };
189
-
190
- let titleXPos;
191
- let titleYPos;
192
-
193
- const margin = 10;
194
- if (this.type === 'x') {
195
- titleXPos = axisLinePosition.xRight;
196
- titleYPos = chartRect.y2 + titleOpt.fontSize + margin;
197
- } else {
198
- titleYPos = axisLinePosition.yTop - titleOpt.fontSize - margin;
199
- titleXPos = axisLinePosition.xLeft;
200
- }
201
-
202
- if (titleXPos > 0 && titleYPos > 0) {
203
- ctx.fillText(titleOpt.text, titleXPos, titleYPos);
204
- }
205
-
206
- ctx.restore();
207
- }
208
-
209
- /**
210
- * Draw axis
211
- * @param {object} chartRect min/max information
212
- * @param {object} labelOffset label offset information
213
- * @param {object} stepInfo label steps information
214
- *
215
- * @returns {undefined}
216
- */
217
- draw(chartRect, labelOffset, stepInfo, hitInfo, selectLabelInfo) {
218
- const ctx = this.ctx;
219
- const options = this.options;
220
- const aPos = {
221
- x1: chartRect.x1 + labelOffset.left,
222
- x2: chartRect.x2 - labelOffset.right,
223
- y1: chartRect.y1 + labelOffset.top,
224
- y2: chartRect.y2 - labelOffset.bottom,
225
- };
226
-
227
- const steps = stepInfo.steps;
228
- const axisMin = stepInfo.graphMin;
229
- const axisMax = stepInfo.graphMax;
230
- const stepValue = stepInfo.interval;
231
-
232
- const startPoint = aPos[this.units.rectStart];
233
- const endPoint = aPos[this.units.rectEnd];
234
- const offsetPoint = aPos[this.units.rectOffset(this.position)];
235
- const offsetCounterPoint = aPos[this.units.rectOffsetCounter(this.position)];
236
-
237
- let aliasPixel;
238
-
239
- this.drawAxisTitle(chartRect, labelOffset);
240
-
241
- // label font 설정
242
- ctx.font = Util.getLabelStyle(this.labelStyle);
243
- ctx.fillStyle = this.labelStyle.color;
244
-
245
- if (this.type === 'x') {
246
- ctx.textAlign = 'center';
247
- ctx.textBaseline = this.position === 'top' ? 'bottom' : 'top';
248
- } else {
249
- ctx.textAlign = this.position === 'left' ? 'right' : 'left';
250
- ctx.textBaseline = 'middle';
251
- }
252
-
253
- if (this.showAxis) {
254
- ctx.lineWidth = 2;
255
- aliasPixel = Util.aliasPixel(ctx.lineWidth);
256
-
257
- ctx.beginPath();
258
- ctx.strokeStyle = this.axisLineColor;
259
-
260
- if (this.type === 'x') {
261
- ctx.moveTo(startPoint, offsetPoint + aliasPixel);
262
- ctx.lineTo(endPoint, offsetPoint + aliasPixel);
263
- } else {
264
- ctx.moveTo(offsetPoint + aliasPixel + 1, startPoint);
265
- ctx.lineTo(offsetPoint + aliasPixel + 1, endPoint);
266
- }
267
- ctx.stroke();
268
- ctx.closePath();
269
- }
270
-
271
- if (steps === 0 || axisMin === null) {
272
- return;
273
- }
274
-
275
- if (this.labelStyle?.show) {
276
- const labelGap = (endPoint - startPoint) / steps;
277
- const ticks = [];
278
- let labelCenter = null;
279
- let linePosition = null;
280
-
281
- ctx.strokeStyle = this.gridLineColor;
282
- ctx.lineWidth = 1;
283
- aliasPixel = Util.aliasPixel(ctx.lineWidth);
284
-
285
- let labelText;
286
- for (let ix = 0; ix <= steps; ix++) {
287
- ctx.beginPath();
288
- ticks[ix] = axisMin + (ix * stepValue);
289
-
290
- labelCenter = Math.round(startPoint + (labelGap * ix));
291
- linePosition = labelCenter + aliasPixel;
292
- labelText = this.getLabelFormat(Math.min(axisMax, ticks[ix]));
293
-
294
- const isBlurredLabel = this.options?.selectLabel?.use
295
- && this.options?.selectLabel?.useLabelOpacity
296
- && (this.options.horizontal === (this.type === 'y'))
297
- && selectLabelInfo?.dataIndex?.length
298
- && !selectLabelInfo?.label
299
- .map(t => this.getLabelFormat(Math.min(axisMax, t))).includes(labelText);
300
-
301
- const labelColor = this.labelStyle.color;
302
- let defaultOpacity = 1;
303
-
304
- if (Util.getColorStringType(labelColor) === 'RGBA') {
305
- defaultOpacity = Util.getOpacity(labelColor);
306
- }
307
-
308
- ctx.fillStyle = Util.colorStringToRgba(labelColor, isBlurredLabel ? 0.1 : defaultOpacity);
309
-
310
- let labelPoint;
311
-
312
- if (this.type === 'x') {
313
- labelPoint = this.position === 'top' ? offsetPoint - 10 : offsetPoint + 10;
314
- ctx.fillText(labelText, labelCenter, labelPoint);
315
- if (!isBlurredLabel
316
- && options?.selectItem?.showLabelTip
317
- && hitInfo?.label
318
- && !this.options?.horizontal) {
319
- const selectedLabel = this.getLabelFormat(
320
- Math.min(axisMax, hitInfo.label + (0 * stepValue)),
321
- );
322
- if (selectedLabel === labelText) {
323
- const height = Math.round(ctx.measureText(this.labelStyle?.fontSize).width);
324
- Util.showLabelTip({
325
- ctx: this.ctx,
326
- width: Math.round(ctx.measureText(selectedLabel).width) + 10,
327
- height,
328
- x: labelCenter,
329
- y: labelPoint + (height - 2),
330
- borderRadius: 2,
331
- arrowSize: 3,
332
- text: labelText,
333
- backgroundColor: options?.selectItem?.labelTipStyle?.backgroundColor,
334
- textColor: options?.selectItem?.labelTipStyle?.textColor,
335
- });
336
- }
337
- }
338
- if (ix !== 0 && this.showGrid) {
339
- ctx.moveTo(linePosition, offsetPoint);
340
- ctx.lineTo(linePosition, offsetCounterPoint);
341
- }
342
- } else {
343
- labelPoint = this.position === 'left' ? offsetPoint - 10 : offsetPoint + 10;
344
- ctx.fillText(labelText, labelPoint, labelCenter);
345
-
346
- if (ix === steps) {
347
- linePosition += 1;
348
- }
349
-
350
- if (ix !== 0 && this.showGrid) {
351
- ctx.moveTo(offsetPoint, linePosition);
352
- ctx.lineTo(offsetCounterPoint, linePosition);
353
- }
354
- }
355
-
356
- ctx.stroke();
357
- ctx.closePath();
358
- }
359
- }
360
-
361
- // Draw plot lines and plot bands
362
- if (this.plotBands?.length || this.plotLines?.length) {
363
- const xArea = chartRect.chartWidth - (labelOffset.left + labelOffset.right);
364
- const yArea = chartRect.chartHeight - (labelOffset.top + labelOffset.bottom);
365
- const padding = aliasPixel + 1;
366
- const minX = aPos.x1 + padding;
367
- const maxX = aPos.x2;
368
- const minY = aPos.y1 + padding; // top
369
- const maxY = aPos.y2; // bottom
370
-
371
- this.plotBands?.forEach((plotBand) => {
372
- if (!plotBand.from && !plotBand.to) {
373
- return;
374
- }
375
-
376
- const mergedPlotBandOpt = defaultsDeep({}, plotBand, PLOT_BAND_OPTION);
377
- const { from, to, label: labelOpt } = mergedPlotBandOpt;
378
-
379
- this.setPlotBandStyle(mergedPlotBandOpt);
380
-
381
- let fromPos;
382
- let toPos;
383
- if (this.type === 'x') {
384
- fromPos = Canvas.calculateX(from ?? minX, axisMin, axisMax, xArea, minX);
385
- toPos = Canvas.calculateX(to ?? maxX, axisMin, axisMax, xArea, minX);
386
-
387
- if (fromPos === null || toPos === null) {
388
- return;
389
- }
390
-
391
- this.drawXPlotBand(fromPos, toPos, minX, maxX, minY, maxY);
392
- } else {
393
- fromPos = Canvas.calculateY(from ?? axisMin, axisMin, axisMax, yArea, maxY);
394
- toPos = Canvas.calculateY(to ?? axisMax, axisMin, axisMax, yArea, maxY);
395
-
396
- if (fromPos === null || toPos === null) {
397
- return;
398
- }
399
-
400
- this.drawYPlotBand(fromPos, toPos, minX, maxX, minY, maxY);
401
- }
402
-
403
- if (labelOpt.show) {
404
- const labelOptions = this.getNormalizedLabelOptions(chartRect, labelOpt);
405
- const textXY = this.getPlotBandLabelPosition(fromPos, toPos, labelOptions, maxX, minY);
406
- this.drawPlotLabel(labelOptions, textXY);
407
- }
408
-
409
- ctx.restore();
410
- });
411
-
412
- this.plotLines?.forEach((plotLine) => {
413
- if (!plotLine.value) {
414
- return;
415
- }
416
-
417
- const mergedPlotLineOpt = defaultsDeep({}, plotLine, PLOT_LINE_OPTION);
418
- const { value, label: labelOpt } = mergedPlotLineOpt;
419
-
420
- this.setPlotLineStyle(mergedPlotLineOpt);
421
-
422
- let dataPos;
423
- if (this.type === 'x') {
424
- dataPos = Canvas.calculateX(value, axisMin, axisMax, xArea, minX);
425
-
426
- if (dataPos === null) {
427
- return;
428
- }
429
-
430
- this.drawXPlotLine(dataPos, minX, maxX, minY, maxY);
431
- } else {
432
- dataPos = Canvas.calculateY(value, axisMin, axisMax, yArea, maxY);
433
-
434
- if (dataPos === null) {
435
- return;
436
- }
437
-
438
- this.drawYPlotLine(dataPos, minX, maxX, minY, maxY);
439
- }
440
-
441
- if (labelOpt.show) {
442
- const labelOptions = this.getNormalizedLabelOptions(chartRect, labelOpt);
443
- const textXY = this.getPlotLineLabelPosition(dataPos, labelOptions, maxX, minY);
444
- this.drawPlotLabel(labelOptions, textXY);
445
- }
446
-
447
- ctx.restore();
448
- });
449
- }
450
- }
451
-
452
- /**
453
- * Set plot line style
454
- * @param {object} plotLine plotLine Options
455
- *
456
- * @returns {undefined}
457
- */
458
- setPlotLineStyle(plotLine) {
459
- const ctx = this.ctx;
460
- const { color, lineWidth } = plotLine;
461
-
462
- ctx.beginPath();
463
- ctx.save();
464
- ctx.lineWidth = lineWidth;
465
- ctx.strokeStyle = color;
466
-
467
- if (plotLine.segments) {
468
- ctx.setLineDash(plotLine.segments);
469
- }
470
- }
471
-
472
- /**
473
- * Set plot band style
474
- * @param {object} plotBand plotBand Options
475
- *
476
- * @returns {undefined}
477
- */
478
- setPlotBandStyle(plotBand) {
479
- const ctx = this.ctx;
480
- const { color } = plotBand;
481
-
482
- ctx.beginPath();
483
- ctx.save();
484
- ctx.fillStyle = color;
485
- }
486
-
487
- /**
488
- * Draw X Plot band
489
- * @param {number} fromDataX From data's X Position
490
- * @param {number} toDataX To data's X Position
491
- * @param {number} minX Min X Position
492
- * @param {number} maxX Max X Position
493
- * @param {number} minY Min Y Position
494
- * @param {number} maxY Max Y Position
495
- *
496
- * @returns {undefined}
497
- */
498
- drawXPlotBand(fromDataX, toDataX, minX, maxX, minY, maxY) {
499
- const ctx = this.ctx;
500
-
501
- const checkValidPosition = x => x || x > minX || x < maxX;
502
-
503
- if (!checkValidPosition(fromDataX) || !checkValidPosition(toDataX)) {
504
- ctx.closePath();
505
- ctx.restore();
506
- return;
507
- }
508
-
509
- ctx.moveTo(fromDataX, minY);
510
- ctx.lineTo(fromDataX, maxY);
511
- ctx.lineTo(toDataX, maxY);
512
- ctx.lineTo(toDataX, minY);
513
- ctx.lineTo(fromDataX, minY);
514
-
515
- ctx.stroke();
516
- ctx.fill();
517
- ctx.restore();
518
- ctx.closePath();
519
- }
520
-
521
- /**
522
- * Draw X Plot line
523
- * @param {object} dataX Data's X Position
524
- * @param {number} minX Min X Position
525
- * @param {number} maxX Max X Position
526
- * @param {number} minY Min Y Position
527
- * @param {number} maxY Max Y Position
528
- *
529
- * @returns {undefined}
530
- */
531
- drawXPlotLine(dataX, minX, maxX, minY, maxY) {
532
- const ctx = this.ctx;
533
-
534
- if (!dataX || dataX < minX || dataX > maxX) {
535
- ctx.closePath();
536
- ctx.restore();
537
- return;
538
- }
539
-
540
- ctx.moveTo(dataX, maxY);
541
- ctx.lineTo(dataX, minY);
542
-
543
- ctx.stroke();
544
- ctx.restore();
545
- ctx.closePath();
546
- }
547
-
548
- /**
549
- * Draw Y Plot line
550
- * @param {object} dataY Data's Y Position
551
- * @param {number} minX Min X Position
552
- * @param {number} maxX Max X Position
553
- * @param {number} minY Min Y Position
554
- * @param {number} maxY Max Y Position
555
- *
556
- * @returns {undefined}
557
- */
558
- drawYPlotLine(dataY, minX, maxX, minY, maxY) {
559
- const ctx = this.ctx;
560
-
561
- if (!dataY || dataY > maxY || dataY < minY) {
562
- ctx.closePath();
563
- ctx.restore();
564
- return;
565
- }
566
-
567
- ctx.moveTo(minX, dataY);
568
- ctx.lineTo(maxX, dataY);
569
-
570
- ctx.stroke();
571
- ctx.restore();
572
- ctx.closePath();
573
- }
574
-
575
- /**
576
- * Draw Y Plot band
577
- * @param {number} fromDataY From data's Y Position (bottom)
578
- * @param {number} toDataY To data's Y Position (top)
579
- * @param {number} minX Min X Position
580
- * @param {number} maxX Max X Position
581
- * @param {number} minY Min Y Position
582
- * @param {number} maxY Max Y Position
583
- *
584
- * @returns {undefined}
585
- */
586
- drawYPlotBand(fromDataY, toDataY, minX, maxX, minY, maxY) {
587
- const ctx = this.ctx;
588
-
589
- const checkValidPosition = y => y || y > minY || y < maxY;
590
-
591
- if (!checkValidPosition(fromDataY) || !checkValidPosition(toDataY)) {
592
- ctx.closePath();
593
- ctx.restore();
594
- return;
595
- }
596
-
597
- ctx.moveTo(minX, fromDataY);
598
- ctx.lineTo(minX, toDataY);
599
- ctx.lineTo(maxX, toDataY);
600
- ctx.lineTo(maxX, fromDataY);
601
- ctx.lineTo(minX, fromDataY);
602
-
603
- ctx.fill();
604
- ctx.restore();
605
- ctx.closePath();
606
- }
607
-
608
- /**
609
- * get normalized options for plot label
610
- * @param {object} chartRect chartRect
611
- * @param {object} labelOpt plotLine Options
612
- *
613
- * @returns {object}
614
- */
615
- getNormalizedLabelOptions(chartRect, labelOpt) {
616
- const mergedLabelOpt = defaultsDeep({}, labelOpt, PLOT_LINE_LABEL_OPTION);
617
-
618
- const ctx = this.ctx;
619
- const { maxWidth } = mergedLabelOpt;
620
- const fontSize = mergedLabelOpt.fontSize > 20 ? 20 : mergedLabelOpt.fontSize;
621
- let label = mergedLabelOpt.text;
622
- let labelWidth = maxWidth ?? ctx.measureText(label).width;
623
-
624
- const plotLabelAreaWidth = this.type === 'y'
625
- ? chartRect.width - chartRect.chartWidth
626
- : maxWidth ?? chartRect.width;
627
-
628
- if (plotLabelAreaWidth < ctx.measureText(label).width && mergedLabelOpt.textOverflow === 'ellipsis') {
629
- label = Util.truncateLabelWithEllipsis(mergedLabelOpt.text, plotLabelAreaWidth, ctx);
630
- labelWidth = ctx.measureText(label).width;
631
- }
632
-
633
- return {
634
- label,
635
- fontSize,
636
- labelWidth,
637
- labelBoxPadding: fontSize / 4,
638
- labelHalfWidth: labelWidth / 2,
639
- labelHalfHeight: fontSize / 2,
640
- ...mergedLabelOpt,
641
- };
642
- }
643
-
644
- /**
645
- * Calculate position of plot band's label
646
- * @param {object} fromPos from data position
647
- * @param {object} toPos to data position
648
- * @param {object} labelOpt label options
649
- * @param {object} maxX max x position
650
- * @param {object} minY min y position
651
- *
652
- * @returns {object}
653
- */
654
- getPlotBandLabelPosition(fromPos, toPos, labelOpt, maxX, minY) {
655
- const {
656
- fontSize,
657
- labelWidth,
658
- labelHalfWidth,
659
- labelHalfHeight,
660
- labelBoxPadding,
661
- textAlign,
662
- verticalAlign,
663
- } = labelOpt;
664
-
665
- if (fontSize <= 0) {
666
- return { textX: 0, textY: 0 };
667
- }
668
-
669
- let textX;
670
- let textY;
671
-
672
- if (this.type === 'x') {
673
- textY = minY - labelBoxPadding - fontSize;
674
-
675
- switch (textAlign) {
676
- case 'left':
677
- textX = fromPos + labelHalfWidth + labelBoxPadding;
678
- break;
679
-
680
- case 'right':
681
- textX = toPos - labelHalfWidth - labelBoxPadding;
682
- break;
683
-
684
- case 'center':
685
- default:
686
- textX = ((toPos - fromPos) / 2) + fromPos;
687
- break;
688
- }
689
- } else {
690
- textX = maxX + labelWidth + labelBoxPadding;
691
-
692
- switch (verticalAlign) {
693
- case 'top':
694
- textY = toPos + labelHalfHeight + labelBoxPadding;
695
- break;
696
-
697
- case 'bottom':
698
- textY = fromPos - labelHalfHeight - labelBoxPadding;
699
- break;
700
-
701
- case 'middle':
702
- default:
703
- textY = ((fromPos - toPos) / 2) + toPos;
704
- break;
705
- }
706
- }
707
-
708
- return { textX, textY };
709
- }
710
-
711
- /**
712
- * Calculate position of plot line's label
713
- * @param {object} dataPos data position
714
- * @param {object} labelOpt label options
715
- * @param {object} maxX max x position
716
- * @param {object} minY min y position
717
- *
718
- * @returns {undefined}
719
- */
720
- getPlotLineLabelPosition(dataPos, labelOpt, maxX, minY) {
721
- const {
722
- fontSize,
723
- labelWidth,
724
- labelHalfWidth,
725
- labelHalfHeight,
726
- labelBoxPadding,
727
- } = labelOpt;
728
-
729
- if (fontSize <= 0) {
730
- return { textX: 0, textY: 0 };
731
- }
732
-
733
- let textX;
734
- let textY;
735
-
736
- if (this.type === 'x') {
737
- textY = minY - labelBoxPadding - fontSize;
738
-
739
- switch (labelOpt.textAlign) {
740
- case 'left':
741
- textX = dataPos - labelHalfWidth - labelBoxPadding;
742
- break;
743
-
744
- case 'right':
745
- textX = dataPos + labelHalfWidth + labelBoxPadding;
746
- break;
747
-
748
- case 'center':
749
- default:
750
- textX = dataPos;
751
- break;
752
- }
753
- } else {
754
- textX = maxX + labelWidth + labelBoxPadding;
755
-
756
- switch (labelOpt.verticalAlign) {
757
- case 'top':
758
- textY = dataPos - labelHalfHeight - labelBoxPadding;
759
- break;
760
-
761
- case 'bottom':
762
- textY = dataPos + labelHalfHeight + labelBoxPadding;
763
- break;
764
-
765
- case 'middle':
766
- default:
767
- textY = dataPos;
768
- break;
769
- }
770
- }
771
-
772
- return { textX, textY };
773
- }
774
-
775
- /**
776
- * Calculate Values for drawing label
777
- * @param {object} labelOptions plot line Label Options
778
- * @param {object} positions x, y Position
779
- *
780
- * @returns {undefined}
781
- */
782
- drawPlotLabel(labelOptions, positions) {
783
- if (!positions) {
784
- return;
785
- }
786
-
787
- const { textX, textY } = positions;
788
- const {
789
- label,
790
- fontSize,
791
- fontColor,
792
- fillColor,
793
- lineColor,
794
- lineWidth,
795
- labelBoxPadding,
796
- labelWidth,
797
- labelHalfWidth,
798
- labelHalfHeight,
799
- } = labelOptions;
800
-
801
- if (fontSize <= 0) {
802
- return;
803
- }
804
-
805
- const ctx = this.ctx;
806
- ctx.save();
807
- ctx.beginPath();
808
- ctx.font = Util.getLabelStyle(labelOptions);
809
-
810
- let top = 0;
811
- let bottom = 0;
812
- let left = 0;
813
- let right = 0;
814
-
815
- if (this.type === 'x') {
816
- top = textY - labelBoxPadding;
817
- bottom = textY + fontSize;
818
- left = textX - labelHalfWidth - labelBoxPadding;
819
- right = textX + labelHalfWidth + labelBoxPadding;
820
- } else {
821
- top = textY - labelHalfHeight - labelBoxPadding;
822
- bottom = textY + labelHalfHeight + labelBoxPadding;
823
- left = textX - labelWidth;
824
- right = textX + labelBoxPadding;
825
- }
826
-
827
- ctx.fillStyle = fillColor;
828
- ctx.strokeStyle = lineColor;
829
- ctx.lineWidth = lineWidth;
830
- ctx.moveTo(left, bottom);
831
- ctx.lineTo(left, top);
832
- ctx.lineTo(right, top);
833
- ctx.lineTo(right, bottom);
834
- ctx.lineTo(left, bottom);
835
- ctx.fill();
836
-
837
- if (lineWidth > 0) {
838
- ctx.stroke();
839
- }
840
-
841
- ctx.fillStyle = fontColor;
842
-
843
- ctx.fillText(label, textX, textY);
844
- ctx.closePath();
845
- }
846
- }
847
-
848
- export default Scale;
1
+ import Canvas from '@/components/chart/helpers/helpers.canvas';
2
+ import { defaultsDeep } from 'lodash-es';
3
+ import {
4
+ AXIS_OPTION,
5
+ AXIS_UNITS,
6
+ PLOT_LINE_OPTION,
7
+ PLOT_LINE_LABEL_OPTION,
8
+ PLOT_BAND_OPTION,
9
+ } from '../helpers/helpers.constant';
10
+ import Util from '../helpers/helpers.util';
11
+
12
+ class Scale {
13
+ constructor(type, opt, ctx, options) {
14
+ const merged = defaultsDeep({}, opt, AXIS_OPTION);
15
+ Object.keys(merged).forEach((key) => {
16
+ this[key] = merged[key];
17
+ });
18
+
19
+ this.type = type;
20
+ this.ctx = ctx;
21
+ this.units = AXIS_UNITS[this.type];
22
+ this.options = options;
23
+
24
+ if (!this.position) {
25
+ this.position = type === 'x' ? 'bottom' : 'left';
26
+ }
27
+ }
28
+
29
+ /**
30
+ * Calculate axis's min/max label steps
31
+ * @param {string} type axis direction ('x', 'y')
32
+ * @param {object} chartRect chart size information
33
+ * @param {object} labelOffset chart label offset information
34
+ * @param {number} tickSize label's size
35
+ *
36
+ * @returns {object} label range
37
+ */
38
+ calculateLabelRange(type, chartRect, labelOffset, tickSize) {
39
+ let chartSize;
40
+ let axisOffset;
41
+ let bufferedTickSize;
42
+
43
+ if (type === 'x') {
44
+ chartSize = chartRect.chartWidth;
45
+ bufferedTickSize = Math.floor(tickSize * 1.1);
46
+ axisOffset = [labelOffset.left, labelOffset.right];
47
+ } else {
48
+ chartSize = chartRect.chartHeight;
49
+ axisOffset = [labelOffset.top, labelOffset.bottom];
50
+ bufferedTickSize = tickSize + (Math.floor(chartSize * 0.1));
51
+ }
52
+
53
+ const drawRange = chartSize - (axisOffset[0] + axisOffset[1]);
54
+ const minSteps = 1;
55
+ const maxSteps = Math.max(Math.floor(drawRange / bufferedTickSize) - 1, 1);
56
+
57
+ return {
58
+ min: minSteps,
59
+ max: maxSteps,
60
+ };
61
+ }
62
+
63
+ /**
64
+ * Calculate min/max value, label and size information for axis
65
+ * @param {object} minMax min/max information
66
+ *
67
+ * @returns {object} min/max value and label
68
+ */
69
+ calculateScaleRange(minMax) {
70
+ let maxValue;
71
+ let minValue;
72
+
73
+ if (this.range?.length === 2) {
74
+ maxValue = this.range[1];
75
+ minValue = this.range[0];
76
+ } else {
77
+ maxValue = minMax.max;
78
+ minValue = minMax.min;
79
+ }
80
+
81
+ if (this.autoScaleRatio) {
82
+ maxValue = Math.ceil(maxValue * (this.autoScaleRatio + 1));
83
+ }
84
+
85
+ if (this.startToZero) {
86
+ minValue = 0;
87
+ }
88
+
89
+ if (maxValue === minValue) {
90
+ maxValue += 1;
91
+ }
92
+
93
+ const minLabel = this.getLabelFormat(minValue);
94
+ const maxLabel = this.getLabelFormat(maxValue);
95
+
96
+ return {
97
+ min: minValue,
98
+ max: maxValue,
99
+ minLabel,
100
+ maxLabel,
101
+ size: Util.calcTextSize(maxLabel, Util.getLabelStyle(this.labelStyle)),
102
+ };
103
+ }
104
+
105
+ /**
106
+ * With range information, calculate how many labels in axis
107
+ * @param {object} range min/max information
108
+ *
109
+ * @returns {object} steps, interval, min/max graph value
110
+ */
111
+ calculateSteps(range) {
112
+ const { maxValue, minValue } = range;
113
+ let { maxSteps } = range;
114
+
115
+ let interval = this.getInterval(range);
116
+ let increase = minValue;
117
+ let numberOfSteps;
118
+
119
+ while (increase < maxValue) {
120
+ increase += interval;
121
+ }
122
+
123
+ const graphMax = increase;
124
+ const graphMin = minValue;
125
+ const graphRange = graphMax - graphMin;
126
+
127
+ numberOfSteps = Math.round(graphRange / interval);
128
+
129
+ if (maxValue === 1) {
130
+ if (!this.decimalPoint) {
131
+ interval = 1;
132
+ numberOfSteps = 1;
133
+ maxSteps = 1;
134
+ } else if (maxSteps > 2) {
135
+ interval = 0.2;
136
+ numberOfSteps = 5;
137
+ maxSteps = 5;
138
+ } else {
139
+ interval = 0.5;
140
+ numberOfSteps = 2;
141
+ maxSteps = 2;
142
+ }
143
+ }
144
+
145
+ while (numberOfSteps > maxSteps) {
146
+ interval *= 2;
147
+ numberOfSteps = Math.round(graphRange / interval);
148
+ interval = Math.ceil(graphRange / numberOfSteps);
149
+ }
150
+
151
+ if (graphMax - graphMin > (numberOfSteps * interval)) {
152
+ interval = Math.ceil((graphMax - graphMin) / numberOfSteps);
153
+ }
154
+
155
+ return {
156
+ steps: numberOfSteps,
157
+ interval,
158
+ graphMin,
159
+ graphMax,
160
+ };
161
+ }
162
+
163
+ /**
164
+ * Draw Axis Title
165
+ *
166
+ * @param {object} chartRect min/max information
167
+ * @param {object} labelOffset label offset information
168
+ *
169
+ * @returns {undefined}
170
+ */
171
+ drawAxisTitle(chartRect, labelOffset) {
172
+ const titleOpt = this.title;
173
+
174
+ if (!titleOpt?.use || isNaN(titleOpt.fontSize)) {
175
+ return;
176
+ }
177
+
178
+ const ctx = this.ctx;
179
+ ctx.save();
180
+ ctx.font = Util.getLabelStyle(titleOpt);
181
+ ctx.fillStyle = titleOpt.color;
182
+ ctx.textAlign = titleOpt.textAlign;
183
+
184
+ const axisLinePosition = {
185
+ xLeft: chartRect.x1 + labelOffset.left,
186
+ xRight: chartRect.x2 - labelOffset.right,
187
+ yTop: chartRect.y1,
188
+ };
189
+
190
+ let titleXPos;
191
+ let titleYPos;
192
+
193
+ const margin = 10;
194
+ if (this.type === 'x') {
195
+ titleXPos = axisLinePosition.xRight;
196
+ titleYPos = chartRect.y2 + titleOpt.fontSize + margin;
197
+ } else {
198
+ titleYPos = axisLinePosition.yTop - titleOpt.fontSize - margin;
199
+ titleXPos = axisLinePosition.xLeft;
200
+ }
201
+
202
+ if (titleXPos > 0 && titleYPos > 0) {
203
+ ctx.fillText(titleOpt.text, titleXPos, titleYPos);
204
+ }
205
+
206
+ ctx.restore();
207
+ }
208
+
209
+ /**
210
+ * Draw axis
211
+ * @param {object} chartRect min/max information
212
+ * @param {object} labelOffset label offset information
213
+ * @param {object} stepInfo label steps information
214
+ *
215
+ * @returns {undefined}
216
+ */
217
+ draw(chartRect, labelOffset, stepInfo, hitInfo, selectLabelInfo) {
218
+ const ctx = this.ctx;
219
+ const options = this.options;
220
+ const aPos = {
221
+ x1: chartRect.x1 + labelOffset.left,
222
+ x2: chartRect.x2 - labelOffset.right,
223
+ y1: chartRect.y1 + labelOffset.top,
224
+ y2: chartRect.y2 - labelOffset.bottom,
225
+ };
226
+
227
+ const steps = stepInfo.steps;
228
+ const axisMin = stepInfo.graphMin;
229
+ const axisMax = stepInfo.graphMax;
230
+ const stepValue = stepInfo.interval;
231
+
232
+ const startPoint = aPos[this.units.rectStart];
233
+ const endPoint = aPos[this.units.rectEnd];
234
+ const offsetPoint = aPos[this.units.rectOffset(this.position)];
235
+ const offsetCounterPoint = aPos[this.units.rectOffsetCounter(this.position)];
236
+
237
+ let aliasPixel;
238
+
239
+ this.drawAxisTitle(chartRect, labelOffset);
240
+
241
+ // label font 설정
242
+ ctx.font = Util.getLabelStyle(this.labelStyle);
243
+ ctx.fillStyle = this.labelStyle.color;
244
+
245
+ if (this.type === 'x') {
246
+ ctx.textAlign = 'center';
247
+ ctx.textBaseline = this.position === 'top' ? 'bottom' : 'top';
248
+ } else {
249
+ ctx.textAlign = this.position === 'left' ? 'right' : 'left';
250
+ ctx.textBaseline = 'middle';
251
+ }
252
+
253
+ if (this.showAxis) {
254
+ ctx.lineWidth = 2;
255
+ aliasPixel = Util.aliasPixel(ctx.lineWidth);
256
+
257
+ ctx.beginPath();
258
+ ctx.strokeStyle = this.axisLineColor;
259
+
260
+ if (this.type === 'x') {
261
+ ctx.moveTo(startPoint, offsetPoint + aliasPixel);
262
+ ctx.lineTo(endPoint, offsetPoint + aliasPixel);
263
+ } else {
264
+ ctx.moveTo(offsetPoint + aliasPixel + 1, startPoint);
265
+ ctx.lineTo(offsetPoint + aliasPixel + 1, endPoint);
266
+ }
267
+ ctx.stroke();
268
+ ctx.closePath();
269
+ }
270
+
271
+ if (steps === 0 || axisMin === null) {
272
+ return;
273
+ }
274
+
275
+ if (this.labelStyle?.show) {
276
+ const labelGap = (endPoint - startPoint) / steps;
277
+ const ticks = [];
278
+ let labelCenter = null;
279
+ let linePosition = null;
280
+
281
+ ctx.strokeStyle = this.gridLineColor;
282
+ ctx.lineWidth = 1;
283
+ aliasPixel = Util.aliasPixel(ctx.lineWidth);
284
+
285
+ let labelText;
286
+ for (let ix = 0; ix <= steps; ix++) {
287
+ ctx.beginPath();
288
+ ticks[ix] = axisMin + (ix * stepValue);
289
+
290
+ labelCenter = Math.round(startPoint + (labelGap * ix));
291
+ linePosition = labelCenter + aliasPixel;
292
+ labelText = this.getLabelFormat(Math.min(axisMax, ticks[ix]));
293
+
294
+ const isBlurredLabel = this.options?.selectLabel?.use
295
+ && this.options?.selectLabel?.useLabelOpacity
296
+ && (this.options.horizontal === (this.type === 'y'))
297
+ && selectLabelInfo?.dataIndex?.length
298
+ && !selectLabelInfo?.label
299
+ .map(t => this.getLabelFormat(Math.min(axisMax, t))).includes(labelText);
300
+
301
+ const labelColor = this.labelStyle.color;
302
+ let defaultOpacity = 1;
303
+
304
+ if (Util.getColorStringType(labelColor) === 'RGBA') {
305
+ defaultOpacity = Util.getOpacity(labelColor);
306
+ }
307
+
308
+ ctx.fillStyle = Util.colorStringToRgba(labelColor, isBlurredLabel ? 0.1 : defaultOpacity);
309
+
310
+ let labelPoint;
311
+
312
+ if (this.type === 'x') {
313
+ labelPoint = this.position === 'top' ? offsetPoint - 10 : offsetPoint + 10;
314
+ ctx.fillText(labelText, labelCenter, labelPoint);
315
+ if (!isBlurredLabel
316
+ && options?.selectItem?.showLabelTip
317
+ && hitInfo?.label
318
+ && !this.options?.horizontal) {
319
+ const selectedLabel = this.getLabelFormat(
320
+ Math.min(axisMax, hitInfo.label + (0 * stepValue)),
321
+ );
322
+ if (selectedLabel === labelText) {
323
+ const height = Math.round(ctx.measureText(this.labelStyle?.fontSize).width);
324
+ Util.showLabelTip({
325
+ ctx: this.ctx,
326
+ width: Math.round(ctx.measureText(selectedLabel).width) + 10,
327
+ height,
328
+ x: labelCenter,
329
+ y: labelPoint + (height - 2),
330
+ borderRadius: 2,
331
+ arrowSize: 3,
332
+ text: labelText,
333
+ backgroundColor: options?.selectItem?.labelTipStyle?.backgroundColor,
334
+ textColor: options?.selectItem?.labelTipStyle?.textColor,
335
+ });
336
+ }
337
+ }
338
+ if (ix !== 0 && this.showGrid) {
339
+ ctx.moveTo(linePosition, offsetPoint);
340
+ ctx.lineTo(linePosition, offsetCounterPoint);
341
+ }
342
+ } else {
343
+ labelPoint = this.position === 'left' ? offsetPoint - 10 : offsetPoint + 10;
344
+ ctx.fillText(labelText, labelPoint, labelCenter);
345
+
346
+ if (ix === steps) {
347
+ linePosition += 1;
348
+ }
349
+
350
+ if (ix !== 0 && this.showGrid) {
351
+ ctx.moveTo(offsetPoint, linePosition);
352
+ ctx.lineTo(offsetCounterPoint, linePosition);
353
+ }
354
+ }
355
+
356
+ ctx.stroke();
357
+ ctx.closePath();
358
+ }
359
+ }
360
+
361
+ // Draw plot lines and plot bands
362
+ if (this.plotBands?.length || this.plotLines?.length) {
363
+ const xArea = chartRect.chartWidth - (labelOffset.left + labelOffset.right);
364
+ const yArea = chartRect.chartHeight - (labelOffset.top + labelOffset.bottom);
365
+ const padding = aliasPixel + 1;
366
+ const minX = aPos.x1 + padding;
367
+ const maxX = aPos.x2;
368
+ const minY = aPos.y1 + padding; // top
369
+ const maxY = aPos.y2; // bottom
370
+
371
+ this.plotBands?.forEach((plotBand) => {
372
+ if (!plotBand.from && !plotBand.to) {
373
+ return;
374
+ }
375
+
376
+ const mergedPlotBandOpt = defaultsDeep({}, plotBand, PLOT_BAND_OPTION);
377
+ const { from, to, label: labelOpt } = mergedPlotBandOpt;
378
+
379
+ this.setPlotBandStyle(mergedPlotBandOpt);
380
+
381
+ let fromPos;
382
+ let toPos;
383
+ if (this.type === 'x') {
384
+ fromPos = Canvas.calculateX(from ?? minX, axisMin, axisMax, xArea, minX);
385
+ toPos = Canvas.calculateX(to ?? maxX, axisMin, axisMax, xArea, minX);
386
+
387
+ if (fromPos === null || toPos === null) {
388
+ return;
389
+ }
390
+
391
+ this.drawXPlotBand(fromPos, toPos, minX, maxX, minY, maxY);
392
+ } else {
393
+ fromPos = Canvas.calculateY(from ?? axisMin, axisMin, axisMax, yArea, maxY);
394
+ toPos = Canvas.calculateY(to ?? axisMax, axisMin, axisMax, yArea, maxY);
395
+
396
+ if (fromPos === null || toPos === null) {
397
+ return;
398
+ }
399
+
400
+ this.drawYPlotBand(fromPos, toPos, minX, maxX, minY, maxY);
401
+ }
402
+
403
+ if (labelOpt.show) {
404
+ const labelOptions = this.getNormalizedLabelOptions(chartRect, labelOpt);
405
+ const textXY = this.getPlotBandLabelPosition(fromPos, toPos, labelOptions, maxX, minY);
406
+ this.drawPlotLabel(labelOptions, textXY);
407
+ }
408
+
409
+ ctx.restore();
410
+ });
411
+
412
+ this.plotLines?.forEach((plotLine) => {
413
+ if (!plotLine.value) {
414
+ return;
415
+ }
416
+
417
+ const mergedPlotLineOpt = defaultsDeep({}, plotLine, PLOT_LINE_OPTION);
418
+ const { value, label: labelOpt } = mergedPlotLineOpt;
419
+
420
+ this.setPlotLineStyle(mergedPlotLineOpt);
421
+
422
+ let dataPos;
423
+ if (this.type === 'x') {
424
+ dataPos = Canvas.calculateX(value, axisMin, axisMax, xArea, minX);
425
+
426
+ if (dataPos === null) {
427
+ return;
428
+ }
429
+
430
+ this.drawXPlotLine(dataPos, minX, maxX, minY, maxY);
431
+ } else {
432
+ dataPos = Canvas.calculateY(value, axisMin, axisMax, yArea, maxY);
433
+
434
+ if (dataPos === null) {
435
+ return;
436
+ }
437
+
438
+ this.drawYPlotLine(dataPos, minX, maxX, minY, maxY);
439
+ }
440
+
441
+ if (labelOpt.show) {
442
+ const labelOptions = this.getNormalizedLabelOptions(chartRect, labelOpt);
443
+ const textXY = this.getPlotLineLabelPosition(dataPos, labelOptions, maxX, minY);
444
+ this.drawPlotLabel(labelOptions, textXY);
445
+ }
446
+
447
+ ctx.restore();
448
+ });
449
+ }
450
+ }
451
+
452
+ /**
453
+ * Set plot line style
454
+ * @param {object} plotLine plotLine Options
455
+ *
456
+ * @returns {undefined}
457
+ */
458
+ setPlotLineStyle(plotLine) {
459
+ const ctx = this.ctx;
460
+ const { color, lineWidth } = plotLine;
461
+
462
+ ctx.beginPath();
463
+ ctx.save();
464
+ ctx.lineWidth = lineWidth;
465
+ ctx.strokeStyle = color;
466
+
467
+ if (plotLine.segments) {
468
+ ctx.setLineDash(plotLine.segments);
469
+ }
470
+ }
471
+
472
+ /**
473
+ * Set plot band style
474
+ * @param {object} plotBand plotBand Options
475
+ *
476
+ * @returns {undefined}
477
+ */
478
+ setPlotBandStyle(plotBand) {
479
+ const ctx = this.ctx;
480
+ const { color } = plotBand;
481
+
482
+ ctx.beginPath();
483
+ ctx.save();
484
+ ctx.fillStyle = color;
485
+ }
486
+
487
+ /**
488
+ * Draw X Plot band
489
+ * @param {number} fromDataX From data's X Position
490
+ * @param {number} toDataX To data's X Position
491
+ * @param {number} minX Min X Position
492
+ * @param {number} maxX Max X Position
493
+ * @param {number} minY Min Y Position
494
+ * @param {number} maxY Max Y Position
495
+ *
496
+ * @returns {undefined}
497
+ */
498
+ drawXPlotBand(fromDataX, toDataX, minX, maxX, minY, maxY) {
499
+ const ctx = this.ctx;
500
+
501
+ const checkValidPosition = x => x || x > minX || x < maxX;
502
+
503
+ if (!checkValidPosition(fromDataX) || !checkValidPosition(toDataX)) {
504
+ ctx.closePath();
505
+ ctx.restore();
506
+ return;
507
+ }
508
+
509
+ ctx.moveTo(fromDataX, minY);
510
+ ctx.lineTo(fromDataX, maxY);
511
+ ctx.lineTo(toDataX, maxY);
512
+ ctx.lineTo(toDataX, minY);
513
+ ctx.lineTo(fromDataX, minY);
514
+
515
+ ctx.stroke();
516
+ ctx.fill();
517
+ ctx.restore();
518
+ ctx.closePath();
519
+ }
520
+
521
+ /**
522
+ * Draw X Plot line
523
+ * @param {object} dataX Data's X Position
524
+ * @param {number} minX Min X Position
525
+ * @param {number} maxX Max X Position
526
+ * @param {number} minY Min Y Position
527
+ * @param {number} maxY Max Y Position
528
+ *
529
+ * @returns {undefined}
530
+ */
531
+ drawXPlotLine(dataX, minX, maxX, minY, maxY) {
532
+ const ctx = this.ctx;
533
+
534
+ if (!dataX || dataX < minX || dataX > maxX) {
535
+ ctx.closePath();
536
+ ctx.restore();
537
+ return;
538
+ }
539
+
540
+ ctx.moveTo(dataX, maxY);
541
+ ctx.lineTo(dataX, minY);
542
+
543
+ ctx.stroke();
544
+ ctx.restore();
545
+ ctx.closePath();
546
+ }
547
+
548
+ /**
549
+ * Draw Y Plot line
550
+ * @param {object} dataY Data's Y Position
551
+ * @param {number} minX Min X Position
552
+ * @param {number} maxX Max X Position
553
+ * @param {number} minY Min Y Position
554
+ * @param {number} maxY Max Y Position
555
+ *
556
+ * @returns {undefined}
557
+ */
558
+ drawYPlotLine(dataY, minX, maxX, minY, maxY) {
559
+ const ctx = this.ctx;
560
+
561
+ if (!dataY || dataY > maxY || dataY < minY) {
562
+ ctx.closePath();
563
+ ctx.restore();
564
+ return;
565
+ }
566
+
567
+ ctx.moveTo(minX, dataY);
568
+ ctx.lineTo(maxX, dataY);
569
+
570
+ ctx.stroke();
571
+ ctx.restore();
572
+ ctx.closePath();
573
+ }
574
+
575
+ /**
576
+ * Draw Y Plot band
577
+ * @param {number} fromDataY From data's Y Position (bottom)
578
+ * @param {number} toDataY To data's Y Position (top)
579
+ * @param {number} minX Min X Position
580
+ * @param {number} maxX Max X Position
581
+ * @param {number} minY Min Y Position
582
+ * @param {number} maxY Max Y Position
583
+ *
584
+ * @returns {undefined}
585
+ */
586
+ drawYPlotBand(fromDataY, toDataY, minX, maxX, minY, maxY) {
587
+ const ctx = this.ctx;
588
+
589
+ const checkValidPosition = y => y || y > minY || y < maxY;
590
+
591
+ if (!checkValidPosition(fromDataY) || !checkValidPosition(toDataY)) {
592
+ ctx.closePath();
593
+ ctx.restore();
594
+ return;
595
+ }
596
+
597
+ ctx.moveTo(minX, fromDataY);
598
+ ctx.lineTo(minX, toDataY);
599
+ ctx.lineTo(maxX, toDataY);
600
+ ctx.lineTo(maxX, fromDataY);
601
+ ctx.lineTo(minX, fromDataY);
602
+
603
+ ctx.fill();
604
+ ctx.restore();
605
+ ctx.closePath();
606
+ }
607
+
608
+ /**
609
+ * get normalized options for plot label
610
+ * @param {object} chartRect chartRect
611
+ * @param {object} labelOpt plotLine Options
612
+ *
613
+ * @returns {object}
614
+ */
615
+ getNormalizedLabelOptions(chartRect, labelOpt) {
616
+ const mergedLabelOpt = defaultsDeep({}, labelOpt, PLOT_LINE_LABEL_OPTION);
617
+
618
+ const ctx = this.ctx;
619
+ const { maxWidth } = mergedLabelOpt;
620
+ const fontSize = mergedLabelOpt.fontSize > 20 ? 20 : mergedLabelOpt.fontSize;
621
+ let label = mergedLabelOpt.text;
622
+ let labelWidth = maxWidth ?? ctx.measureText(label).width;
623
+
624
+ const plotLabelAreaWidth = this.type === 'y'
625
+ ? chartRect.width - chartRect.chartWidth
626
+ : maxWidth ?? chartRect.width;
627
+
628
+ if (plotLabelAreaWidth < ctx.measureText(label).width && mergedLabelOpt.textOverflow === 'ellipsis') {
629
+ label = Util.truncateLabelWithEllipsis(mergedLabelOpt.text, plotLabelAreaWidth, ctx);
630
+ labelWidth = ctx.measureText(label).width;
631
+ }
632
+
633
+ return {
634
+ label,
635
+ fontSize,
636
+ labelWidth,
637
+ labelBoxPadding: fontSize / 4,
638
+ labelHalfWidth: labelWidth / 2,
639
+ labelHalfHeight: fontSize / 2,
640
+ ...mergedLabelOpt,
641
+ };
642
+ }
643
+
644
+ /**
645
+ * Calculate position of plot band's label
646
+ * @param {object} fromPos from data position
647
+ * @param {object} toPos to data position
648
+ * @param {object} labelOpt label options
649
+ * @param {object} maxX max x position
650
+ * @param {object} minY min y position
651
+ *
652
+ * @returns {object}
653
+ */
654
+ getPlotBandLabelPosition(fromPos, toPos, labelOpt, maxX, minY) {
655
+ const {
656
+ fontSize,
657
+ labelWidth,
658
+ labelHalfWidth,
659
+ labelHalfHeight,
660
+ labelBoxPadding,
661
+ textAlign,
662
+ verticalAlign,
663
+ } = labelOpt;
664
+
665
+ if (fontSize <= 0) {
666
+ return { textX: 0, textY: 0 };
667
+ }
668
+
669
+ let textX;
670
+ let textY;
671
+
672
+ if (this.type === 'x') {
673
+ textY = minY - labelBoxPadding - fontSize;
674
+
675
+ switch (textAlign) {
676
+ case 'left':
677
+ textX = fromPos + labelHalfWidth + labelBoxPadding;
678
+ break;
679
+
680
+ case 'right':
681
+ textX = toPos - labelHalfWidth - labelBoxPadding;
682
+ break;
683
+
684
+ case 'center':
685
+ default:
686
+ textX = ((toPos - fromPos) / 2) + fromPos;
687
+ break;
688
+ }
689
+ } else {
690
+ textX = maxX + labelWidth + labelBoxPadding;
691
+
692
+ switch (verticalAlign) {
693
+ case 'top':
694
+ textY = toPos + labelHalfHeight + labelBoxPadding;
695
+ break;
696
+
697
+ case 'bottom':
698
+ textY = fromPos - labelHalfHeight - labelBoxPadding;
699
+ break;
700
+
701
+ case 'middle':
702
+ default:
703
+ textY = ((fromPos - toPos) / 2) + toPos;
704
+ break;
705
+ }
706
+ }
707
+
708
+ return { textX, textY };
709
+ }
710
+
711
+ /**
712
+ * Calculate position of plot line's label
713
+ * @param {object} dataPos data position
714
+ * @param {object} labelOpt label options
715
+ * @param {object} maxX max x position
716
+ * @param {object} minY min y position
717
+ *
718
+ * @returns {undefined}
719
+ */
720
+ getPlotLineLabelPosition(dataPos, labelOpt, maxX, minY) {
721
+ const {
722
+ fontSize,
723
+ labelWidth,
724
+ labelHalfWidth,
725
+ labelHalfHeight,
726
+ labelBoxPadding,
727
+ } = labelOpt;
728
+
729
+ if (fontSize <= 0) {
730
+ return { textX: 0, textY: 0 };
731
+ }
732
+
733
+ let textX;
734
+ let textY;
735
+
736
+ if (this.type === 'x') {
737
+ textY = minY - labelBoxPadding - fontSize;
738
+
739
+ switch (labelOpt.textAlign) {
740
+ case 'left':
741
+ textX = dataPos - labelHalfWidth - labelBoxPadding;
742
+ break;
743
+
744
+ case 'right':
745
+ textX = dataPos + labelHalfWidth + labelBoxPadding;
746
+ break;
747
+
748
+ case 'center':
749
+ default:
750
+ textX = dataPos;
751
+ break;
752
+ }
753
+ } else {
754
+ textX = maxX + labelWidth + labelBoxPadding;
755
+
756
+ switch (labelOpt.verticalAlign) {
757
+ case 'top':
758
+ textY = dataPos - labelHalfHeight - labelBoxPadding;
759
+ break;
760
+
761
+ case 'bottom':
762
+ textY = dataPos + labelHalfHeight + labelBoxPadding;
763
+ break;
764
+
765
+ case 'middle':
766
+ default:
767
+ textY = dataPos;
768
+ break;
769
+ }
770
+ }
771
+
772
+ return { textX, textY };
773
+ }
774
+
775
+ /**
776
+ * Calculate Values for drawing label
777
+ * @param {object} labelOptions plot line Label Options
778
+ * @param {object} positions x, y Position
779
+ *
780
+ * @returns {undefined}
781
+ */
782
+ drawPlotLabel(labelOptions, positions) {
783
+ if (!positions) {
784
+ return;
785
+ }
786
+
787
+ const { textX, textY } = positions;
788
+ const {
789
+ label,
790
+ fontSize,
791
+ fontColor,
792
+ fillColor,
793
+ lineColor,
794
+ lineWidth,
795
+ labelBoxPadding,
796
+ labelWidth,
797
+ labelHalfWidth,
798
+ labelHalfHeight,
799
+ } = labelOptions;
800
+
801
+ if (fontSize <= 0) {
802
+ return;
803
+ }
804
+
805
+ const ctx = this.ctx;
806
+ ctx.save();
807
+ ctx.beginPath();
808
+ ctx.font = Util.getLabelStyle(labelOptions);
809
+
810
+ let top = 0;
811
+ let bottom = 0;
812
+ let left = 0;
813
+ let right = 0;
814
+
815
+ if (this.type === 'x') {
816
+ top = textY - labelBoxPadding;
817
+ bottom = textY + fontSize;
818
+ left = textX - labelHalfWidth - labelBoxPadding;
819
+ right = textX + labelHalfWidth + labelBoxPadding;
820
+ } else {
821
+ top = textY - labelHalfHeight - labelBoxPadding;
822
+ bottom = textY + labelHalfHeight + labelBoxPadding;
823
+ left = textX - labelWidth;
824
+ right = textX + labelBoxPadding;
825
+ }
826
+
827
+ ctx.fillStyle = fillColor;
828
+ ctx.strokeStyle = lineColor;
829
+ ctx.lineWidth = lineWidth;
830
+ ctx.moveTo(left, bottom);
831
+ ctx.lineTo(left, top);
832
+ ctx.lineTo(right, top);
833
+ ctx.lineTo(right, bottom);
834
+ ctx.lineTo(left, bottom);
835
+ ctx.fill();
836
+
837
+ if (lineWidth > 0) {
838
+ ctx.stroke();
839
+ }
840
+
841
+ ctx.fillStyle = fontColor;
842
+
843
+ ctx.fillText(label, textX, textY);
844
+ ctx.closePath();
845
+ }
846
+ }
847
+
848
+ export default Scale;