evui 3.4.151 → 3.4.153
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/dist/evui.common.js +453 -134
- package/dist/evui.common.js.map +1 -1
- package/dist/evui.umd.js +453 -134
- package/dist/evui.umd.js.map +1 -1
- package/dist/evui.umd.min.js +1 -1
- package/dist/evui.umd.min.js.map +1 -1
- package/package.json +1 -1
- package/src/components/chart/Chart.vue +68 -64
- package/src/components/chart/chart.core.js +25 -19
- package/src/components/chart/plugins/plugins.interaction.js +5 -2
- package/src/components/chart/plugins/plugins.legend.js +44 -8
- package/src/components/chart/plugins/plugins.scrollbar.js +2 -9
- package/src/components/chart/scale/scale.linear.js +156 -25
- package/src/components/chart/scale/scale.time.js +94 -0
- package/src/components/chart/uses.js +7 -0
package/package.json
CHANGED
|
@@ -34,70 +34,71 @@
|
|
|
34
34
|
import EvChartToolbar from './ChartToolbar';
|
|
35
35
|
import { useModel, useWrapper, useZoomModel } from './uses';
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
37
|
+
export default {
|
|
38
|
+
name: 'EvChart',
|
|
39
|
+
components: {
|
|
40
|
+
EvChartToolbar,
|
|
41
|
+
},
|
|
42
|
+
props: {
|
|
43
|
+
selectedItem: {
|
|
44
|
+
type: Object,
|
|
45
|
+
default: null,
|
|
41
46
|
},
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
default: null,
|
|
46
|
-
},
|
|
47
|
-
selectedLabel: {
|
|
48
|
-
type: Object,
|
|
49
|
-
default: null,
|
|
50
|
-
},
|
|
51
|
-
selectedSeries: {
|
|
52
|
-
type: Object,
|
|
53
|
-
default: null,
|
|
54
|
-
},
|
|
55
|
-
options: {
|
|
56
|
-
type: Object,
|
|
57
|
-
default: () => ({}),
|
|
58
|
-
},
|
|
59
|
-
data: {
|
|
60
|
-
type: Object,
|
|
61
|
-
default: () => ({}),
|
|
62
|
-
},
|
|
63
|
-
resizeTimeout: {
|
|
64
|
-
type: Number,
|
|
65
|
-
default: 0,
|
|
66
|
-
},
|
|
67
|
-
zoomStartIdx: {
|
|
68
|
-
type: Number,
|
|
69
|
-
default: 0,
|
|
70
|
-
},
|
|
71
|
-
zoomEndIdx: {
|
|
72
|
-
type: Number,
|
|
73
|
-
default: 0,
|
|
74
|
-
},
|
|
75
|
-
realTimeScatterReset: {
|
|
76
|
-
type: Boolean,
|
|
77
|
-
default: false,
|
|
78
|
-
},
|
|
47
|
+
selectedLabel: {
|
|
48
|
+
type: Object,
|
|
49
|
+
default: null,
|
|
79
50
|
},
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
51
|
+
selectedSeries: {
|
|
52
|
+
type: Object,
|
|
53
|
+
default: null,
|
|
54
|
+
},
|
|
55
|
+
options: {
|
|
56
|
+
type: Object,
|
|
57
|
+
default: () => ({}),
|
|
58
|
+
},
|
|
59
|
+
data: {
|
|
60
|
+
type: Object,
|
|
61
|
+
default: () => ({}),
|
|
62
|
+
},
|
|
63
|
+
resizeTimeout: {
|
|
64
|
+
type: Number,
|
|
65
|
+
default: 0,
|
|
66
|
+
},
|
|
67
|
+
zoomStartIdx: {
|
|
68
|
+
type: Number,
|
|
69
|
+
default: 0,
|
|
70
|
+
},
|
|
71
|
+
zoomEndIdx: {
|
|
72
|
+
type: Number,
|
|
73
|
+
default: 0,
|
|
74
|
+
},
|
|
75
|
+
realTimeScatterReset: {
|
|
76
|
+
type: Boolean,
|
|
77
|
+
default: false,
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
emits: [
|
|
81
|
+
'click',
|
|
82
|
+
'dbl-click',
|
|
83
|
+
'drag-select',
|
|
84
|
+
'mouse-move',
|
|
85
|
+
'update:selectedItem',
|
|
86
|
+
'update:selectedLabel',
|
|
87
|
+
'update:selectedSeries',
|
|
88
|
+
'update:zoomStartIdx',
|
|
89
|
+
'update:zoomEndIdx',
|
|
90
|
+
'update:realTimeScatterReset',
|
|
91
|
+
'click-legend',
|
|
92
|
+
],
|
|
93
|
+
setup(props, { emit }) {
|
|
94
|
+
let evChart = null;
|
|
95
|
+
const isMounted = ref(false);
|
|
96
|
+
const injectIsChartGroup = inject('isChartGroup', false);
|
|
97
|
+
const injectBrushSeries = inject('brushSeries', { list: [], chartIdx: null });
|
|
98
|
+
const injectGroupSelectedLabel = inject('groupSelectedLabel', null);
|
|
99
|
+
const injectGroupHoveredLabel = inject('groupHoveredLabel', null);
|
|
100
|
+
const injectBrushIdx = inject('brushIdx', { start: 0, end: -1 });
|
|
101
|
+
const injectEvChartPropsInGroup = inject('evChartPropsInGroup', []);
|
|
101
102
|
|
|
102
103
|
const {
|
|
103
104
|
eventListeners,
|
|
@@ -269,9 +270,12 @@
|
|
|
269
270
|
|
|
270
271
|
emit('update:realTimeScatterReset', false);
|
|
271
272
|
}
|
|
272
|
-
}
|
|
273
|
+
},
|
|
274
|
+
);
|
|
273
275
|
|
|
274
|
-
|
|
276
|
+
watch(
|
|
277
|
+
() => props.options.realTimeScatter?.use,
|
|
278
|
+
(use) => {
|
|
275
279
|
evChart.options.realTimeScatter.use = use ?? false;
|
|
276
280
|
|
|
277
281
|
evChart.update({
|
|
@@ -127,6 +127,10 @@ class EvChart {
|
|
|
127
127
|
this.axesX = this.createAxes('x', axesX);
|
|
128
128
|
this.axesY = this.createAxes('y', axesY);
|
|
129
129
|
|
|
130
|
+
if (axesX?.[0]?.scrollbar?.use || axesY?.[0]?.scrollbar?.use) {
|
|
131
|
+
this.initScrollbar();
|
|
132
|
+
}
|
|
133
|
+
|
|
130
134
|
this.initDefaultSelectInfo();
|
|
131
135
|
|
|
132
136
|
this.drawChart();
|
|
@@ -162,10 +166,6 @@ class EvChart {
|
|
|
162
166
|
this.setLegendPosition();
|
|
163
167
|
}
|
|
164
168
|
|
|
165
|
-
if (opt.axesX?.[0]?.scrollbar?.use || opt.axesY?.[0]?.scrollbar?.use) {
|
|
166
|
-
this.initScrollbar();
|
|
167
|
-
}
|
|
168
|
-
|
|
169
169
|
this.chartRect = this.getChartRect();
|
|
170
170
|
}
|
|
171
171
|
|
|
@@ -277,7 +277,6 @@ class EvChart {
|
|
|
277
277
|
this.drawSeries(hitInfo);
|
|
278
278
|
|
|
279
279
|
if (this.scrollbar?.x?.use || this.scrollbar?.y?.use) {
|
|
280
|
-
this.initScrollbar();
|
|
281
280
|
this.updateScrollbarPosition();
|
|
282
281
|
}
|
|
283
282
|
|
|
@@ -852,6 +851,21 @@ class EvChart {
|
|
|
852
851
|
return labelOffset;
|
|
853
852
|
}
|
|
854
853
|
|
|
854
|
+
/**
|
|
855
|
+
* Update scrollbar information
|
|
856
|
+
* @param {boolean} updateData is update data
|
|
857
|
+
* @returns {undefined}
|
|
858
|
+
*/
|
|
859
|
+
updateScrollbar(updateData) {
|
|
860
|
+
if (this.scrollbar?.x?.isInit || this.options.axesX?.[0]?.scrollbar?.use) {
|
|
861
|
+
this.updateScrollbarInfo('x', updateData);
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
if (this.scrollbar?.y?.isInit || this.options.axesY?.[0]?.scrollbar?.use) {
|
|
865
|
+
this.updateScrollbarInfo('y', updateData);
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
|
|
855
869
|
/**
|
|
856
870
|
* To re-render chart, reset properties, canvas and then render chart.
|
|
857
871
|
* @param {object} updateInfo information for each components are needed to update
|
|
@@ -871,7 +885,6 @@ class EvChart {
|
|
|
871
885
|
updateLegend,
|
|
872
886
|
updateData,
|
|
873
887
|
updateTooltip,
|
|
874
|
-
updateByScrollbar,
|
|
875
888
|
lightUpdate,
|
|
876
889
|
} = updateInfo;
|
|
877
890
|
|
|
@@ -879,9 +892,7 @@ class EvChart {
|
|
|
879
892
|
return;
|
|
880
893
|
}
|
|
881
894
|
|
|
882
|
-
|
|
883
|
-
this.updateScrollbar?.(updateData);
|
|
884
|
-
}
|
|
895
|
+
this.updateScrollbar(updateData);
|
|
885
896
|
|
|
886
897
|
this.resetProps();
|
|
887
898
|
|
|
@@ -992,7 +1003,6 @@ class EvChart {
|
|
|
992
1003
|
|
|
993
1004
|
this.initDefaultSelectInfo();
|
|
994
1005
|
|
|
995
|
-
|
|
996
1006
|
let renderHitInfo = updateInfo?.hitInfo;
|
|
997
1007
|
if (!renderHitInfo?.legend && this.legendHover?.sId) {
|
|
998
1008
|
renderHitInfo = { ...(renderHitInfo || {}), legend: this.legendHover };
|
|
@@ -1065,20 +1075,16 @@ class EvChart {
|
|
|
1065
1075
|
* @returns {undefined}
|
|
1066
1076
|
*/
|
|
1067
1077
|
resize(promiseRes) {
|
|
1068
|
-
// 차트 크기가 변경될 때 저장된 스크롤 픽셀 위치를 초기화하여
|
|
1069
|
-
// 새로운 크기에 맞춰 스크롤바 크기/위치를 재계산하도록 함
|
|
1070
|
-
if (this.scrollbar?.x) {
|
|
1071
|
-
delete this.scrollbar.x.savedPosition;
|
|
1072
|
-
}
|
|
1073
|
-
if (this.scrollbar?.y) {
|
|
1074
|
-
delete this.scrollbar.y.savedPosition;
|
|
1075
|
-
}
|
|
1076
|
-
|
|
1077
1078
|
this.clear();
|
|
1078
1079
|
this.bufferCtx.restore();
|
|
1079
1080
|
this.bufferCtx.save();
|
|
1080
1081
|
|
|
1081
1082
|
this.initRect();
|
|
1083
|
+
|
|
1084
|
+
if (this.options.axesX?.[0]?.scrollbar?.use || this.options.axesY?.[0]?.scrollbar?.use) {
|
|
1085
|
+
this.initScrollbar();
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1082
1088
|
this.initScale();
|
|
1083
1089
|
this.chartRect = this.getChartRect();
|
|
1084
1090
|
this.drawChart();
|
|
@@ -1018,8 +1018,11 @@ const modules = {
|
|
|
1018
1018
|
const isHorizontal = !!this.options.horizontal;
|
|
1019
1019
|
const mousePos = isHorizontal ? yp : xp;
|
|
1020
1020
|
|
|
1021
|
-
//
|
|
1022
|
-
const referenceSeries = sIds.find(sId =>
|
|
1021
|
+
// 데이터 있는 시리즈를 기준으로 라벨 위치 확인
|
|
1022
|
+
const referenceSeries = sIds.find((sId) => {
|
|
1023
|
+
const series = this.seriesList[sId];
|
|
1024
|
+
return series?.show && series?.data?.length > 0;
|
|
1025
|
+
});
|
|
1023
1026
|
if (!referenceSeries || !this.seriesList[referenceSeries]?.data) {
|
|
1024
1027
|
return -1;
|
|
1025
1028
|
}
|
|
@@ -542,10 +542,28 @@ const modules = {
|
|
|
542
542
|
this.brushSeries.chartIdx = chartIdx;
|
|
543
543
|
}
|
|
544
544
|
|
|
545
|
-
this.
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
545
|
+
if (this.options.eventBehavior?.legendClick !== 'emitOnly') {
|
|
546
|
+
this.update({
|
|
547
|
+
updateSeries: false,
|
|
548
|
+
updateSelTip: { update: true, keepDomain: true },
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
// click-legend event 발생
|
|
553
|
+
const activeSeries = Object.values(this.seriesList).filter(series => series.show);
|
|
554
|
+
const activeSeriesIds = activeSeries.map(series => series.sId);
|
|
555
|
+
const isActiveAll = activeSeriesIds.length === Object.values(this.seriesList).length;
|
|
556
|
+
const args = {
|
|
557
|
+
e,
|
|
558
|
+
data: {
|
|
559
|
+
seriesIds: isActiveAll ? [] : activeSeriesIds,
|
|
560
|
+
isActiveAll,
|
|
561
|
+
},
|
|
562
|
+
};
|
|
563
|
+
|
|
564
|
+
if (typeof this.listeners['click-legend'] === 'function') {
|
|
565
|
+
this.listeners['click-legend'](args);
|
|
566
|
+
}
|
|
549
567
|
};
|
|
550
568
|
|
|
551
569
|
/**
|
|
@@ -750,10 +768,28 @@ const modules = {
|
|
|
750
768
|
}
|
|
751
769
|
}
|
|
752
770
|
|
|
753
|
-
this.
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
771
|
+
if (this.options.eventBehavior?.legendClick !== 'emitOnly') {
|
|
772
|
+
this.update({
|
|
773
|
+
updateSeries: false,
|
|
774
|
+
updateSelTip: { update: true, keepDomain: true },
|
|
775
|
+
});
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
// click-legend event 발생
|
|
779
|
+
const activeSeries = series.colorState.filter(colorItem => colorItem.show);
|
|
780
|
+
const activeSerieIndices = activeSeries.map(colorItem => +colorItem.id.split('#')[1]);
|
|
781
|
+
const isActiveAll = series.colorState.length === activeSeries.length;
|
|
782
|
+
const args = {
|
|
783
|
+
e,
|
|
784
|
+
data: {
|
|
785
|
+
seriesIndices: isActiveAll ? [] : activeSerieIndices,
|
|
786
|
+
isActiveAll,
|
|
787
|
+
},
|
|
788
|
+
};
|
|
789
|
+
|
|
790
|
+
if (typeof this.listeners['click-legend'] === 'function') {
|
|
791
|
+
this.listeners['click-legend'](args);
|
|
792
|
+
}
|
|
757
793
|
};
|
|
758
794
|
|
|
759
795
|
/**
|
|
@@ -29,6 +29,8 @@ const module = {
|
|
|
29
29
|
scrollbarOpt[key] = merged[key];
|
|
30
30
|
});
|
|
31
31
|
|
|
32
|
+
delete scrollbarOpt.savedPosition;
|
|
33
|
+
|
|
32
34
|
if (!scrollbarOpt.isInit) {
|
|
33
35
|
scrollbarOpt.type = axisOpt?.[0]?.type;
|
|
34
36
|
scrollbarOpt.range = axisOpt?.[0]?.range?.length ? [...axisOpt?.[0]?.range] : null;
|
|
@@ -83,14 +85,6 @@ const module = {
|
|
|
83
85
|
}
|
|
84
86
|
},
|
|
85
87
|
|
|
86
|
-
/**
|
|
87
|
-
* update scrollbar information
|
|
88
|
-
*/
|
|
89
|
-
updateScrollbar(updateData) {
|
|
90
|
-
this.updateScrollbarInfo('x', updateData);
|
|
91
|
-
this.updateScrollbarInfo('y', updateData);
|
|
92
|
-
},
|
|
93
|
-
|
|
94
88
|
/**
|
|
95
89
|
* Updated scrollbar information with updated axis information
|
|
96
90
|
* @param dir axis direction (x | y)
|
|
@@ -449,7 +443,6 @@ const module = {
|
|
|
449
443
|
this.update({
|
|
450
444
|
updateSeries: false,
|
|
451
445
|
updateSelTip: { update: false, keepDomain: false },
|
|
452
|
-
updateByScrollbar: true,
|
|
453
446
|
lightUpdate: minValue > 1,
|
|
454
447
|
});
|
|
455
448
|
}
|
|
@@ -11,7 +11,8 @@ class LinearScale extends Scale {
|
|
|
11
11
|
* @returns {string} formatted label
|
|
12
12
|
*/
|
|
13
13
|
getTruthyValue(value) {
|
|
14
|
-
|
|
14
|
+
const decimalPoint = this.adjustedDecimalPoint ?? this.decimalPoint;
|
|
15
|
+
return truthyNumber(value) ? Number(value.toFixed(decimalPoint)) : value;
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
getLabelFormat(value, data = {}) {
|
|
@@ -31,8 +32,8 @@ class LinearScale extends Scale {
|
|
|
31
32
|
}
|
|
32
33
|
}
|
|
33
34
|
|
|
34
|
-
|
|
35
|
-
return Util.labelSignFormat(value,
|
|
35
|
+
const decimalPoint = this.adjustedDecimalPoint ?? this.decimalPoint;
|
|
36
|
+
return Util.labelSignFormat(value, decimalPoint);
|
|
36
37
|
}
|
|
37
38
|
|
|
38
39
|
|
|
@@ -58,41 +59,171 @@ class LinearScale extends Scale {
|
|
|
58
59
|
return Math.ceil((max - min) / step);
|
|
59
60
|
}
|
|
60
61
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
* }
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Get auto decimal point from interval
|
|
65
|
+
* interval을 표현할 수 있는 최소 decimal 반환
|
|
66
|
+
* 너무 긴 decimal은 제한
|
|
67
|
+
* @param {number} interval
|
|
68
68
|
* @returns {number} decimal point
|
|
69
69
|
*/
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
numberOfSteps,
|
|
73
|
-
}) {
|
|
74
|
-
if (numberOfSteps <= 0 || graphRange === 0) {
|
|
70
|
+
getAutoDecimalPointFromInterval(interval) {
|
|
71
|
+
if (!isFinite(interval) || interval === 0) {
|
|
75
72
|
return 0;
|
|
76
73
|
}
|
|
77
74
|
|
|
78
|
-
const
|
|
79
|
-
|
|
75
|
+
const absInterval = Math.abs(interval);
|
|
76
|
+
|
|
77
|
+
// 1 미만 값 처리 (소수점 최대 10자리 제한)
|
|
78
|
+
if (absInterval < 1) {
|
|
79
|
+
let decimals = 0;
|
|
80
|
+
let temp = absInterval;
|
|
81
|
+
|
|
82
|
+
while (temp < 1) {
|
|
83
|
+
temp *= 10;
|
|
84
|
+
decimals++;
|
|
85
|
+
|
|
86
|
+
if (decimals > 10) {
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return decimals;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// 1 이상 값 처리 (소수점 최대 2자리 제한)
|
|
95
|
+
for (let decimal = 0; decimal <= 6; decimal++) {
|
|
96
|
+
const rounded = Number(absInterval.toFixed(decimal));
|
|
97
|
+
|
|
98
|
+
if (Math.abs(rounded - absInterval) < 1e-10) {
|
|
99
|
+
return Math.min(decimal, 2);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return 2;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* axis interval을 nice number로 변환
|
|
108
|
+
* (1, 2, 5 × 10^n)
|
|
109
|
+
*
|
|
110
|
+
* @param {Object} params
|
|
111
|
+
* @param {number} params.range
|
|
112
|
+
* @param {boolean} params.round
|
|
113
|
+
* @returns {number}
|
|
114
|
+
*/
|
|
115
|
+
getNiceNumber({ range, round = false }) {
|
|
116
|
+
if (!isFinite(range) || range <= 0) {
|
|
80
117
|
return 0;
|
|
81
118
|
}
|
|
82
119
|
|
|
83
|
-
|
|
84
|
-
|
|
120
|
+
const exponent = Math.floor(Math.log10(range));
|
|
121
|
+
const fraction = range / (10 ** exponent);
|
|
122
|
+
|
|
123
|
+
let niceFraction;
|
|
85
124
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
125
|
+
if (round) {
|
|
126
|
+
if (fraction < 1.5) {
|
|
127
|
+
niceFraction = 1;
|
|
128
|
+
} else if (fraction < 3) {
|
|
129
|
+
niceFraction = 2;
|
|
130
|
+
} else if (fraction < 7) {
|
|
131
|
+
niceFraction = 5;
|
|
132
|
+
} else {
|
|
133
|
+
niceFraction = 10;
|
|
134
|
+
}
|
|
135
|
+
} else if (fraction <= 1) {
|
|
136
|
+
niceFraction = 1;
|
|
137
|
+
} else if (fraction <= 2) {
|
|
138
|
+
niceFraction = 2;
|
|
139
|
+
} else if (fraction <= 5) {
|
|
140
|
+
niceFraction = 5;
|
|
141
|
+
} else {
|
|
142
|
+
niceFraction = 10;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return niceFraction * (10 ** exponent);
|
|
146
|
+
}
|
|
89
147
|
|
|
90
|
-
|
|
91
|
-
|
|
148
|
+
/**
|
|
149
|
+
* With range information, calculate how many labels in axis
|
|
150
|
+
* @param {object} range min/max information
|
|
151
|
+
*
|
|
152
|
+
* @returns {object} steps, interval, min/max graph value
|
|
153
|
+
*/
|
|
154
|
+
calculateSteps(range) {
|
|
155
|
+
const { minValue, maxValue } = range;
|
|
156
|
+
const maxSteps = Math.max(1, range.maxSteps);
|
|
157
|
+
|
|
158
|
+
const hasUserRange = Array.isArray(this.range) && this.range.length === 2;
|
|
159
|
+
const hasUserInterval = (
|
|
160
|
+
typeof this.interval === 'number'
|
|
161
|
+
|| (typeof this.interval === 'object' && this.interval !== null)
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
const resolvedInterval = hasUserInterval ? this.getInterval(range) : null;
|
|
165
|
+
const isValidInterval = (
|
|
166
|
+
resolvedInterval != null
|
|
167
|
+
&& resolvedInterval > 0
|
|
168
|
+
&& isFinite(resolvedInterval)
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
const graphMin = +minValue;
|
|
172
|
+
let graphMax = +maxValue;
|
|
173
|
+
const graphRange = graphMax - graphMin;
|
|
174
|
+
|
|
175
|
+
let interval;
|
|
176
|
+
let steps;
|
|
177
|
+
|
|
178
|
+
if (hasUserRange && isValidInterval) {
|
|
179
|
+
// 1) user range + interval
|
|
180
|
+
const candidateSteps = graphRange / resolvedInterval;
|
|
181
|
+
const isExactlyDividable = Math.abs(candidateSteps - Math.round(candidateSteps)) < 1e-10;
|
|
182
|
+
|
|
183
|
+
if (isExactlyDividable && candidateSteps <= maxSteps) {
|
|
184
|
+
interval = resolvedInterval;
|
|
185
|
+
steps = Math.round(candidateSteps);
|
|
186
|
+
} else {
|
|
187
|
+
// interval 호환되지 않음 -> 사용자 interval을 사용하지 않음
|
|
188
|
+
steps = maxSteps;
|
|
189
|
+
interval = graphRange / steps;
|
|
190
|
+
}
|
|
191
|
+
} else if (hasUserRange) {
|
|
192
|
+
// 2) user range only
|
|
193
|
+
steps = maxSteps;
|
|
194
|
+
interval = graphRange / steps;
|
|
195
|
+
} else if (isValidInterval) {
|
|
196
|
+
// 3) user interval only
|
|
197
|
+
interval = resolvedInterval;
|
|
198
|
+
steps = Math.ceil(graphRange / interval);
|
|
199
|
+
|
|
200
|
+
while (steps > maxSteps) {
|
|
201
|
+
interval *= 2;
|
|
202
|
+
steps = Math.ceil(graphRange / interval);
|
|
92
203
|
}
|
|
204
|
+
|
|
205
|
+
graphMax = graphMin + (interval * steps);
|
|
206
|
+
} else {
|
|
207
|
+
// 4) auto
|
|
208
|
+
interval = this.getNiceNumber({
|
|
209
|
+
range: graphRange / maxSteps,
|
|
210
|
+
round: true,
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
steps = Math.ceil(graphRange / interval);
|
|
214
|
+
graphMax = graphMin + (interval * steps);
|
|
93
215
|
}
|
|
94
216
|
|
|
95
|
-
|
|
217
|
+
this.adjustedDecimalPoint = this.decimalPoint === 'auto'
|
|
218
|
+
? this.getAutoDecimalPointFromInterval(interval)
|
|
219
|
+
: this.decimalPoint;
|
|
220
|
+
|
|
221
|
+
return {
|
|
222
|
+
steps,
|
|
223
|
+
interval,
|
|
224
|
+
graphMin,
|
|
225
|
+
graphMax,
|
|
226
|
+
};
|
|
96
227
|
}
|
|
97
228
|
}
|
|
98
229
|
|
|
@@ -44,6 +44,100 @@ class TimeScale extends Scale {
|
|
|
44
44
|
}
|
|
45
45
|
return Math.ceil((max - min) / step);
|
|
46
46
|
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* With range information, calculate how many labels in axis
|
|
50
|
+
* @param {object} range min/max information
|
|
51
|
+
*
|
|
52
|
+
* @returns {object} steps, interval, min/max graph value
|
|
53
|
+
*/
|
|
54
|
+
calculateSteps(range) {
|
|
55
|
+
const { maxValue, minValue, maxSteps } = range;
|
|
56
|
+
|
|
57
|
+
// 사용자 interval로 인식하는 경우: 숫자 또는 객체({ time, unit }) 형태만
|
|
58
|
+
// 문자열('hour', 'second' 등)은 기존 로직(분기 D)으로 처리
|
|
59
|
+
const hasUserRange = Array.isArray(this.range) && this.range.length === 2;
|
|
60
|
+
const hasUserInterval = (
|
|
61
|
+
typeof this.interval === 'number'
|
|
62
|
+
|| (typeof this.interval === 'object' && this.interval !== null)
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
const resolvedInterval = hasUserInterval ? this.getInterval(range) : null;
|
|
66
|
+
const isValidInterval = (
|
|
67
|
+
resolvedInterval != null
|
|
68
|
+
&& resolvedInterval > 0
|
|
69
|
+
&& isFinite(resolvedInterval)
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
const graphMin = +minValue;
|
|
73
|
+
let graphMax = +maxValue;
|
|
74
|
+
const graphRange = graphMax - graphMin;
|
|
75
|
+
|
|
76
|
+
let interval;
|
|
77
|
+
let steps;
|
|
78
|
+
|
|
79
|
+
if (hasUserRange && isValidInterval) {
|
|
80
|
+
// 1) user range + interval
|
|
81
|
+
const candidateSteps = graphRange / resolvedInterval;
|
|
82
|
+
const isExactlyDividable = Math.abs(candidateSteps - Math.round(candidateSteps)) < 1e-10;
|
|
83
|
+
if (isExactlyDividable && candidateSteps <= maxSteps) {
|
|
84
|
+
// 1-1) interval 호환되는 경우
|
|
85
|
+
interval = resolvedInterval;
|
|
86
|
+
steps = Math.round(candidateSteps);
|
|
87
|
+
} else {
|
|
88
|
+
// 1-2) interval 호환되지 않음 -> 사용자 interval을 사용하지 않음
|
|
89
|
+
steps = maxSteps;
|
|
90
|
+
interval = graphRange / steps;
|
|
91
|
+
}
|
|
92
|
+
} else if (hasUserRange) {
|
|
93
|
+
// 2) user range only
|
|
94
|
+
steps = maxSteps;
|
|
95
|
+
interval = graphRange / steps;
|
|
96
|
+
} else if (isValidInterval) {
|
|
97
|
+
// 3) user interval only
|
|
98
|
+
interval = resolvedInterval;
|
|
99
|
+
steps = Math.ceil(graphRange / interval);
|
|
100
|
+
while (steps > maxSteps) {
|
|
101
|
+
interval *= 2;
|
|
102
|
+
steps = Math.ceil(graphRange / interval);
|
|
103
|
+
}
|
|
104
|
+
graphMax = graphMin + (interval * steps);
|
|
105
|
+
} else {
|
|
106
|
+
// 4) 기존 로직
|
|
107
|
+
interval = this.getInterval(range);
|
|
108
|
+
let increase = minValue;
|
|
109
|
+
let numberOfSteps;
|
|
110
|
+
|
|
111
|
+
while (increase < maxValue) {
|
|
112
|
+
increase += interval;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
graphMax = increase;
|
|
116
|
+
|
|
117
|
+
numberOfSteps = Math.round(graphRange / interval);
|
|
118
|
+
|
|
119
|
+
while (numberOfSteps > maxSteps) {
|
|
120
|
+
interval *= 2;
|
|
121
|
+
numberOfSteps = Math.round(graphRange / interval);
|
|
122
|
+
const tempInterval = graphRange / numberOfSteps;
|
|
123
|
+
interval = this.decimalPoint ? tempInterval : Math.ceil(tempInterval);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (graphMax - graphMin > (numberOfSteps * interval)) {
|
|
127
|
+
const tempInterval = (graphMax - graphMin) / numberOfSteps;
|
|
128
|
+
interval = this.decimalPoint ? tempInterval : Math.ceil(tempInterval);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
steps = numberOfSteps;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return {
|
|
135
|
+
steps,
|
|
136
|
+
interval,
|
|
137
|
+
graphMin,
|
|
138
|
+
graphMax,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
47
141
|
}
|
|
48
142
|
|
|
49
143
|
export default TimeScale;
|