evui 3.3.12 → 3.3.15

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 (29) hide show
  1. package/dist/evui.common.js +1489 -716
  2. package/dist/evui.common.js.map +1 -1
  3. package/dist/evui.umd.js +1489 -716
  4. package/dist/evui.umd.js.map +1 -1
  5. package/dist/evui.umd.min.js +1 -1
  6. package/dist/evui.umd.min.js.map +1 -1
  7. package/package.json +1 -1
  8. package/src/components/chart/Chart.vue +23 -3
  9. package/src/components/chart/chart.core.js +12 -9
  10. package/src/components/chart/element/element.heatmap.js +293 -51
  11. package/src/components/chart/element/element.line.js +39 -42
  12. package/src/components/chart/element/element.pie.js +17 -17
  13. package/src/components/chart/element/element.tip.js +1 -1
  14. package/src/components/chart/helpers/helpers.constant.js +13 -11
  15. package/src/components/chart/model/model.series.js +1 -1
  16. package/src/components/chart/model/model.store.js +121 -73
  17. package/src/components/chart/plugins/plugins.interaction.js +111 -25
  18. package/src/components/chart/plugins/plugins.legend.js +28 -7
  19. package/src/components/chart/plugins/plugins.pie.js +17 -0
  20. package/src/components/chart/plugins/plugins.tooltip.js +28 -14
  21. package/src/components/chart/scale/scale.js +13 -3
  22. package/src/components/chart/scale/scale.step.js +52 -10
  23. package/src/components/chart/scale/scale.time.category.js +27 -3
  24. package/src/components/chart/uses.js +23 -1
  25. package/src/components/grid/Grid.vue +26 -11
  26. package/src/components/grid/grid.summary.vue +36 -6
  27. package/src/components/grid/uses.js +4 -1
  28. package/src/components/treeGrid/TreeGrid.vue +29 -11
  29. package/src/components/treeGrid/uses.js +4 -1
