evui 3.4.2 → 3.4.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "evui",
3
- "version": "3.4.2",
3
+ "version": "3.4.5",
4
4
  "description": "A EXEM Library project",
5
5
  "author": "exem <dev_client@ex-em.com>",
6
6
  "license": "MIT",
@@ -136,3 +136,17 @@ export function getPrecision(v) {
136
136
  export function checkNullAndUndefined(value) {
137
137
  return value === null || value === undefined;
138
138
  }
139
+
140
+ /**
141
+ * Check if the device is mobile
142
+ * @returns {boolean}
143
+ */
144
+ export function mobileCheck() {
145
+ return (
146
+ /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
147
+ navigator.userAgent,
148
+ )
149
+ || 'ontouchstart' in window
150
+ || navigator.maxTouchPoints
151
+ );
152
+ }
@@ -1,4 +1,5 @@
1
1
  import throttle from '@/common/utils.throttle';
2
+ import { mobileCheck } from '@/common/utils';
2
3
  import Model from './model';
3
4
  import TimeScale from './scale/scale.time';
4
5
  import LinearScale from './scale/scale.linear';
@@ -40,6 +41,7 @@ class EvChart {
40
41
  Object.assign(this, GradientLegend);
41
42
  }
42
43
 
44
+ this.isMobile = mobileCheck();
43
45
  this.brushSeries = brushSeries;
44
46
  this.target = target;
45
47
  this.data = data;
