diff --git a/docs/views/zoomChart/example/ChartBrush.vue b/docs/views/zoomChart/example/ChartBrush.vue
index a535fccea..ca8d911f3 100644
--- a/docs/views/zoomChart/example/ChartBrush.vue
+++ b/docs/views/zoomChart/example/ChartBrush.vue
@@ -18,11 +18,6 @@
:options="chartOptions2"
/>
-
-
@@ -141,24 +136,6 @@ export default {
},
});
- const chartData3 = reactive({
- series: {
- series1: { name: 'series#1', fill: false, point: true },
- series2: { name: 'series#2', fill: false, point: true },
- series3: { name: 'series#3', fill: false, point: true },
- series4: { name: 'series#4', fill: false, point: true },
- series5: { name: 'series#5', fill: false, point: true },
- },
- labels: [],
- data: {
- series1: [],
- series2: [],
- series3: [],
- series4: [],
- series5: [],
- },
- });
-
const chartOptions = reactive({
type: 'line',
width: '100%',
@@ -215,61 +192,25 @@ export default {
},
});
- const chartOptions3 = reactive({
- type: 'line',
- width: '100%',
- title: {
- text: '그룹에 있는 차트 3',
- show: true,
- },
- legend: {
- show: false,
- position: 'right',
- },
- axesX: [{
- type: 'time',
- showGrid: false,
- timeFormat: 'HH:mm:ss',
- interval: 'second',
- }],
- axesY: [{
- type: 'linear',
- showGrid: true,
- startToZero: true,
- autoScaleRatio: 0.1,
- }],
- maxTip: {
- use: true,
- showIndicator: true,
- indicatorColor: '#FF0000',
- tipBackground: '#000000',
- tipTextColor: '#FFFFFF',
- },
- });
-
const brushOptions = reactive({
show: true,
chartIdx: 0,
- height: 90,
+ height: 100,
+ buttonColor: '#FF0000',
});
const brushOptions2 = reactive({
show: true,
chartIdx: 1,
- height: 90,
- });
+ height: 100,
+ buttonColor: '#FF0000',
- const brushOptions3 = reactive({
- show: true,
- chartIdx: 2,
- height: 90,
});
const addRandomChartData = () => {
timeValue = dayjs(timeValue).add(1, 'second');
chartData.labels.push(dayjs(timeValue));
chartData2.labels.push(dayjs(timeValue));
- chartData3.labels.push(dayjs(timeValue));
Object.values(chartData.data).forEach((seriesData) => {
seriesData.push(Math.floor(Math.random() * ((5000 - 5) + 1)) + 5);
@@ -278,10 +219,6 @@ export default {
Object.values(chartData2.data).forEach((seriesData) => {
seriesData.push(Math.floor(Math.random() * ((5000 - 5) + 1)) + 5);
});
-
- Object.values(chartData3.data).forEach((seriesData) => {
- seriesData.push(Math.floor(Math.random() * ((5000 - 5) + 1)) + 5);
- });
};
onMounted(() => {
@@ -304,7 +241,6 @@ export default {
init(chartData);
init(chartData2);
- init(chartData3);
for (let ix = 0; ix < Math.ceil(Math.random() * 100); ix++) {
addRandomChartData();
@@ -318,13 +254,11 @@ export default {
const onToggleBrush = () => {
brushOptions.show = !brushOptions.show;
brushOptions2.show = !brushOptions2.show;
- brushOptions3.show = !brushOptions3.show;
};
watch(isShowToggleLegend, (isShow) => {
chartOptions.legend.show = isShow;
chartOptions2.legend.show = isShow;
- chartOptions3.legend.show = isShow;
});
watch(isExpandChartArea, (isExpand) => {
@@ -347,13 +281,10 @@ export default {
chartGroupOptions,
chartData,
chartData2,
- chartData3,
chartOptions,
chartOptions2,
- chartOptions3,
brushOptions,
brushOptions2,
- brushOptions3,
isShowToggleLegend,
isExpandChartArea,
zoomRef,
diff --git a/src/components/chart/chart.core.js b/src/components/chart/chart.core.js
index b6c121a26..00444cd2e 100644
--- a/src/components/chart/chart.core.js
+++ b/src/components/chart/chart.core.js
@@ -57,6 +57,7 @@ class EvChart {
this.bufferCtx = this.bufferCanvas.getContext('2d');
this.overlayCanvas = document.createElement('canvas');
this.overlayCanvas.setAttribute('style', 'display: block; z-index: 2;');
+ this.overlayCanvas.setAttribute('class', 'overlay-canvas');
this.overlayCtx = this.overlayCanvas.getContext('2d');
this.pixelRatio = window.devicePixelRatio || 1;
diff --git a/src/components/chart/chartZoom.core.js b/src/components/chart/chartZoom.core.js
index f42dd167a..638235e80 100644
--- a/src/components/chart/chartZoom.core.js
+++ b/src/components/chart/chartZoom.core.js
@@ -105,7 +105,7 @@ export default class EvChartZoom {
zoomMoveEndIdx = zoomEndIdx + 1;
}
- this.isExecuteZoomAtToolbar = true;
+ this.isExecutedByToolbar = true;
this.executeZoom(zoomMoveStartIdx, zoomMoveEndIdx);
this.zoomAreaMemory.current[0] = [zoomMoveStartIdx, zoomMoveEndIdx];
}
@@ -117,7 +117,7 @@ export default class EvChartZoom {
const [zoomStartIdx, zoomEndIdx] = this.zoomAreaMemory[direction].pop();
- this.isExecuteZoomAtToolbar = true;
+ this.isExecutedByToolbar = true;
this.executeZoom(zoomStartIdx, zoomEndIdx);
this.setZoomAreaMemory(zoomStartIdx, zoomEndIdx, direction === 'previous' ? 'latest' : 'previous');
}
@@ -228,7 +228,7 @@ export default class EvChartZoom {
}
this.isAnimationFinish = false;
- this.isExecuteZoomAtToolbar = true;
+ this.isExecutedByToolbar = true;
this.executeDragZoomAnimation(
displayCanvas,
animationCtx,
@@ -275,8 +275,10 @@ export default class EvChartZoom {
);
}
- this.brushIdx.start = zoomStartIdx;
- this.brushIdx.end = zoomEndIdx;
+ if (!this.brushIdx.isExecutedByBrush) {
+ this.brushIdx.start = zoomStartIdx;
+ this.brushIdx.end = zoomEndIdx;
+ }
if (this.emitFunc) {
this.emitFunc.updateZoomStartIdx(zoomStartIdx);
@@ -469,7 +471,7 @@ export default class EvChartZoom {
const cloneLabelsLastIdx = this.cloneLabelsLastIdx;
if (currentZoomStartIdx !== 0 || currentZoomEndIdx !== cloneLabelsLastIdx) {
- this.isExecuteZoomAtToolbar = true;
+ this.isExecutedByToolbar = true;
this.executeZoom(0, cloneLabelsLastIdx);
this.setZoomAreaMemory(0, cloneLabelsLastIdx);
}
diff --git a/src/components/chart/plugins/plugins.interaction.js b/src/components/chart/plugins/plugins.interaction.js
index 835a6159e..30aa19ae5 100644
--- a/src/components/chart/plugins/plugins.interaction.js
+++ b/src/components/chart/plugins/plugins.interaction.js
@@ -18,6 +18,28 @@ const modules = {
return;
}
+ if (this.options.brush) {
+ if (!this.brushCanvas) {
+ if (e.path[0].nextSibling.className === 'brush-canvas') {
+ this.brushCanvas = e.path[0].nextSibling;
+ }
+ } else {
+ const isCurMouseXInsideBrushBtn = xPos =>
+ e.offsetX + this.evBrushChartPos.width >= this.evBrushChartPos[xPos]
+ && e.offsetX - this.evBrushChartPos.width <= this.evBrushChartPos[xPos];
+
+ if (isCurMouseXInsideBrushBtn('leftX')) {
+ this.overlayCanvas.style['z-index'] = 1;
+ this.brushCanvas.style['z-index'] = 2;
+ }
+
+ if (isCurMouseXInsideBrushBtn('rightX')) {
+ this.overlayCanvas.style['z-index'] = 1;
+ this.brushCanvas.style['z-index'] = 2;
+ }
+ }
+ }
+
const { indicator, tooltip, type } = this.options;
const offset = this.getMousePosition(e);
const hitInfo = this.findHitItem(offset);
diff --git a/src/components/chart/uses.js b/src/components/chart/uses.js
index e3299b5b9..2d6fee45f 100644
--- a/src/components/chart/uses.js
+++ b/src/components/chart/uses.js
@@ -304,7 +304,7 @@ export const useZoomModel = (
const evChartToolbarRef = ref();
const evChartZoomOptions = reactive({ zoom: evChartNormalizedOptions.zoom });
- const brushIdx = reactive({ start: 0, end: 0 });
+ const brushIdx = reactive({ start: 0, end: 0, isExecutedByBrush: false });
let evChartZoom = null;
const evChartInfo = reactive({
@@ -329,7 +329,6 @@ export const useZoomModel = (
use: isUseZoomMode.value,
getRangeInfo,
};
- option.chartIdx = idx;
if (isUseZoomMode.value) {
option.dragSelection = {
@@ -477,9 +476,9 @@ export const useZoomModel = (
};
const controlZoomIdx = (zoomStartIdx, zoomEndIdx) => {
- if (evChartZoom.isExecuteZoomAtToolbar) {
- evChartZoom.isExecuteZoomAtToolbar = false;
- return;
+ if (evChartZoom.isExecutedByToolbar && !brushIdx.isExecutedByBrush) {
+ evChartZoom.isExecutedByToolbar = false;
+ return;
}
if (isUseZoomMode.value) {
@@ -493,6 +492,7 @@ export const useZoomModel = (
evChartInfo,
evChartToolbarRef,
evChartClone,
+ isUseZoomMode,
brushIdx,
createEvChartZoom,
diff --git a/src/components/chartBrush/ChartBrush.vue b/src/components/chartBrush/ChartBrush.vue
index 39ae88fd8..ee4c52c6d 100644
--- a/src/components/chartBrush/ChartBrush.vue
+++ b/src/components/chartBrush/ChartBrush.vue
@@ -29,7 +29,8 @@ export default {
let evChartBrush = null;
const injectEvChartClone = inject('evChartClone', { data: [], options: [] });
- const injectBrushIdx = inject('brushIdx', { start: 0, end: 0 });
+ const injectBrushIdx = inject('brushIdx', { start: 0, end: 0, isExecutedByBrush: false });
+ const injectIsUseZoomMode = inject('isUseZoomMode', false);
const {
getNormalizedBrushOptions,
@@ -53,8 +54,8 @@ export default {
const evChartOption = computed(() => {
const option = {
...(injectEvChartClone.options ?? [])[evChartBrushOptions.value.chartIdx],
- chartIdx: evChartBrushOptions.value.chartIdx,
brush: true,
+ brushButtonColor: evChartBrushOptions.value.buttonColor,
height: evChartBrushOptions.value.height,
title: {
show: false,
@@ -129,6 +130,7 @@ export default {
evChart,
evChartData,
evChartOption,
+ injectIsUseZoomMode,
injectBrushIdx,
evChartBrushRef,
);
@@ -146,7 +148,7 @@ export default {
}
};
- watch(injectBrushIdx, () => {
+ watch(() => [injectBrushIdx.start, injectBrushIdx.end], () => {
if (evChartBrushRef.value) {
drawChartBrush();
}
diff --git a/src/components/chartBrush/chartBrush.core.js b/src/components/chartBrush/chartBrush.core.js
index d87a11193..7c87203c9 100644
--- a/src/components/chartBrush/chartBrush.core.js
+++ b/src/components/chartBrush/chartBrush.core.js
@@ -1,86 +1,222 @@
+import { throttle } from 'lodash-es';
+
export default class EvChartBrush {
- constructor(evChart, evChartData, evChartOption, brushIdx, evChartBrushRef) {
+ constructor(evChart, evChartData, evChartOption, isUseZoomMode, brushIdx, evChartBrushRef) {
this.evChart = evChart;
this.evChartData = evChartData;
this.evChartOption = evChartOption;
+ this.isUseZooMode = isUseZoomMode;
this.brushIdx = brushIdx;
this.evChartBrushRef = evChartBrushRef;
}
init(isResize) {
- const { chartRect, labelOffset } = this.evChart;
-
- if (chartRect && labelOffset) {
- if (this.brushIdx.start > this.brushIdx.end) {
- return;
- }
-
- const evChartRange = {
- x1: chartRect.x1 + labelOffset.left,
- x2: chartRect.x2 - labelOffset.right,
- y1: chartRect.y1 + labelOffset.top,
- y2: chartRect.y2 - labelOffset.bottom,
- };
-
- const existedBrushCanvas = this.evChartBrushRef.value.querySelector('.brush-canvas');
+ if (this.brushIdx.start > this.brushIdx.end) {
+ return;
+ }
- if (!existedBrushCanvas) {
- const brushCanvas = document.createElement('canvas');
+ const existedBrushCanvas = this.evChartBrushRef.value.querySelector('.brush-canvas');
- brushCanvas.setAttribute('class', 'brush-canvas');
- brushCanvas.setAttribute('style', 'display: block; z-index: 1;');
+ if (!existedBrushCanvas) {
+ const brushCanvas = document.createElement('canvas');
- const evChartBrushContainer = this.evChartBrushRef.value.querySelector('.ev-chart-brush-container');
- evChartBrushContainer.appendChild(brushCanvas);
+ brushCanvas.setAttribute('class', 'brush-canvas');
+ brushCanvas.setAttribute('style', 'display: block; z-index: 1; cursor: initial;');
- brushCanvas.style.position = 'absolute';
- brushCanvas.style.top = `${evChartRange.y1}px`;
- brushCanvas.style.left = `${evChartRange.x1}px`;
+ const evChartBrushContainer = this.evChartBrushRef.value.querySelector('.ev-chart-brush-container');
+ evChartBrushContainer.appendChild(brushCanvas);
- this.drawBrushRect(brushCanvas, evChartRange);
- } else {
- this.drawBrushRect(existedBrushCanvas, evChartRange, isResize);
- }
+ this.drawBrushRect(brushCanvas);
+ this.addEvent(brushCanvas);
+ } else {
+ this.drawBrushRect(existedBrushCanvas, isResize);
}
}
- drawBrushRect(canvas, evChartRange, isResize) {
+ drawBrushRect(brushCanvas, isResize) {
+ const { chartRect, labelOffset } = this.evChart;
+ if (!chartRect && !labelOffset) {
+ return;
+ }
+
+ const evChartRange = {
+ x1: chartRect.x1 + labelOffset.left,
+ x2: chartRect.x2 - labelOffset.right,
+ y1: chartRect.y1 + labelOffset.top,
+ y2: chartRect.y2 - labelOffset.bottom,
+ };
+
const pixelRatio = window.devicePixelRatio || 1;
- const brushCanvasWidth = evChartRange.x2 - evChartRange.x1;
+ const brushButtonWidth = 6;
+ const brushCanvasWidth = evChartRange.x2 - evChartRange.x1 + brushButtonWidth;
const brushCanvasHeight = evChartRange.y2 - evChartRange.y1;
- const isEqualWidth = canvas.width === Math.floor(
- (brushCanvasWidth) * pixelRatio,
- );
+ const isEqualWidth = brushCanvas.width === Math.floor(brushCanvasWidth * pixelRatio);
if (isResize && isEqualWidth) {
return;
}
const labelEndIdx = this.evChartData.value.labels.length - 1;
- const axesXInterval = brushCanvasWidth / labelEndIdx;
+ const axesXInterval = (evChartRange.x2 - evChartRange.x1) / labelEndIdx;
const brushRectX = this.brushIdx.start * axesXInterval * pixelRatio;
const brushRectWidth = (
brushCanvasWidth - (labelEndIdx - (this.brushIdx.end - this.brushIdx.start)) * axesXInterval
) * pixelRatio;
+ const brushRectHeight = this.evChartOption.value.height - evChartRange.y1;
+ const brushButtonLeftXPos = brushRectX;
+ const brushButtonRightXPos = brushRectX + brushRectWidth - brushButtonWidth;
+
+ this.evBrushChartPos = {
+ leftX: (brushButtonLeftXPos / pixelRatio) + evChartRange.x1,
+ rightX: (brushButtonRightXPos / pixelRatio) + evChartRange.x1,
+ width: brushButtonWidth,
+ leftLabelX: evChartRange.x1 - (brushButtonWidth / 2),
+ axesXInterval,
+ };
+ this.evChart.evBrushChartPos = this.evBrushChartPos;
+
+ if (!brushCanvas.style.position) {
+ brushCanvas.style.position = 'absolute';
+ brushCanvas.style.top = `${evChartRange.y1}px`;
+ brushCanvas.style.left = `${evChartRange.x1 - (brushButtonWidth / 2)}px`;
+ }
if (!isEqualWidth) {
- canvas.width = (brushCanvasWidth) * pixelRatio;
- canvas.style.width = `${brushCanvasWidth}px`;
- canvas.height = (brushCanvasHeight) * pixelRatio;
- canvas.style.height = `${brushCanvasHeight}px`;
+ brushCanvas.width = (brushCanvasWidth * pixelRatio);
+ brushCanvas.style.width = `${brushCanvasWidth}px`;
+ brushCanvas.height = brushCanvasHeight * pixelRatio;
+ brushCanvas.style.height = `${brushCanvasHeight}px`;
}
- const ctx = canvas.getContext('2d');
+ const ctx = brushCanvas.getContext('2d');
+
ctx.clearRect(
0,
0,
- (brushCanvasWidth) * pixelRatio,
- this.evChartOption.value.height - evChartRange.y1,
+ brushCanvasWidth * pixelRatio,
+ brushCanvasHeight * pixelRatio,
);
ctx.fillStyle = this.evChartOption.value.dragSelection.fillColor;
ctx.globalAlpha = this.evChartOption.value.dragSelection.opacity;
- ctx.fillRect(brushRectX, 0, brushRectWidth, this.evChartOption.value.height - evChartRange.y1);
+ ctx.fillRect(brushRectX, 0, brushRectWidth, brushRectHeight);
+
+ ctx.globalAlpha = 1;
+ ctx.fillStyle = this.evChartOption.value.brushButtonColor;
+ ctx.fillRect(brushButtonLeftXPos, 0, brushButtonWidth, brushRectHeight);
+ ctx.fillRect(brushButtonRightXPos, 0, brushButtonWidth, brushRectHeight);
+ }
+
+ addEvent(brushCanvas) {
+ if (!this.overlayCanvas) {
+ if (this.evChartBrushRef.value.querySelector('.overlay-canvas')) {
+ this.overlayCanvas = this.evChartBrushRef.value.querySelector('.overlay-canvas');
+ }
+ }
+
+ let isClickBrushButton = false;
+ let beforeMouseXPos = 0;
+ let curClickButtonType = null;
+
+ const onMouseMove = (e) => {
+ const evBrushChartPos = this.evBrushChartPos;
+
+ if (brushCanvas.style.cursor === 'initial' && this.isUseZooMode.value) {
+ brushCanvas.style.cursor = 'ew-resize';
+ }
+
+ if (isClickBrushButton) {
+ if (!this.isUseZooMode.value) {
+ return;
+ }
+
+ if (!curClickButtonType) {
+ this.brushIdx.isExecutedByBrush = true;
+ const calDisToCurMouseX = xPos => Math.abs(
+ evBrushChartPos[xPos] - evBrushChartPos.leftLabelX - e.offsetX,
+ );
+
+ curClickButtonType = calDisToCurMouseX('rightX') > calDisToCurMouseX('leftX') ? 'leftX' : 'rightX';
+ return;
+ }
+
+ const brushButtonSensitivity = evBrushChartPos.axesXInterval / 3;
+ if (e.offsetX > beforeMouseXPos) {
+ const isMoveRight = e.offsetX - (
+ evBrushChartPos[curClickButtonType] - evBrushChartPos.leftLabelX
+ ) > brushButtonSensitivity;
+
+ if (isMoveRight && curClickButtonType === 'leftX') {
+ if (this.brushIdx.start < this.brushIdx.end - 1) {
+ this.brushIdx.start += 1;
+ }
+ } else if (isMoveRight && curClickButtonType === 'rightX') {
+ if (this.brushIdx.end !== this.evChartData.value.labels.length - 1) {
+ this.brushIdx.end += 1;
+ }
+ }
+ } else if (e.offsetX < beforeMouseXPos) {
+ const isMoveLeft = evBrushChartPos[curClickButtonType]
+ - evBrushChartPos.leftLabelX - e.offsetX > brushButtonSensitivity;
+
+ if (isMoveLeft && curClickButtonType === 'leftX') {
+ if (this.brushIdx.start !== 0) {
+ this.brushIdx.start -= 1;
+ }
+ } else if (isMoveLeft && curClickButtonType === 'rightX') {
+ if (this.brushIdx.start < this.brushIdx.end - 1) {
+ this.brushIdx.end -= 1;
+ }
+ }
+ }
+
+ beforeMouseXPos = e.offsetX;
+ } else {
+ const moveRight = xPos =>
+ e.offsetX + evBrushChartPos.leftLabelX - evBrushChartPos.width > evBrushChartPos[xPos];
+ const moveLeft = xPos =>
+ e.offsetX + evBrushChartPos.leftLabelX + evBrushChartPos.width < evBrushChartPos[xPos];
+
+ const isCurMouseXOutsideBrush = moveLeft('leftX') || moveRight('rightX');
+ const isCurMouseXInsideBrush = moveRight('leftX') && moveLeft('rightX');
+
+ if (isCurMouseXOutsideBrush) {
+ this.overlayCanvas.style['z-index'] = 2;
+ brushCanvas.style['z-index'] = 1;
+ }
+
+ if (isCurMouseXInsideBrush) {
+ this.overlayCanvas.style['z-index'] = 2;
+ brushCanvas.style['z-index'] = 1;
+ }
+ }
+ };
+
+ const onMouseDown = (e) => {
+ e.preventDefault();
+ isClickBrushButton = true;
+ };
+
+ const initState = () => {
+ brushCanvas.style.cursor = 'initial';
+ this.brushIdx.isExecutedByBrush = false;
+ isClickBrushButton = false;
+ beforeMouseXPos = 0;
+ curClickButtonType = null;
+ };
+
+ const onMouseUp = () => {
+ initState();
+ };
+
+ const onMouseLeave = () => {
+ initState();
+ };
+
+ brushCanvas.addEventListener('mousemove', throttle(onMouseMove, 50));
+ brushCanvas.addEventListener('mousedown', onMouseDown);
+ brushCanvas.addEventListener('mouseup', onMouseUp);
+ brushCanvas.addEventListener('mouseleave', onMouseLeave);
}
}
diff --git a/src/components/chartBrush/uses.js b/src/components/chartBrush/uses.js
index f2e1c0fff..759f0e2ea 100644
--- a/src/components/chartBrush/uses.js
+++ b/src/components/chartBrush/uses.js
@@ -3,7 +3,8 @@ import { defaultsDeep } from 'lodash-es';
const DEFAULT_OPTIONS = {
show: true,
chartIdx: 0,
- height: 90,
+ height: 100,
+ buttonColor: '',
};
// eslint-disable-next-line import/prefer-default-export
diff --git a/src/components/chartGroup/ChartGroup.vue b/src/components/chartGroup/ChartGroup.vue
index 91765501c..3c97beea8 100644
--- a/src/components/chartGroup/ChartGroup.vue
+++ b/src/components/chartGroup/ChartGroup.vue
@@ -62,6 +62,7 @@ export default {
evChartInfo,
evChartToolbarRef,
evChartClone,
+ isUseZoomMode,
brushIdx,
createEvChartZoom,
@@ -72,6 +73,7 @@ export default {
} = useZoomModel(normalizedOptions, { wrapper: null, evChartGroupRef });
provide('evChartClone', evChartClone);
+ provide('isUseZoomMode', isUseZoomMode);
provide('brushIdx', brushIdx);
onMounted(() => {
@@ -89,7 +91,15 @@ export default {
}, { deep: true });
watch(() => [props.zoomStartIdx, props.zoomEndIdx], ([zoomStartIdx, zoomEndIdx]) => {
- controlZoomIdx(zoomStartIdx, zoomEndIdx);
+ if (!brushIdx.isExecutedByBrush) {
+ controlZoomIdx(zoomStartIdx, zoomEndIdx);
+ }
+ });
+
+ watch(() => [brushIdx.start, brushIdx.end], ([brushStartIdx, brushEndIdx]) => {
+ if (brushIdx.isExecutedByBrush) {
+ controlZoomIdx(brushStartIdx, brushEndIdx);
+ }
});
return {