@@ -102,7 +102,8 @@ class Line {
102
102
  const getXPos = val => Canvas.calculateX(val, minmaxX.graphMin, minmaxX.graphMax, xArea, xsp);
103
103
  const getYPos = val => Canvas.calculateY(val, minmaxY.graphMin, minmaxY.graphMax, yArea, ysp);
104
104
 
105
- this.data.reduce((prev, curr, ix, item) => {
105
+ // draw line
106
+ this.data.reduce((prev, curr, ix) => {
106
107
  x = getXPos(curr.x);
107
108
  y = getYPos(curr.y);
108
109
 
@@ -110,22 +111,12 @@ class Line {
110
111
  x += Util.aliasPixel(x);
111
112
  }
112
113
 
113
- if (y === null || x === null) {
114
- if (ix - 1 > -1) {
115
- // draw fill(area) series not stacked
116
- if (this.fill && prev.y !== null && !this.stackIndex) {
117
- ctx.stroke();
118
- ctx.lineTo(prev.xp, endPoint);
119
- ctx.lineTo(item[startFillIndex].xp, endPoint);
120
-
121
- ctx.fill();
122
- ctx.beginPath();
123
- }
124
- }
114
+ if (ix === 0) {
115
+ ctx.moveTo(x, y);
116
+ }
125
117
 
126
- startFillIndex = ix + 1;
127
- } else if (ix === 0 || prev.y === null || curr.y === null
128
- || prev.x === null || curr.x === null) {
118
+ const isNullValue = prev.y === null || curr.y === null || prev.x === null || curr.x === null;
119
+ if (isNullValue) {
129
120
  ctx.moveTo(x, y);
130
121
  } else {
131
122
  ctx.lineTo(x, y);
@@ -139,39 +130,45 @@ class Line {
139
130
 
140
131
  ctx.stroke();
141
132
 
142
- const dataLen = this.data.length;
133
+ // draw fill
134
+ if (this.fill && this.data.length) {
135
+ ctx.beginPath();
143
136
 
144
- if (this.fill && dataLen) {
145
137
  ctx.fillStyle = Util.colorStringToRgba(mainColor, fillOpacity);
146
138
 
147
- if (this.stackIndex) {
148
- const reversedDataList = this.data.slice().reverse();
149
- reversedDataList.forEach((curr, ix) => {
150
- x = getXPos(curr.x);
151
- y = getYPos(curr.b);
152
-
153
- const prev = reversedDataList[ix - 1];
154
- if (curr.o !== null) {
155
- if (prev && prev.o == null) {
156
- ctx.moveTo(x, getYPos(curr.b + curr.o));
157
- }
158
-
159
- ctx.lineTo(x, y);
160
-
161
- if (ix === reversedDataList.length - 1) {
162
- ctx.lineTo(x, getYPos(curr.b + curr.o));
163
- }
164
- } else if (prev && prev.o) {
165
- ctx.lineTo(getXPos(prev.x), getYPos(prev.b + prev.o));
139
+ this.data.forEach((currData, ix) => {
140
+ const isEmptyPoint = data => data?.x === null || data?.y === null
141
+ || data?.x === undefined || data?.y === undefined;
142
+
143
+ const nextData = this.data[ix + 1];
144
+
145
+ if (isEmptyPoint(currData)) {
146
+ startFillIndex = ix + 1;
147
+
148
+ if (!isEmptyPoint(nextData)) {
149
+ ctx.moveTo(nextData.xp, nextData.yp);
166
150
  }
167
- });
168
- } else if (startFillIndex < dataLen) {
169
- ctx.lineTo(this.data[dataLen - 1].xp, endPoint);
170
- ctx.lineTo(this.data[startFillIndex].xp, endPoint);
171
- }
151
+
152
+ return;
153
+ }
154
+
155
+ ctx.lineTo(currData.xp, currData.yp);
156
+
157
+ if (isEmptyPoint(nextData)) {
158
+ for (let jx = ix; jx >= startFillIndex; jx--) {
159
+ const prevData = this.data[jx];
160
+ const xp = prevData.xp;
161
+ const bp = getYPos(prevData.b) ?? endPoint;
162
+ ctx.lineTo(xp, bp);
163
+ }
164
+
165
+ ctx.closePath();
166
+ }
167
+ });
172
168
 
173
169
  ctx.fill();
174
170
  }
171
+
175
172
  if (this.point || isExistSelectedLabel) {
176
173
  ctx.strokeStyle = Util.colorStringToRgba(mainColor, mainColorOpacity);
177
174
  const focusStyle = Util.colorStringToRgba(pointFillColor, 1);
@@ -45,7 +45,6 @@ class Pie {
45
45
  const slice = new Path2D();
46
46
 
47
47
  const radius = this.isSelect ? this.radius + 5 : this.radius;
48
- const doughnutHoleRadius = this.radius * this.doughnutHoleSize;
49
48
 
50
49
  const color = this.color;
51
50
  const noneDownplayOpacity = color.includes('rgba') ? Util.getOpacity(color) : 1;
@@ -66,7 +65,7 @@ class Pie {
66
65
  }
67
66
 
68
67
  if (this.showValue?.use) {
69
- this.drawValueLabels(ctx, doughnutHoleRadius);
68
+ this.drawValueLabels(ctx);
70
69
  }
71
70
 
72
71
  ctx.closePath();
@@ -115,7 +114,6 @@ class Pie {
115
114
  itemHighlight(item, context) {
116
115
  const ctx = context;
117
116
  const radius = this.isSelect ? this.radius + 5 : this.radius;
118
- const doughnutHoleRadius = this.radius * this.doughnutHoleSize;
119
117
 
120
118
  ctx.save();
121
119
  ctx.shadowOffsetX = 0;
@@ -133,7 +131,7 @@ class Pie {
133
131
  ctx.fill();
134
132
 
135
133
  if (this.showValue?.use) {
136
- this.drawValueLabels(ctx, doughnutHoleRadius);
134
+ this.drawValueLabels(ctx);
137
135
  }
138
136
 
139
137
  ctx.closePath();
@@ -146,19 +144,9 @@ class Pie {
146
144
  * @param context canvas context
147
145
  */
148
146
  drawValueLabels(context) {
149
- const { fontSize, textColor, formatter } = this.showValue;
150
147
  const ctx = context;
151
-
152
- ctx.save();
153
- ctx.beginPath();
154
-
155
- ctx.font = `normal normal normal ${fontSize}px Roboto`;
156
- ctx.fillStyle = textColor;
157
- ctx.lineWidth = 1;
158
- ctx.textAlign = 'center';
159
- ctx.textBaseline = 'middle';
160
-
161
148
  const value = this.data.o;
149
+ const { fontSize, textColor, formatter } = this.showValue;
162
150
 
163
151
  let formattedTxt;
164
152
  if (formatter) {
@@ -180,15 +168,27 @@ class Pie {
180
168
  && radius >= valueWidth * ratio
181
169
  && radius >= valueHeight * ratio
182
170
  ) {
171
+ ctx.save();
172
+ ctx.beginPath();
173
+
174
+ const noneDownplayOpacity = textColor.includes('rgba') ? Util.getOpacity(textColor) : 1;
175
+ const opacity = this.state === 'downplay' ? 0.1 : noneDownplayOpacity;
176
+
177
+ ctx.font = `normal normal normal ${fontSize}px Roboto`;
178
+ ctx.fillStyle = Util.colorStringToRgba(textColor, opacity);
179
+ ctx.lineWidth = 1;
180
+ ctx.textAlign = 'center';
181
+ ctx.textBaseline = 'middle';
182
+
183
183
  const halfRadius = (radius / 2) + this.doughnutHoleSize;
184
184
  const centerAngle = ((this.endAngle - this.startAngle) / 2) + this.startAngle;
185
185
  const xPos = halfRadius * Math.cos(centerAngle) + this.centerX;
186
186
  const yPos = halfRadius * Math.sin(centerAngle) + this.centerY;
187
187
 
188
188
  ctx.fillText(formattedTxt, xPos, yPos);
189
- }
190
189
 
191
- ctx.restore();
190
+ ctx.restore();
191
+ }
192
192
  }
193
193
  }
194
194
 
@@ -250,7 +250,7 @@ const modules = {
250
250
  const opt = this.options;
251
251
  const isHorizontal = !!opt.horizontal;
252
252
  const labelTipOpt = opt.selectLabel;
253
- const { dataIndex, data, label } = this.defaultSelectLabelInfo;
253
+ const { dataIndex, data, label } = this.defaultSelectInfo;
254
254
  let drawTip = false;
255
255
 
256
256
  if (dataIndex.length) {
@@ -129,17 +129,19 @@ export const PLOT_BAND_OPTION = {
129
129
  };
130
130
 
131
131
  export const HEAT_MAP_OPTION = {
132
- ...LINE_OPTION,
133
- colorOpt: {
134
- min: '#FFFFFF',
135
- max: '#0052FF',
136
- categoryCnt: 5,
137
- border: '#FFFFFF',
138
- error: '#FF0000',
139
- },
140
- spaces: {
141
- x: 0,
142
- y: 0,
132
+ show: true,
133
+ highlight: {
134
+ maxShadowOpacity: 0.4,
135
+ },
136
+ xAxisIndex: 0,
137
+ yAxisIndex: 0,
138
+ showLegend: true,
139
+ showValue: {
140
+ use: false,
141
+ fontSize: 12,
142
+ textColor: '#000000',
143
+ formatter: null,
144
+ decimalPoint: 0,
143
145
  },
144
146
  };
145
147
 
@@ -54,7 +54,7 @@ const modules = {
54
54
  return new Pie(id, opt, index);
55
55
  } else if (type === 'heatMap') {
56
56
  this.seriesInfo.charts.heatMap.push(id);
57
- return new HeatMap(id, opt, index);
57
+ return new HeatMap(id, opt, this.options.heatMapColor);
58
58
  }
59
59
 
60
60
  return false;
@@ -1,6 +1,5 @@
1
1
  import { reverse } from 'lodash-es';
2
2
  import Util from '../helpers/helpers.util';
3
- import { TIME_INTERVALS } from '../helpers/helpers.constant';
4
3
 
5
4
  const modules = {
6
5
  /**
@@ -37,8 +36,9 @@ const modules = {
37
36
  const sData = data[seriesID];
38
37
 
39
38
  if (series && sData) {
39
+ series.labels = label;
40
40
  series.data = this.addSeriesDSForHeatMap(sData);
41
- series.minMax = this.getSeriesMinMaxForHeatMap(series.data, series.spaces);
41
+ series.minMax = this.getSeriesMinMax(series.data);
42
42
  series.valueOpt = this.getSeriesValueOptForHeatMap(series);
43
43
  }
44
44
  });
@@ -301,17 +301,20 @@ const modules = {
301
301
  /**
302
302
  * Take data to create data for each series
303
303
  * @param {array} data data array for each series
304
+ * @param {object} label chart label
305
+ *
304
306
  * @returns {array} data info added position and etc
305
307
  */
306
308
  addSeriesDSForHeatMap(data) {
307
- return data.map(item => ({
308
- x: item.x,
309
- y: item.y,
310
- o: item.value,
309
+ return data.map(({ x, y, value }) => ({
310
+ x,
311
+ y,
312
+ o: value,
311
313
  xp: null,
312
314
  yp: null,
315
+ w: null,
316
+ h: null,
313
317
  dataColor: null,
314
- value: item.value,
315
318
  cId: null,
316
319
  }));
317
320
  },
@@ -414,92 +417,42 @@ const modules = {
414
417
  return def;
415
418
  },
416
419
 
417
- adjustMinMax(max, min, opt, space) {
418
- if ((opt.type === 'time' && opt.categoryMode) || opt.type === 'step') {
419
- return {
420
- max,
421
- min,
422
- };
423
- }
424
-
425
- let targetMax = max;
426
- let targetMin = min;
427
- if (targetMax > 0 && opt.interval && space) {
428
- if (targetMax < (opt.interval * space)) {
429
- targetMax += opt.interval;
430
- }
431
- }
432
-
433
- let targetInterval = opt.interval;
434
- if (opt.type === 'time') {
435
- if (typeof targetInterval === 'string') {
436
- targetInterval = TIME_INTERVALS[targetInterval].size;
437
- } else if (typeof targetInterval === 'object') {
438
- targetInterval = targetInterval.time * TIME_INTERVALS[targetInterval.unit].size;
439
- }
440
- }
441
-
442
- if (!opt.startToZero || targetMin > 0) {
443
- const targetSpace = space ? (space - 1) : (targetMax - targetMin);
444
- const targetStep = Math.ceil((max - targetMin) / targetSpace);
445
- targetMin = targetMin < targetStep ? 0 : targetMin - targetStep;
446
- }
447
-
448
- return {
449
- max: targetMax,
450
- min: targetMin,
451
- };
452
- },
453
- /**
454
- * Take series data to create min/max info for each series
455
- * @param data
456
- * @param spaces
457
- * @returns {*|{maxDomain: null, minY: null, minX: null, maxY: null, maxX: null}}
458
- */
459
- getSeriesMinMaxForHeatMap(data, spaces) {
460
- const axesXOption = this.options.axesX[0];
461
- const axesYOption = this.options.axesY[0];
462
- const seriesMinMax = this.getSeriesMinMax(data);
463
-
464
- const adjustX = this.adjustMinMax(seriesMinMax.maxX, seriesMinMax.minX, axesXOption, spaces.x);
465
- seriesMinMax.maxX = adjustX.max;
466
- seriesMinMax.minX = adjustX.min;
467
-
468
- const adjustY = this.adjustMinMax(seriesMinMax.maxY, seriesMinMax.minY, axesYOption, spaces.y);
469
- seriesMinMax.maxY = adjustY.max;
470
- seriesMinMax.minY = adjustY.min;
471
-
472
- return seriesMinMax;
473
- },
474
-
475
420
  getSeriesValueOptForHeatMap(series) {
476
421
  const data = series.data;
477
- const colorOpt = series.colorOpt;
478
- const colorAxis = series.colorAxis;
422
+ const colorOpt = this.options.heatMapColor;
479
423
  const categoryCnt = colorOpt.categoryCnt;
480
424
 
425
+ let minValue;
481
426
  let maxValue = 0;
427
+
482
428
  let isExistError = false;
483
- data.forEach(({ value }) => {
429
+ data.forEach(({ o: value }) => {
484
430
  if (maxValue < value) {
485
- maxValue = value;
431
+ maxValue = Math.max(maxValue, value);
486
432
  }
433
+
487
434
  if (value < 0) {
488
435
  isExistError = true;
436
+ } else if (minValue === undefined) {
437
+ minValue = value;
438
+ } else {
439
+ minValue = Math.min(minValue, value);
489
440
  }
490
441
  });
491
- const valueInterval = Math.ceil(maxValue / categoryCnt);
492
- if (isExistError && colorAxis.length === categoryCnt) {
493
- colorAxis.push({
442
+
443
+ if (isExistError && series.colorAxis.length === categoryCnt) {
444
+ series.colorAxis.push({
494
445
  id: `color#${categoryCnt}`,
495
446
  value: colorOpt.error,
496
447
  state: 'normal',
497
448
  show: true,
498
449
  });
499
450
  }
451
+
500
452
  return {
453
+ min: minValue,
501
454
  max: maxValue,
502
- interval: valueInterval,
455
+ interval: Math.ceil((maxValue - minValue) / categoryCnt),
503
456
  existError: isExistError,
504
457
  };
505
458
  },
@@ -673,6 +626,101 @@ const modules = {
673
626
  };
674
627
  },
675
628
 
629
+ /**
630
+ * Find seriesId by position x and y
631
+ * @param {array} offset position x and y
632
+ *
633
+ * @returns {object} clicked series id
634
+ */
635
+ getSeriesIdByPosition(offset) {
636
+ const [clickedX, clickedY] = offset;
637
+ const chartRect = this.chartRect;
638
+ const labelOffset = this.labelOffset;
639
+ const aPos = {
640
+ x1: chartRect.x1 + labelOffset.left,
641
+ x2: chartRect.x2 - labelOffset.right,
642
+ y1: chartRect.y1 + labelOffset.top,
643
+ y2: chartRect.y2 - labelOffset.bottom,
644
+ };
645
+ const valueAxes = this.axesY[0];
646
+ const labelAxes = this.axesX[0];
647
+ const valueStartPoint = aPos[valueAxes.units.rectStart];
648
+ const valueEndPoint = aPos[valueAxes.units.rectEnd];
649
+ const labelStartPoint = aPos[labelAxes.units.rectStart];
650
+ const labelEndPoint = aPos[labelAxes.units.rectEnd];
651
+
652
+ const result = { sId: null };
653
+
654
+ if (clickedY > valueEndPoint && clickedY < valueStartPoint
655
+ && clickedX < labelEndPoint && clickedX > labelStartPoint) {
656
+ let hitSeries;
657
+ let positionList;
658
+ const hitItem = this.findHitItem(offset);
659
+ const hitSeriesList = Object.keys(hitItem.items);
660
+
661
+ switch (this.options.type) {
662
+ case 'line': {
663
+ const orderedSeriesList = this.seriesInfo.charts.line;
664
+ const isStackChart = Object.values(this.seriesList).some(({ stackIndex }) => stackIndex);
665
+
666
+ if (hitSeriesList.length) { // 클릭한 위치에 data 가 존재하는 경우
667
+ if (isStackChart) {
668
+ positionList = orderedSeriesList.filter(sId => hitSeriesList.includes(sId))
669
+ .map(sId => ({ sId, position: hitItem.items[sId]?.data?.yp }));
670
+ hitSeries = positionList.find(({ position }) => clickedY > position)?.sId;
671
+ } else {
672
+ hitSeries = Object.entries(hitItem.items).find(([, { hit }]) => hit)?.[0];
673
+ }
674
+ } else { // 클릭한 위치에 data 가 존재하지 않는 경우
675
+ const visibleSeriesList = orderedSeriesList.filter(sId => this.seriesList[sId].show);
676
+ positionList = visibleSeriesList.map(sId => ({
677
+ sId,
678
+ position: this.seriesList[sId].data?.map(({ xp, yp }) => [xp, yp]),
679
+ }));
680
+ const dataIndex = positionList[0].position?.findIndex(([xp]) => xp >= clickedX);
681
+ const vectorList = positionList.map(({ sId, position }) => ({
682
+ sId,
683
+ vector: { start: position[dataIndex - 1], end: position[dataIndex] },
684
+ }));
685
+
686
+ // canvas 의 클릭 위치값은 제 4 사분면의 위치이므로 clickedY, y1, y2 의 값은 음수를 취한다.
687
+ if (isStackChart) {
688
+ hitSeries = vectorList.find(({ vector }) => {
689
+ const [x1, y1] = vector.start;
690
+ const [x2, y2] = vector.end;
691
+ const v1 = [x2 - x1, y1 - y2];
692
+ const v2 = [x2 - clickedX, clickedY - y2];
693
+ const xp = v1[0] * v2[1] - v1[1] * v2[0];
694
+
695
+ return vector.start.every(v => typeof v === 'number')
696
+ && vector.end.every(v => typeof v === 'number')
697
+ && xp > 0;
698
+ })?.sId;
699
+ } else {
700
+ hitSeries = vectorList.find(({ vector }) => {
701
+ const [x1, y1] = vector.start;
702
+ const [x2, y2] = vector.end;
703
+ const a = (y1 - y2) / (x2 - x1);
704
+ const b = -1;
705
+ const c = -y1 - a * x1;
706
+ const distance = Math.abs(a * clickedX - b * clickedY + c)
707
+ / Math.sqrt(a ** 2 + b ** 2);
708
+
709
+ return distance < 3;
710
+ })?.sId;
711
+ }
712
+ }
713
+ break;
714
+ }
715
+ default:
716
+ break;
717
+ }
718
+
719
+ result.sId = hitSeries;
720
+ }
721
+
722
+ return result;
723
+ },
676
724
  /**
677
725
  * Find label info by position x and y
678
726
  * @param {array} offset position x and y