@@ -377,12 +379,12 @@ class EvChart {
377
379
  return axes.map((axis) => {
378
380
  switch (axis.type) {
379
381
  case 'linear':
380
- return new LinearScale(dir, axis, ctx, options);
382
+ return new LinearScale(dir, axis, ctx, labels, options);
381
383
  case 'time':
382
384
  if (axis.categoryMode) {
383
385
  return new TimeCategoryScale(dir, axis, ctx, labels, options);
384
386
  }
385
- return new TimeScale(dir, axis, ctx, options);
387
+ return new TimeScale(dir, axis, ctx, labels, options);
386
388
  case 'log':
387
389
  return new LogarithmicScale(dir, axis, ctx);
388
390
  case 'step':
@@ -939,6 +941,7 @@ class EvChart {
939
941
  this.overlayCanvas.removeEventListener('click', this.onClick);
940
942
  this.overlayCanvas.removeEventListener('mousedown', this.onMouseDown);
941
943
  this.overlayCanvas.removeEventListener('wheel', this.onWheel);
944
+ window.removeEventListener('click', this.dragTouchSelectionEvent);
942
945
  }
943
946
 
944
947
  if (this.options.tooltip.use) {
@@ -127,11 +127,6 @@ export const AXIS_OPTION = {
127
127
  },
128
128
  };
129
129
 
130
- export const PLOT_LINE_OPTION = {
131
- color: '#FF0000',
132
- lineWidth: 1,
133
- };
134
-
135
130
  export const PLOT_LINE_LABEL_OPTION = {
136
131
  show: false,
137
132
  fontSize: 12,
@@ -147,6 +142,12 @@ export const PLOT_LINE_LABEL_OPTION = {
147
142
  maxWidth: null,
148
143
  };
149
144
 
145
+ export const PLOT_LINE_OPTION = {
146
+ color: '#FF0000',
147
+ lineWidth: 1,
148
+ label: PLOT_LINE_LABEL_OPTION,
149
+ };
150
+
150
151
  export const PLOT_BAND_OPTION = {
151
152
  color: '#FAE59D',
152
153
  };
@@ -15,7 +15,7 @@ const modules = {
15
15
  * @returns {undefined}
16
16
  */
17
17
  this.onMouseMove = (e) => {
18
- if (this.dragInfo?.isMove) {
18
+ if (this.dragInfo?.isMove || this.isMobile) {
19
19
  return;
20
20
  }
21
21
 
@@ -265,11 +265,53 @@ const modules = {
265
265
  break;
266
266
  }
267
267
 
268
- case 'pie':
268
+ case 'pie': {
269
+ if (useSelectItem) {
270
+ setSelectedItemInfo();
271
+ }
272
+
273
+ break;
274
+ }
275
+
269
276
  case 'scatter': {
270
277
  if (useSelectItem) {
271
278
  setSelectedItemInfo();
272
279
  }
280
+
281
+ // 모바일용 dragSelection
282
+ if (this.options.dragSelection?.use && this.isMobile) {
283
+ let touchInfo = this.setTouchInfo(e);
284
+ this.overlayClear();
285
+
286
+ if (this.options.dragSelection.keepDisplay
287
+ && (e.layerX < touchInfo.range.x1
288
+ || e.layerY < touchInfo.range.y1
289
+ || e.layerX > touchInfo.range.x2
290
+ || e.layerY > touchInfo.range.y2)) {
291
+ this.isTouchOverlay = false;
292
+ } else {
293
+ touchInfo = this.setTouchBoxDimensions(touchInfo);
294
+ this.isTouchOverlay = true;
295
+ this.drawSelectionArea(touchInfo);
296
+ }
297
+
298
+ if (!this.options.dragSelection.keepDisplay) {
299
+ setTimeout(() => {
300
+ this.isTouchOverlay = false;
301
+ this.overlayClear();
302
+ }, 100);
303
+ }
304
+
305
+ args.e = e;
306
+ args.touchInfo = touchInfo;
307
+ args.data = this.findSelectedItems(touchInfo);
308
+ args.range = this.getSelectionRange(touchInfo);
309
+
310
+ if (typeof this.listeners['drag-select'] === 'function') {
311
+ this.listeners['drag-select'](args);
312
+ }
313
+ }
314
+
273
315
  break;
274
316
  }
275
317
  }
@@ -313,6 +355,9 @@ const modules = {
313
355
  this.overlayCanvas.addEventListener('dblclick', this.onDblClick);
314
356
  this.overlayCanvas.addEventListener('click', this.onClick);
315
357
  this.overlayCanvas.addEventListener('mousedown', this.onMouseDown);
358
+
359
+ this.dragTouchSelectionEvent = e => this.dragTouchSelectionDestroy(e);
360
+ window.addEventListener('click', this.dragTouchSelectionEvent);
316
361
  },
317
362
 
318
363
  /**
@@ -551,6 +596,99 @@ const modules = {
551
596
  return curMouseTargetVal;
552
597
  },
553
598
 
599
+ /**
600
+ * Processes touch event to determine touch position within the chart.
601
+ *
602
+ * @param {TouchEvent} event - the touch event to process
603
+ * @returns {object} - the processed touch information
604
+ */
605
+ setTouchInfo(event) {
606
+ let [offsetX, offsetY] = this.getMousePosition(event);
607
+ const chartRect = this.chartRect;
608
+ const labelOffset = this.labelOffset;
609
+ const range = {
610
+ x1: chartRect.x1 + labelOffset.left,
611
+ x2: chartRect.x2 - labelOffset.right,
612
+ y1: chartRect.y1 + labelOffset.top,
613
+ y2: chartRect.y2 - labelOffset.bottom,
614
+ };
615
+
616
+ offsetX = Math.max(range.x1, Math.min(offsetX, range.x2));
617
+ offsetY = Math.max(range.y1, Math.min(offsetY, range.y2));
618
+
619
+ return {
620
+ xcp: offsetX,
621
+ ycp: offsetY,
622
+ range,
623
+ };
624
+ },
625
+
626
+ /**
627
+ * Adjusts the touch box dimensions based on the provided touch information.
628
+ *
629
+ * @param {object} touchInfo - The touch information including touch position and plotting range
630
+ * @returns {object} - The adjusted touch information
631
+ */
632
+ setTouchBoxDimensions(touchInfo) {
633
+ const boxSize = this.options.dragSelection?.size || 50;
634
+ const halfBoxSize = boxSize / 2;
635
+ const { xcp, ycp, range } = touchInfo;
636
+ let xsp = xcp - halfBoxSize;
637
+ let ysp = ycp - halfBoxSize;
638
+ let width = boxSize;
639
+ let height = boxSize;
640
+
641
+ this.boxOverflow = {
642
+ x1: false,
643
+ x2: false,
644
+ y1: false,
645
+ y2: false,
646
+ };
647
+
648
+ if (xcp < range.x1 + halfBoxSize) {
649
+ xsp = range.x1;
650
+ width = halfBoxSize - (range.x1 - xcp);
651
+ this.boxOverflow.x1 = true;
652
+ }
653
+ if (xcp > range.x2 - halfBoxSize) {
654
+ width = halfBoxSize - (xcp - range.x2);
655
+ this.boxOverflow.x2 = true;
656
+ }
657
+ if (ycp < range.y1 + halfBoxSize) {
658
+ ysp = range.y1;
659
+ height = halfBoxSize - (range.y1 - ycp);
660
+ this.boxOverflow.y1 = true;
661
+ }
662
+ if (ycp > range.y2 - halfBoxSize) {
663
+ height = halfBoxSize - (ycp - range.y2);
664
+ this.boxOverflow.y2 = true;
665
+ }
666
+
667
+ touchInfo.xsp = xsp;
668
+ touchInfo.ysp = ysp;
669
+ touchInfo.width = width;
670
+ touchInfo.height = height;
671
+
672
+ return touchInfo;
673
+ },
674
+
675
+ /**
676
+ * Remove a touch selection.
677
+ *
678
+ * @param {TouchEvent} e - the touch event to process
679
+ * @returns {undefined}
680
+ */
681
+ dragTouchSelectionDestroy(e) {
682
+ if (
683
+ this.options.dragSelection?.use
684
+ && e.target !== this.overlayCanvas
685
+ && this.isTouchOverlay
686
+ ) {
687
+ this.isTouchOverlay = false;
688
+ this.overlayClear();
689
+ }
690
+ },
691
+
554
692
  /**
555
693
  * Find graph item on mouse position
556
694
  * @param {array} offset return value from getMousePosition()
@@ -952,16 +1090,24 @@ const modules = {
952
1090
  const yMinRatio = this.getRatioInRange(range.y1, range.y2, yep);
953
1091
  const yMaxRatio = this.getRatioInRange(range.y1, range.y2, ysp);
954
1092
 
955
- const xMin = dataRangeX.graphMin + graphWidth * xMinRatio;
956
- const xMax = dataRangeX.graphMin + graphWidth * xMaxRatio;
957
- const yMin = dataRangeY.graphMin + graphHeight * (1 - yMinRatio);
958
- const yMax = dataRangeY.graphMin + graphHeight * (1 - yMaxRatio);
1093
+ const xMin = this.isMobile && this.boxOverflow?.x1
1094
+ ? Math.min(this.minMax.x[0].min, dataRangeX.graphMin)
1095
+ : Math.max(dataRangeX.graphMin + graphWidth * xMinRatio, dataRangeX.graphMin);
1096
+ const xMax = this.isMobile && this.boxOverflow?.x2
1097
+ ? Math.max(this.minMax.x[0].max, dataRangeX.graphMax)
1098
+ : Math.min(dataRangeX.graphMin + graphWidth * xMaxRatio, dataRangeX.graphMax);
1099
+ const yMin = this.isMobile && this.boxOverflow?.y2
1100
+ ? Math.min(this.minMax.y[0].min, dataRangeY.graphMin)
1101
+ : Math.max(dataRangeY.graphMin + graphHeight * (1 - yMinRatio), dataRangeY.graphMin);
1102
+ const yMax = this.isMobile && this.boxOverflow?.y1
1103
+ ? Math.max(this.minMax.y[0].max, dataRangeY.graphMax)
1104
+ : Math.min(dataRangeY.graphMin + graphHeight * (1 - yMaxRatio), dataRangeY.graphMax);
959
1105
 
960
1106
  return {
961
- xMin: Math.max(+parseFloat(xMin).toFixed(3), dataRangeX.graphMin),
962
- xMax: Math.min(+parseFloat(xMax).toFixed(3), dataRangeX.graphMax),
963
- yMin: Math.max(+parseFloat(yMin).toFixed(3), dataRangeY.graphMin),
964
- yMax: Math.min(+parseFloat(yMax).toFixed(3), dataRangeY.graphMax),
1107
+ xMin: +xMin.toFixed(3),
1108
+ xMax: +xMax.toFixed(3),
1109
+ yMin: +yMin.toFixed(3),
1110
+ yMax: +yMax.toFixed(3),
965
1111
  };
966
1112
  },
967
1113
 
@@ -10,7 +10,7 @@ import {
10
10
  import Util from '../helpers/helpers.util';
11
11
 
12
12
  class Scale {
13
- constructor(type, axisOpt, ctx, options) {
13
+ constructor(type, axisOpt, ctx, labels, options) {
14
14
  const merged = defaultsDeep({}, axisOpt, AXIS_OPTION);
15
15
  Object.keys(merged).forEach((key) => {
16
16
  this[key] = merged[key];
@@ -19,6 +19,7 @@ class Scale {
19
19
  this.type = type;
20
20
  this.ctx = ctx;
21
21
  this.units = AXIS_UNITS[this.type];
22
+ this.labels = labels;
22
23
  this.options = options;
23
24
 
24
25
  if (!this.position) {
@@ -275,10 +276,22 @@ class Scale {
275
276
  }
276
277
 
277
278
  if (this.labelStyle?.show) {
278
- const labelGap = (endPoint - startPoint) / steps;
279
+ const distance = endPoint - startPoint;
280
+ const labelGap = distance / steps;
279
281
  const ticks = [];
282
+ const size = stepInfo.interval;
280
283
  let labelCenter = null;
281
284
  let linePosition = null;
285
+ let offsetStartPoint = startPoint;
286
+ let axisMinForLabel = axisMin;
287
+
288
+ if (this.type === 'x' && options?.axesX[0].flow && this.labels.length !== steps + 1) {
289
+ const axisMinByMinutes = Math.floor(axisMin / size) * size;
290
+ if (axisMinByMinutes !== +axisMin) {
291
+ axisMinForLabel = axisMinByMinutes + size;
292
+ offsetStartPoint += (distance / (axisMax - axisMin)) * (axisMinForLabel - axisMin);
293
+ }
294
+ }
282
295
 
283
296
  ctx.strokeStyle = this.gridLineColor;
284
297
  ctx.lineWidth = 1;
@@ -286,82 +299,85 @@ class Scale {
286
299
 
287
300
  let labelText;
288
301
  for (let ix = 0; ix <= steps; ix++) {
289
- ctx.beginPath();
290
- ticks[ix] = axisMin + (ix * stepValue);
302
+ labelCenter = Math.round(offsetStartPoint + (labelGap * ix));
291
303
 
292
- labelCenter = Math.round(startPoint + (labelGap * ix));
293
- linePosition = labelCenter + aliasPixel;
294
- labelText = this.getLabelFormat(Math.min(axisMax, ticks[ix]));
304
+ if (labelCenter <= endPoint || this.type !== 'x' || !options?.axesX[0].flow || this.labels.length === steps + 1) {
305
+ ctx.beginPath();
306
+ ticks[ix] = axisMinForLabel + (ix * stepValue);
295
307
 
296
- const isBlurredLabel = this.options?.selectLabel?.use
297
- && this.options?.selectLabel?.useLabelOpacity
298
- && (this.options.horizontal === (this.type === 'y'))
299
- && selectLabelInfo?.dataIndex?.length
300
- && !selectLabelInfo?.label
301
- .map(t => this.getLabelFormat(Math.min(axisMax, t))).includes(labelText);
308
+ linePosition = labelCenter + aliasPixel;
309
+ labelText = this.getLabelFormat(Math.min(axisMax, ticks[ix]));
302
310
 
303
- const labelColor = this.labelStyle.color;
304
- let defaultOpacity = 1;
311
+ const isBlurredLabel = this.options?.selectLabel?.use
312
+ && this.options?.selectLabel?.useLabelOpacity
313
+ && (this.options.horizontal === (this.type === 'y'))
314
+ && selectLabelInfo?.dataIndex?.length
315
+ && !selectLabelInfo?.label
316
+ .map(t => this.getLabelFormat(Math.min(axisMax, t))).includes(labelText);
305
317
 
306
- if (Util.getColorStringType(labelColor) === 'RGBA') {
307
- defaultOpacity = Util.getOpacity(labelColor);
308
- }
318
+ const labelColor = this.labelStyle.color;
319
+ let defaultOpacity = 1;
309
320
 
310
- ctx.fillStyle = Util.colorStringToRgba(labelColor, isBlurredLabel ? 0.1 : defaultOpacity);
321
+ if (Util.getColorStringType(labelColor) === 'RGBA') {
322
+ defaultOpacity = Util.getOpacity(labelColor);
323
+ }
311
324
 
312
- let labelPoint;
325
+ ctx.fillStyle = Util.colorStringToRgba(labelColor, isBlurredLabel ? 0.1 : defaultOpacity);
313
326
 
314
- if (this.type === 'x') {
315
- labelPoint = this.position === 'top' ? offsetPoint - 10 : offsetPoint + 10;
316
- if (options?.brush?.showLabel || !options?.brush) {
317
- ctx.fillText(labelText, labelCenter, labelPoint);
318
- }
327
+ let labelPoint;
319
328
 
320
- if (!isBlurredLabel
321
- && options?.selectItem?.showLabelTip
322
- && hitInfo?.label
323
- && !this.options?.horizontal) {
324
- const selectedLabel = this.getLabelFormat(
325
- Math.min(axisMax, hitInfo.label + (0 * stepValue)),
326
- );
327
- if (selectedLabel === labelText) {
328
- const height = Math.round(ctx.measureText(this.labelStyle?.fontSize).width);
329
- Util.showLabelTip({
330
- ctx: this.ctx,
331
- width: Math.round(ctx.measureText(selectedLabel).width) + 10,
332
- height,
333
- x: labelCenter,
334
- y: labelPoint + (height - 2),
335
- borderRadius: 2,
336
- arrowSize: 3,
337
- text: labelText,
338
- backgroundColor: options?.selectItem?.labelTipStyle?.backgroundColor,
339
- textColor: options?.selectItem?.labelTipStyle?.textColor,
340
- });
329
+ if (this.type === 'x') {
330
+ labelPoint = this.position === 'top' ? offsetPoint - 10 : offsetPoint + 10;
331
+ if (options?.brush?.showLabel || !options?.brush) {
332
+ ctx.fillText(labelText, labelCenter, labelPoint);
341
333
  }
342
- }
343
- if (ix !== 0 && this.showGrid) {
344
- ctx.moveTo(linePosition, offsetPoint);
345
- ctx.lineTo(linePosition, offsetCounterPoint);
346
- }
347
- } else {
348
- labelPoint = this.position === 'left' ? offsetPoint - 10 : offsetPoint + 10;
349
- if (options?.brush?.showLabel || !options?.brush) {
350
- ctx.fillText(labelText, labelPoint, labelCenter);
351
- }
352
334
 
353
- if (ix === steps) {
354
- linePosition -= 1;
355
- }
335
+ if (!isBlurredLabel
336
+ && options?.selectItem?.showLabelTip
337
+ && hitInfo?.label
338
+ && !this.options?.horizontal) {
339
+ const selectedLabel = this.getLabelFormat(
340
+ Math.min(axisMax, hitInfo.label + (0 * stepValue)),
341
+ );
342
+ if (selectedLabel === labelText) {
343
+ const height = Math.round(ctx.measureText(this.labelStyle?.fontSize).width);
344
+ Util.showLabelTip({
345
+ ctx: this.ctx,
346
+ width: Math.round(ctx.measureText(selectedLabel).width) + 10,
347
+ height,
348
+ x: labelCenter,
349
+ y: labelPoint + (height - 2),
350
+ borderRadius: 2,
351
+ arrowSize: 3,
352
+ text: labelText,
353
+ backgroundColor: options?.selectItem?.labelTipStyle?.backgroundColor,
354
+ textColor: options?.selectItem?.labelTipStyle?.textColor,
355
+ });
356
+ }
357
+ }
358
+ if ((ix !== 0 || options?.axesX[0].flow) && this.showGrid) {
359
+ ctx.moveTo(linePosition, offsetPoint);
360
+ ctx.lineTo(linePosition, offsetCounterPoint);
361
+ }
362
+ } else {
363
+ labelPoint = this.position === 'left' ? offsetPoint - 10 : offsetPoint + 10;
364
+ if (options?.brush?.showLabel || !options?.brush) {
365
+ ctx.fillText(labelText, labelPoint, labelCenter);
366
+ }
367
+
368
+ if (ix === steps) {
369
+ linePosition -= 1;
370
+ }
356
371
 
357
- if (ix !== 0 && this.showGrid) {
358
- ctx.moveTo(offsetPoint, linePosition);
359
- ctx.lineTo(offsetCounterPoint, linePosition);
372
+ if (ix !== 0 && this.showGrid) {
373
+ ctx.moveTo(offsetPoint, linePosition);
374
+ ctx.lineTo(offsetCounterPoint, linePosition);
375
+ }
360
376
  }
361
- }
362
377
 
363
- ctx.stroke();
364
- ctx.closePath();
378
+ ctx.stroke();
379
+ ctx.closePath();
380
+ }
365
381
  }
366
382
  }
367
383
 
@@ -527,7 +543,7 @@ class Scale {
527
543
 
528
544
  /**
529
545
  * Draw X Plot line
530
- * @param {object} dataX Data's X Position
546
+ * @param {number} dataX Data's X Position
531
547
  * @param {number} minX Min X Position
532
548
  * @param {number} maxX Max X Position
533
549
  * @param {number} minY Min Y Position
@@ -544,8 +560,11 @@ class Scale {
544
560
  return;
545
561
  }
546
562
 
547
- ctx.moveTo(dataX, maxY);
548
- ctx.lineTo(dataX, minY);
563
+ let dataXPos = dataX;
564
+ dataXPos += Util.aliasPixel(ctx.lineWidth);
565
+
566
+ ctx.moveTo(dataXPos, maxY);
567
+ ctx.lineTo(dataXPos, minY);
549
568
 
550
569
  ctx.stroke();
551
570
  ctx.restore();
@@ -554,7 +573,7 @@ class Scale {
554
573
 
555
574
  /**
556
575
  * Draw Y Plot line
557
- * @param {object} dataY Data's Y Position
576
+ * @param {number} dataY Data's Y Position
558
577
  * @param {number} minX Min X Position
559
578
  * @param {number} maxX Max X Position
560
579
  * @param {number} minY Min Y Position
@@ -571,8 +590,11 @@ class Scale {
571
590
  return;
572
591
  }
573
592
 
574
- ctx.moveTo(minX, dataY);
575
- ctx.lineTo(maxX, dataY);
593
+ let dataYPos = dataY;
594
+ dataYPos += Util.aliasPixel(ctx.lineWidth);
595
+
596
+ ctx.moveTo(minX, dataYPos);
597
+ ctx.lineTo(maxX, dataYPos);
576
598
 
577
599
  ctx.stroke();
578
600
  ctx.restore();
@@ -5,11 +5,6 @@ import Util from '../helpers/helpers.util';
5
5
  import { truthyNumber } from '../../../common/utils';
6
6
 
7
7
  class StepScale extends Scale {
8
- constructor(type, axisOpt, ctx, labels, options) {
9
- super(type, axisOpt, ctx, options);
10
- this.labels = labels;
11
- }
12
-
13
8
  /**
14
9
  * Calculate min/max value, label and size information for step scale
15
10
  * @param {object} minMax min/max information (unused on step scale)
@@ -4,12 +4,6 @@ import Scale from './scale';
4
4
  import Util from '../helpers/helpers.util';
5
5
 
6
6
  class TimeCategoryScale extends Scale {
7
- constructor(type, axisOpt, ctx, labels, options) {
8
- super(type, axisOpt, ctx);
9
- this.labels = labels;
10
- this.options = options;
11
- }
12
-
13
7
  /**
14
8
  * Transforming label by designated format
15
9
  * @param {number} value label value
@@ -181,6 +181,7 @@ const DEFAULT_OPTIONS = {
181
181
  dragSelection: {
182
182
  use: false,
183
183
  keepDisplay: true,
184
+ size: 50,
184
185
  fillColor: '#38ACEC',
185
186
  opacity: 0.65,
186
187
  },