UNPKG

@syncfusion/ej2-charts

Version:

Feature-rich chart control with built-in support for over 25 chart types, technical indictors, trendline, zooming, tooltip, selection, crosshair and trackball.

1,030 lines (1,029 loc) 51.3 kB
import { EventHandler, Browser, createElement, isNullOrUndefined } from '@syncfusion/ej2-base'; import { getRectLocation, minMax, getElement, ChartLocation, RectOption } from '../../common/utils/helper'; import { Rect, measureText } from '@syncfusion/ej2-svg-base'; import { Toolkit } from './zooming-toolkit'; import { zoomComplete, onZooming } from '../../common/model/constants'; import { withInBounds } from '../../common/utils/helper'; /** * The `Zooming` module handles zooming functionality for charts. */ var Zoom = /** @class */ (function () { /** * Constructor for Zooming module. * * @private */ function Zoom(chart) { this.zoomCompleteEvtCollection = []; /** @private */ this.startPanning = false; this.chart = chart; this.isPointer = Browser.isPointer; this.browserName = Browser.info.name; this.wheelEvent = this.browserName === 'mozilla' ? (this.isPointer ? 'mousewheel' : 'DOMMouseScroll') : 'mousewheel'; this.cancelEvent = this.isPointer ? 'pointerleave' : 'mouseleave'; this.addEventListener(); this.isDevice = Browser.isDevice; var zooming = chart.zoomSettings; this.toolkit = new Toolkit(chart); this.zooming = zooming; this.elementId = chart.element.id; this.zoomingRect = new Rect(0, 0, 0, 0); this.zoomAxes = []; this.zoomkitOpacity = 1; this.isIOS = Browser.isIos || Browser.isIos7; this.isZoomed = this.performedUI = this.zooming.enablePan || ((this.chart.primaryXAxis.zoomFactor < 1 && this.chart.primaryXAxis.zoomPosition > 0) || (this.chart.primaryYAxis.zoomFactor < 1 && this.chart.primaryYAxis.zoomPosition > 0) || this.isAxisZoomed(this.chart.axes)); if (zooming.enableScrollbar) { chart.scrollElement = createElement('div', { id: chart.element.id + '_scrollElement' }); } } /** * Renders the zooming functionality for the chart. * * @param {PointerEvent | TouchEvent} e - The pointer or touch event. * @param {Chart} chart - The chart instance. * @param {boolean} isTouch - Indicates whether the event is a touch event. * @returns {void} * @private */ Zoom.prototype.renderZooming = function (e, chart, isTouch) { this.calculateZoomAxesRange(chart); if (this.zooming.enableSelectionZooming && (!isTouch || (chart.isDoubleTap && this.touchStartList.length === 1)) && (!this.isPanning || chart.isDoubleTap)) { this.isPanning = this.isDevice ? true : this.isPanning; this.performedUI = true; this.drawZoomingRectangle(chart); } else if (this.isPanning && chart.isChartDrag) { if (!isTouch || (isTouch && this.touchStartList.length === 1)) { this.pinchTarget = isTouch ? e.target : null; this.doPan(chart, chart.axisCollections); } } }; // Zooming rectangle drawn here Zoom.prototype.drawZoomingRectangle = function (chart) { var areaBounds = chart.chartAxisLayoutPanel.seriesClipRect; var startLocation = new ChartLocation(chart.previousMouseMoveX, chart.previousMouseMoveY); var endLocation = new ChartLocation(chart.mouseX, chart.mouseY); var rect = this.zoomingRect = getRectLocation(startLocation, endLocation, areaBounds); if (rect.width > 0 && rect.height > 0) { this.isZoomed = true; chart.disableTrackTooltip = true; chart.svgObject.setAttribute('cursor', 'crosshair'); if (this.zooming.mode === 'X') { rect.height = areaBounds.height; rect.y = areaBounds.y; } else if (this.zooming.mode === 'Y') { rect.width = areaBounds.width; rect.x = areaBounds.x; } if (chart.tooltipModule) { chart.tooltipModule.removeTooltip(0); for (var _i = 0, _a = chart.visibleSeries; _i < _a.length; _i++) { var series = _a[_i]; if (!isNullOrUndefined(series) && (series.marker.visible || chart.tooltip.shared)) { chart.markerRender.removeHighlightedMarker(series, null, true); } } } if (chart.crosshairModule) { chart.crosshairModule.removeCrosshair(0); } var svg = chart.svgObject; if (this.chart.enableCanvas) { var secondaryElement = document.getElementById(this.chart.element.id + '_Secondary_Element'); svg = this.chart.svgRenderer.createSvg({ id: this.chart.element.id + '_zoomRect_svg', width: this.chart.availableSize.width, height: this.chart.availableSize.height }); svg.style.cssText = 'position: absolute; display:block; pointer-events: none'; secondaryElement.appendChild(svg); } svg.appendChild(chart.svgRenderer.drawRectangle(new RectOption(this.elementId + '_ZoomArea', chart.themeStyle.selectionRectFill, { color: chart.themeStyle.selectionRectStroke, width: 1 }, 1, rect, 0, 0, '', '3'))); } }; // Panning performed here Zoom.prototype.doPan = function (chart, axes, xDifference, yDifference) { var _this = this; if (xDifference === void 0) { xDifference = 0; } if (yDifference === void 0) { yDifference = 0; } if (chart.startMove && chart.crosshair.enable) { return null; } var currentScale; var offset; this.isZoomed = true; this.startPanning = true; this.offset = !chart.delayRedraw ? chart.chartAxisLayoutPanel.seriesClipRect : this.offset; chart.delayRedraw = true; this.zoomCompleteEvtCollection = []; chart.disableTrackTooltip = true; var argsData; var zoomedAxisCollection = []; for (var _i = 0, _a = axes; _i < _a.length; _i++) { var axis = _a[_i]; argsData = { cancel: false, name: zoomComplete, axis: axis, previousZoomFactor: axis.zoomFactor, previousZoomPosition: axis.zoomPosition, currentZoomFactor: axis.zoomFactor, currentZoomPosition: axis.zoomPosition, previousVisibleRange: axis.visibleRange, currentVisibleRange: null }; currentScale = Math.max(1 / minMax(axis.zoomFactor, 0, 1), 1); if (axis.orientation === 'Horizontal') { offset = (xDifference !== 0 ? xDifference : (chart.previousMouseMoveX - chart.mouseX)) / axis.rect.width / currentScale; argsData.currentZoomPosition = minMax(axis.zoomPosition + offset, 0, (1 - axis.zoomFactor)); } else { offset = (yDifference !== 0 ? yDifference : (chart.previousMouseMoveY - chart.mouseY)) / axis.rect.height / currentScale; argsData.currentZoomPosition = minMax(axis.zoomPosition - offset, 0, (1 - axis.zoomFactor)); } if (!argsData.cancel) { axis.zoomFactor = argsData.currentZoomFactor; axis.zoomPosition = argsData.currentZoomPosition; this.zoomCompleteEvtCollection.push(argsData); } zoomedAxisCollection.push({ zoomFactor: axis.zoomFactor, zoomPosition: axis.zoomFactor, axisName: axis.name, axisRange: axis.visibleRange }); } if (chart.tooltipModule) { var tooltipElement = getElement(chart.element.id + '_tooltip'); if (tooltipElement) { tooltipElement.remove(); } for (var _b = 0, _c = chart.visibleSeries; _b < _c.length; _b++) { var series = _c[_b]; if (!isNullOrUndefined(series) && (series.marker.visible || chart.tooltip.shared || series.type === 'Scatter' || series.type === 'Bubble')) { chart.markerRender.removeHighlightedMarker(series, null, true); } } } var zoomingEventArgs = { cancel: false, axisCollection: zoomedAxisCollection, name: onZooming }; this.chart.trigger(onZooming, zoomingEventArgs, function () { if (zoomingEventArgs.cancel) { _this.zoomCancel(axes, _this.zoomCompleteEvtCollection); } else { _this.performDefferedZoom(chart); _this.redrawOnZooming(chart, false); } }); }; Zoom.prototype.performDefferedZoom = function (chart) { var translateX; var translateY; if (this.zooming.enableDeferredZooming) { translateX = chart.mouseX - chart.mouseDownX; translateY = chart.mouseY - chart.mouseDownY; switch (this.zooming.mode) { case 'X': translateY = 0; break; case 'Y': translateX = 0; break; } this.setTransform(translateX, translateY, null, null, chart, false); this.refreshAxis(chart.chartAxisLayoutPanel, chart, chart.axisCollections); if (chart.enableCanvas) { this.performZoomRedraw(chart); } } else { this.performZoomRedraw(chart); } chart.previousMouseMoveX = chart.mouseX; chart.previousMouseMoveY = chart.mouseY; }; /** * Redraw the chart on zooming. * * @param {Chart} chart - The chart instance. * @returns {void} * @private */ Zoom.prototype.performZoomRedraw = function (chart) { var rect = this.zoomingRect; chart.animateSeries = false; if (this.isZoomed) { if (rect.width > 0 && rect.height > 0) { this.performedUI = true; chart.svgObject.setAttribute('cursor', 'auto'); this.doZoom(chart, chart.axisCollections, chart.chartAxisLayoutPanel.seriesClipRect); chart.isDoubleTap = false; } else if (chart.disableTrackTooltip) { chart.disableTrackTooltip = false; chart.delayRedraw = false; if (chart.enableCanvas) { chart.createChartSvg(); } else { var zoomArea = getElement(chart.element.id + '_ZoomArea'); if (zoomArea) { zoomArea.remove(); } var zoomToolBar = getElement(chart.element.id + '_Zooming_KitCollection'); if (zoomToolBar) { zoomToolBar.remove(); } if (chart.tooltipModule) { if (getElement(chart.element.id + '_tooltip')) { getElement(chart.element.id + '_tooltip').remove(); } for (var _i = 0, _a = chart.visibleSeries; _i < _a.length; _i++) { var series = _a[_i]; if (!isNullOrUndefined(series) && (series.marker.visible || chart.tooltip.shared || series.type === 'Scatter' || series.type === 'Bubble')) { chart.markerRender.removeHighlightedMarker(series, null, true); } } } } var chartDuration = chart.duration; if (!(this.isPanning && (chart.isChartDrag || this.startPanning)) && !chart.enableCanvas) { chart.duration = 600; chart.redraw = this.zooming.enableAnimation; chart.zoomRedraw = this.zooming.enableAnimation; } var highlightDataIndexes = []; if (chart.highlightModule && (chart.legendSettings.enableHighlight || chart.highlightMode !== 'None') && chart.highlightModule.highlightDataIndexes) { highlightDataIndexes = chart.highlightModule.highlightDataIndexes; } // chart.enableCanvas ? chart.createChartSvg() : chart.removeSvg(); chart.refreshAxis(); chart.refreshBound(); if (chart.highlightModule && (chart.legendSettings.enableHighlight || chart.highlightMode !== 'None') && highlightDataIndexes) { chart.highlightModule.highlightDataIndexes = highlightDataIndexes; } if (!this.isZoomed) { chart.zoomRedraw = this.zooming.enableAnimation; } this.startPanning = false; chart.redraw = false; chart.duration = chartDuration; if (this.toolkit.isZoomed) { chart.zoomRedraw = false; this.toolkit.isZoomed = false; } } } }; Zoom.prototype.refreshAxis = function (layout, chart, axes) { var mode = chart.zoomSettings.mode; layout.measureAxis(new Rect(chart.initialClipRect.x, chart.initialClipRect.y, chart.initialClipRect.width, chart.initialClipRect.height)); axes.map(function (axis, index) { if (axis.orientation === 'Horizontal' && mode !== 'Y') { layout.drawXAxisLabels(axis, index, null, (axis.placeNextToAxisLine ? axis.updatedRect : axis.rect)); } if (axis.orientation === 'Vertical' && mode !== 'X') { layout.drawYAxisLabels(axis, index, null, (axis.placeNextToAxisLine ? axis.updatedRect : axis.rect)); } }); }; // Rectangular zoom calculated here performed here Zoom.prototype.doZoom = function (chart, axes, bounds) { var _this = this; var zoomRect = this.zoomingRect; var mode = this.zooming.mode; var argsData; this.isPanning = chart.zoomSettings.enablePan || this.isPanning; var zoomedAxisCollections = []; this.zoomCompleteEvtCollection = []; for (var _i = 0, _a = axes; _i < _a.length; _i++) { var axis = _a[_i]; argsData = { cancel: false, name: zoomComplete, axis: axis, previousZoomFactor: axis.zoomFactor, previousZoomPosition: axis.zoomPosition, currentZoomFactor: axis.zoomFactor, currentZoomPosition: axis.zoomPosition, previousVisibleRange: axis.visibleRange, currentVisibleRange: null }; if (axis.orientation === 'Horizontal') { if (mode !== 'Y') { argsData.currentZoomPosition += Math.abs((zoomRect.x - bounds.x) / (bounds.width)) * axis.zoomFactor; argsData.currentZoomFactor *= (zoomRect.width / bounds.width); } } else { if (mode !== 'X') { argsData.currentZoomPosition += (1 - Math.abs((zoomRect.height + (zoomRect.y - bounds.y)) / (bounds.height))) * axis.zoomFactor; argsData.currentZoomFactor *= (zoomRect.height / bounds.height); } } if (parseFloat(argsData.currentZoomFactor.toFixed(3)) <= 0.001) { argsData.currentZoomFactor = argsData.previousZoomFactor; argsData.currentZoomPosition = argsData.previousZoomPosition; } if (!argsData.cancel) { axis.zoomFactor = argsData.currentZoomFactor; axis.zoomPosition = argsData.currentZoomPosition; chart.zoomRedraw = this.zooming.enableAnimation; this.zoomCompleteEvtCollection.push(argsData); } zoomedAxisCollections.push({ zoomFactor: axis.zoomFactor, zoomPosition: axis.zoomFactor, axisName: axis.name, axisRange: axis.visibleRange }); } var onZoomingEventArg = { cancel: false, axisCollection: zoomedAxisCollections, name: onZooming }; this.chart.trigger(onZooming, onZoomingEventArg, function () { if (onZoomingEventArg.cancel) { _this.zoomCancel(axes, _this.zoomCompleteEvtCollection); } else { _this.zoomingRect = new Rect(0, 0, 0, 0); _this.redrawOnZooming(chart); } }); }; /** * Redraws the chart on zooming. * * @param {Chart} chart - The chart instance. * @param {boolean} [isRedraw=true] - Indicates whether to redraw the chart. * @param {boolean} [isMouseUp=false] - Indicates whether the mouse button is released. * @returns {void} */ Zoom.prototype.redrawOnZooming = function (chart, isRedraw, isMouseUp) { if (isRedraw === void 0) { isRedraw = true; } if (isMouseUp === void 0) { isMouseUp = false; } var zoomCompleteCollection = isMouseUp ? this.toolkit.zoomCompleteEvtCollection : this.zoomCompleteEvtCollection; if (isRedraw) { this.performZoomRedraw(chart); } var argsData; for (var i = 0; i < zoomCompleteCollection.length; i++) { if (!zoomCompleteCollection[i].cancel) { argsData = { cancel: false, name: zoomComplete, axis: chart.axisCollections[i], previousZoomFactor: zoomCompleteCollection[i].previousZoomFactor, previousZoomPosition: zoomCompleteCollection[i].previousZoomPosition, currentZoomFactor: chart.axisCollections[i].zoomFactor, currentZoomPosition: chart.axisCollections[i].zoomPosition, currentVisibleRange: chart.axisCollections[i].visibleRange, previousVisibleRange: zoomCompleteCollection[i].previousVisibleRange }; chart.trigger(zoomComplete, argsData); } } }; /** * Performs mouse wheel zooming on the chart. * * @param {WheelEvent} e - The wheel event. * @param {number} mouseX - The X-coordinate of the mouse pointer. * @param {number} mouseY - The Y-coordinate of the mouse pointer. * @param {Chart} chart - The chart instance. * @param {AxisModel[]} axes - The axes in the chart. * @returns {void} * @private */ Zoom.prototype.performMouseWheelZooming = function (e, mouseX, mouseY, chart, axes) { var _this = this; var direction = (this.browserName === 'mozilla' && !this.isPointer) ? -(e.detail) / 3 > 0 ? 1 : -1 : (e['wheelDelta'] > 0 ? 1 : -1); var mode = this.zooming.mode; var origin = 0.5; var cumulative; var zoomFactor; var zoomPosition; this.isZoomed = true; this.calculateZoomAxesRange(chart); chart.disableTrackTooltip = true; this.performedUI = true; this.isPanning = chart.zoomSettings.enablePan || this.isPanning; this.zoomCompleteEvtCollection = []; var argsData; var zoomedAxisCollection = []; for (var _i = 0, _a = axes; _i < _a.length; _i++) { var axis = _a[_i]; argsData = { cancel: false, name: zoomComplete, axis: axis, previousZoomFactor: axis.zoomFactor, previousZoomPosition: axis.zoomPosition, currentZoomFactor: axis.zoomFactor, currentZoomPosition: axis.zoomPosition, currentVisibleRange: null, previousVisibleRange: axis.visibleRange }; if ((axis.orientation === 'Vertical' && mode !== 'X') || (axis.orientation === 'Horizontal' && mode !== 'Y')) { cumulative = Math.max(Math.max(1 / minMax(axis.zoomFactor, 0, 1), 1) + (0.25 * direction), 1); cumulative = (cumulative > 50000000000) ? 50000000000 : cumulative; if (cumulative >= 1) { origin = axis.orientation === 'Horizontal' ? mouseX / axis.rect.width : 1 - (mouseY / axis.rect.height); origin = origin > 1 ? 1 : origin < 0 ? 0 : origin; zoomFactor = (cumulative === 1) ? 1 : minMax((direction > 0 ? 0.9 : 1.1) / cumulative, 0, 1); zoomPosition = (cumulative === 1) ? 0 : axis.zoomPosition + ((axis.zoomFactor - zoomFactor) * origin); if (axis.zoomPosition !== zoomPosition || axis.zoomFactor !== zoomFactor) { zoomFactor = (zoomPosition + zoomFactor) > 1 ? (1 - zoomPosition) : zoomFactor; } if (parseFloat(argsData.currentZoomFactor.toFixed(3)) <= 0.001) { argsData.currentZoomFactor = argsData.previousZoomFactor; argsData.currentZoomPosition = argsData.previousZoomPosition; } else { argsData.currentZoomFactor = zoomFactor; argsData.currentZoomPosition = zoomPosition; } } if (argsData.currentZoomFactor === argsData.previousZoomFactor && argsData.currentZoomPosition === argsData.previousZoomPosition) { chart.disableTrackTooltip = false; } if (!argsData.cancel) { axis.zoomFactor = argsData.currentZoomFactor; axis.zoomPosition = argsData.currentZoomPosition; chart.zoomRedraw = this.zooming.enableAnimation; this.zoomCompleteEvtCollection.push(argsData); } } zoomedAxisCollection.push({ zoomFactor: axis.zoomFactor, zoomPosition: axis.zoomFactor, axisName: axis.name, axisRange: axis.visibleRange }); } var onZoomingEventArgs = { cancel: false, axisCollection: zoomedAxisCollection, name: onZooming }; this.chart.trigger(onZooming, onZoomingEventArgs, function () { if (onZoomingEventArgs.cancel) { _this.zoomCancel(axes, _this.zoomCompleteEvtCollection); } else { _this.redrawOnZooming(chart); } }); }; /** * Performs pinch zooming on the chart. * * @param {TouchEvent} e - The touch event. * @param {Chart} chart - The chart instance. * @returns {boolean} - Indicates whether pinch zooming is performed. * @private */ Zoom.prototype.performPinchZooming = function (e, chart) { if ((this.zoomingRect.width > 0 && this.zoomingRect.height > 0) || (chart.startMove && chart.crosshair.enable)) { return false; } this.calculateZoomAxesRange(chart); this.isZoomed = true; this.isPanning = true; this.performedUI = true; this.offset = !chart.delayRedraw ? chart.chartAxisLayoutPanel.seriesClipRect : this.offset; chart.delayRedraw = true; chart.disableTrackTooltip = true; var elementOffset = chart.element.getBoundingClientRect(); var touchDown = this.touchStartList; var touchMove = this.touchMoveList; var touch0StartX = touchDown[0].pageX - elementOffset.left; var touch0StartY = touchDown[0].pageY - elementOffset.top; var touch0EndX = touchMove[0].pageX - elementOffset.left; var touch0EndY = touchMove[0].pageY - elementOffset.top; var touch1StartX = touchDown[1].pageX - elementOffset.left; var touch1StartY = touchDown[1].pageY - elementOffset.top; var touch1EndX = touchMove[1].pageX - elementOffset.left; var touch1EndY = touchMove[1].pageY - elementOffset.top; var scaleX = Math.abs(touch0EndX - touch1EndX) / Math.abs(touch0StartX - touch1StartX); var scaleY = Math.abs(touch0EndY - touch1EndY) / Math.abs(touch0StartY - touch1StartY); var clipX = ((this.offset.x - touch0EndX) / scaleX) + touch0StartX; var clipY = ((this.offset.y - touch0EndY) / scaleY) + touch0StartY; var pinchRect = new Rect(clipX, clipY, this.offset.width / scaleX, this.offset.height / scaleY); var translateXValue = (touch0EndX - (scaleX * touch0StartX)); var translateYValue = (touch0EndY - (scaleY * touch0StartY)); if (!isNaN(scaleX - scaleX) && !isNaN(scaleY - scaleY)) { switch (this.zooming.mode) { case 'XY': this.setTransform(translateXValue, translateYValue, scaleX, scaleY, chart, true); break; case 'X': this.setTransform(translateXValue, 0, scaleX, 1, chart, true); break; case 'Y': this.setTransform(0, translateYValue, 1, scaleY, chart, true); break; } } if (!this.calculatePinchZoomFactor(chart, pinchRect)) { this.refreshAxis(chart.chartAxisLayoutPanel, chart, chart.axisCollections); this.redrawOnZooming(chart, false); } return true; }; Zoom.prototype.calculatePinchZoomFactor = function (chart, pinchRect) { var mode = this.zooming.mode; var selectionMin; var selectionMax; var rangeMin; var rangeMax; var value; var axisTrans; var argsData; var currentZF; var currentZP; var zoomedAxisCollection = []; this.zoomCompleteEvtCollection = []; for (var index = 0; index < chart.axisCollections.length; index++) { var axis = chart.axisCollections[index]; if ((axis.orientation === 'Horizontal' && mode !== 'Y') || (axis.orientation === 'Vertical' && mode !== 'X')) { currentZF = axis.zoomFactor; currentZP = axis.zoomPosition; argsData = { cancel: false, name: zoomComplete, axis: axis, previousZoomFactor: axis.zoomFactor, previousZoomPosition: axis.zoomPosition, currentZoomFactor: currentZF, currentZoomPosition: currentZP, previousVisibleRange: axis.visibleRange, currentVisibleRange: null }; if (axis.orientation === 'Horizontal') { value = pinchRect.x - this.offset.x; axisTrans = axis.rect.width / this.zoomAxes[index].delta; rangeMin = value / axisTrans + this.zoomAxes[index].min; value = pinchRect.x + pinchRect.width - this.offset.x; rangeMax = value / axisTrans + this.zoomAxes[index].min; } else { value = pinchRect.y - this.offset.y; axisTrans = axis.rect.height / this.zoomAxes[index].delta; rangeMin = (value * -1 + axis.rect.height) / axisTrans + this.zoomAxes[index].min; value = pinchRect.y + pinchRect.height - this.offset.y; rangeMax = (value * -1 + axis.rect.height) / axisTrans + this.zoomAxes[index].min; } selectionMin = Math.min(rangeMin, rangeMax); selectionMax = Math.max(rangeMin, rangeMax); currentZP = (selectionMin - this.zoomAxes[index].actualMin) / this.zoomAxes[index].actualDelta; currentZF = (selectionMax - selectionMin) / this.zoomAxes[index].actualDelta; argsData.currentZoomPosition = currentZP < 0 ? 0 : currentZP; argsData.currentZoomFactor = currentZF > 1 ? 1 : (currentZF < 0.03) ? 0.03 : currentZF; if (!argsData.cancel) { axis.zoomFactor = argsData.currentZoomFactor; axis.zoomPosition = argsData.currentZoomPosition; chart.zoomRedraw = this.zooming.enableAnimation; this.zoomCompleteEvtCollection.push(argsData); } zoomedAxisCollection.push({ zoomFactor: axis.zoomFactor, zoomPosition: axis.zoomFactor, axisName: axis.name, axisRange: axis.visibleRange }); } } var onZoomingEventArgs = { cancel: false, axisCollection: zoomedAxisCollection, name: onZooming }; if (!onZoomingEventArgs.cancel) { this.chart.trigger(onZooming, onZoomingEventArgs); if (onZoomingEventArgs.cancel) { this.zoomCancel(chart.axisCollections, this.zoomCompleteEvtCollection); return true; } } return false; }; // Series transformation style applied here. Zoom.prototype.setTransform = function (transX, transY, scaleX, scaleY, chart, isPinch) { if (!chart.enableCanvas) { chart.seriesElements.setAttribute('clip-path', 'url(#' + this.elementId + '_ChartAreaClipRect_)'); } if (chart.indicatorElements) { chart.indicatorElements.setAttribute('clip-path', 'url(#' + this.elementId + '_ChartAreaClipRect_)'); } var translate; var xAxisLoc; var yAxisLoc; var element; if (transX !== null && transY !== null) { for (var _i = 0, _a = chart.visibleSeries; _i < _a.length; _i++) { var value = _a[_i]; xAxisLoc = chart.requireInvertedAxis ? value.yAxis.rect.x : value.xAxis.rect.x; yAxisLoc = chart.requireInvertedAxis ? value.xAxis.rect.y : value.yAxis.rect.y; translate = 'translate(' + (transX + (isPinch ? (scaleX * xAxisLoc) : xAxisLoc)) + ',' + (transY + (isPinch ? (scaleY * yAxisLoc) : yAxisLoc)) + ')'; translate = (scaleX || scaleY) ? translate + ' scale(' + scaleX + ' ' + scaleY + ')' : translate; if (value.visible) { if (value.category === 'Indicator') { value.seriesElement.parentNode.setAttribute('transform', translate); } else { if (!chart.enableCanvas) { value.seriesElement.setAttribute('transform', translate); } } element = getElement(chart.element.id + '_Series_' + value.index + '_DataLabelCollections'); if (value.errorBarElement) { value.errorBarElement.setAttribute('transform', translate); } if (value.symbolElement) { value.symbolElement.setAttribute('transform', translate); } if (value.textElement) { value.textElement.setAttribute('visibility', 'hidden'); value.shapeElement.setAttribute('visibility', 'hidden'); } if (element) { element.style.visibility = 'hidden'; } } } } }; Zoom.prototype.calculateZoomAxesRange = function (chart) { var range; var axisRange; for (var index = 0; index < chart.axisCollections.length; index++) { var axis = chart.axisCollections[index]; axisRange = axis.visibleRange; if (this.zoomAxes[index]) { if (!chart.delayRedraw) { this.zoomAxes[index].min = axisRange.min; this.zoomAxes[index].delta = axisRange.delta; } } else { range = { actualMin: axis.actualRange.min, actualDelta: axis.actualRange.delta, min: axisRange.min, delta: axisRange.delta }; this.zoomAxes[index] = range; } } }; // Zooming Toolkit created here Zoom.prototype.showZoomingToolkit = function (chart) { var toolboxItems = this.zooming.toolbarItems; var areaBounds = chart.chartAxisLayoutPanel.seriesClipRect; var spacing = 10; var render = chart.svgRenderer; var length = this.isDevice ? (toolboxItems.length === 0 ? 0 : 1) : toolboxItems.length; var iconSize = this.isDevice ? measureText('Reset Zoom', { size: '12px' }, { size: '12px', fontStyle: 'Normal', fontWeight: '400', fontFamily: 'Segoe UI' }).width : 16; var height = this.isDevice ? measureText('Reset Zoom', { size: '12px' }, { size: '12px', fontStyle: 'Normal', fontWeight: '400', fontFamily: 'Segoe UI' }).height : chart.theme.indexOf('Fluent2') > -1 || chart.theme.indexOf('Bootstrap5') > -1 ? 18 : 22; var width = (length * iconSize) + ((length + 1) * spacing) + ((length - 1) * spacing); var toolbarPosition = this.zooming.toolbarPosition; var transX; var transY; switch (toolbarPosition.horizontalAlignment) { case 'Far': transX = areaBounds.x + areaBounds.width - width - spacing; break; case 'Near': transX = areaBounds.x + spacing; break; case 'Center': transX = (areaBounds.width / 2) - (width / 2) + areaBounds.x; break; } transX += toolbarPosition.x; switch (toolbarPosition.verticalAlignment) { case 'Bottom': transY = areaBounds.height - areaBounds.y + height + spacing; break; case 'Top': transY = areaBounds.y + spacing; break; case 'Middle': transY = (areaBounds.height / 2) - (height / 2) + areaBounds.y; break; } var toolkitShadowPadding = 2; transY += toolbarPosition.y; transX = this.toolkit.dragHorizontalRatio != null ? Math.min(Math.max(this.chart.border.width + toolkitShadowPadding, this.toolkit.dragHorizontalRatio * this.chart.availableSize.width), this.chart.availableSize.width - width - this.chart.border.width - toolkitShadowPadding) : transX; transY = this.toolkit.dragVerticalRatio != null ? Math.min(Math.max(this.chart.border.width + toolkitShadowPadding, this.toolkit.dragVerticalRatio * this.chart.availableSize.height), this.chart.availableSize.height - height - this.chart.border.width - toolkitShadowPadding) : transY; var xPosition = spacing; var toolkit = this.toolkit; var element; var shadowElement = '<filter id="chart_shadow" height="130%"><feGaussianBlur in="SourceAlpha" stdDeviation="5"/>'; shadowElement += '<feOffset dx="-3" dy="4" result="offsetblur"/><feComponentTransfer><feFuncA type="linear" slope="1"/>'; shadowElement += '</feComponentTransfer><feMerge><feMergeNode/><feMergeNode in="SourceGraphic"/></feMerge></filter>'; if (length === 0 || getElement(this.elementId + '_Zooming_KitCollection')) { return false; } var defElement = render.createDefs(); toolboxItems = this.isDevice ? ['Reset'] : toolboxItems; defElement.innerHTML = shadowElement; this.toolkitElements = render.createGroup({ id: this.elementId + '_Zooming_KitCollection', transform: 'translate(' + transX + ',' + transY + ')' }); this.toolkitElements.appendChild(defElement); var zoomFillColor = this.chart.theme === 'Tailwind3' ? '#F9FAFB' : this.chart.theme === 'Fluent' ? '#F3F2F1' : (this.chart.theme === 'Material3' ? '#FFFFFF' : this.chart.theme === 'Material3Dark' ? '#1C1B1F' : this.chart.theme === 'Fluent2' ? '#F5F5F5' : this.chart.theme === 'Fluent2Dark' ? '#141414' : chart.theme === 'Fluent2HighContrast' ? '#000000' : chart.theme === 'Bootstrap5' ? '#E9ECEF' : chart.theme === 'Bootstrap5Dark' ? '#343A40' : (chart.theme === 'Tailwind3Dark' && !this.isDevice) ? '#1D2432' : this.chart.theme === 'Tailwind' ? '#F3F4F6' : '#fafafa'); this.toolkitElements.appendChild(render.drawRectangle(new RectOption(this.elementId + '_Zooming_Rect', zoomFillColor, { color: 'transparent', width: 1 }, 1, new Rect(0, 0, width, (height + (spacing * 2))), this.chart.theme.indexOf('Bootstrap5') > -1 ? 1 : 4, this.chart.theme.indexOf('Bootstrap5') > -1 ? 1 : 4))); var outerElement = render.drawRectangle(new RectOption(this.elementId + '_Zooming_Rect', zoomFillColor, { color: 'transparent', width: 1 }, 0.1, new Rect(0, 0, width, (height + (spacing * 2))), 4, 4)); if (this.chart.theme === 'Tailwind' || this.chart.theme === 'TailwindDark') { outerElement.setAttribute('box-shadow', '0px 1px 2px rgba(0, 0, 0, 0.06), 0px 1px 3px rgba(0, 0, 0, 0.1)'); } else if (this.chart.theme === 'Tailwind3Dark') { outerElement.setAttribute('box-shadow', '0px 1px 2px rgba(0, 0, 0, 0.06), 0px 1px 3px rgba(0, 0, 0, 0.1)'); } else if (this.chart.theme === 'Material3' || this.chart.theme === 'Material3Dark' || this.chart.theme === 'Fluent2' || this.chart.theme === 'Fluent2Dark' || this.chart.theme.indexOf('Bootstrap5') > -1) { outerElement.setAttribute('filter', 'drop-shadow(0px 1px 3px rgba(0, 0, 0, 0.15)) drop-shadow(0px 1px 2px rgba(0, 0, 0, 0.3))'); outerElement.setAttribute('fill', this.chart.theme === 'Material3' ? '#FFFFFF' : this.chart.theme === 'Fluent2' ? '#F5F5F5' : this.chart.theme === 'Bootstrap5' ? '#E9ECEF' : this.chart.theme === 'Bootstrap5Dark' ? '#343A40' : '#1C1B1F'); outerElement.setAttribute('rx', this.chart.theme.indexOf('Bootstrap5') > -1 ? '1px' : '4px'); outerElement.setAttribute('ry', this.chart.theme.indexOf('Bootstrap5') > -1 ? '1px' : '4px'); outerElement.setAttribute('opacity', '1'); } else { if (chart.theme === 'Tailwind3') { outerElement.setAttribute('fill', '#F9FAFB'); } outerElement.setAttribute('filter', 'url(#chart_shadow)'); } this.toolkitElements.appendChild(outerElement); var currentItem; var panIcon = false; for (var i = 1; i <= length; i++) { currentItem = toolboxItems[i - 1]; element = render.createGroup({ transform: 'translate(' + xPosition + ',' + (this.isDevice ? spacing : chart.theme.indexOf('Fluent2') > -1 || chart.theme.indexOf('Bootstrap5') > -1 ? (spacing + 1) : (spacing + 3)) + ')' }); // for desktop toolkit hight is 32 and top padding is 8 icon size 16 switch (currentItem) { case 'Pan': toolkit.createPanButton(element, this.toolkitElements); panIcon = true; break; case 'Zoom': toolkit.createZoomButton(element, this.toolkitElements); break; case 'ZoomIn': toolkit.createZoomInButton(element, this.toolkitElements, chart); break; case 'ZoomOut': toolkit.createZoomOutButton(element, this.toolkitElements, chart); break; case 'Reset': toolkit.createResetButton(element, this.toolkitElements, chart, this.isDevice); break; } xPosition += iconSize + (spacing * 2); } this.toolkitElements.setAttribute('opacity', this.isDevice ? '1' : '' + this.zoomkitOpacity); this.toolkitElements.setAttribute('cursor', 'auto'); if (chart.enableCanvas) { var zoomDiv = document.createElement('div'); zoomDiv.id = chart.element.id + '_zoom'; zoomDiv.style.cssText = 'position:absolute; z-index:1'; var zoomheight = chart.availableSize.height / 2; var svg = chart.svgRenderer.createSvg({ id: chart.element.id + '_zoomkit_svg', width: chart.availableSize.width, height: zoomheight }); svg.style.position = 'absolute'; svg.appendChild(this.toolkitElements); zoomDiv.appendChild(svg); document.getElementById(this.elementId + '_Secondary_Element').appendChild(zoomDiv); } else { chart.svgObject.appendChild(this.toolkitElements); } if (!this.isDevice) { EventHandler.add(this.toolkitElements, 'mousemove touchstart', this.zoomToolkitMove, this); EventHandler.add(this.toolkitElements, 'mouseleave touchend', this.zoomToolkitLeave, this); if (this.isPanning && panIcon) { toolkit.pan(); } } return true; }; /** * Applies the zoom toolkit on the chart. * * @param {Chart} chart - The chart instance. * @param {AxisModel[]} axes - The axis models. * @returns {void} * @private */ Zoom.prototype.applyZoomToolkit = function (chart, axes) { var showToolkit = this.isAxisZoomed(axes); if (showToolkit) { this.showZoomingToolkit(chart); this.isZoomed = true; } else if (chart.zoomSettings.showToolbar) { this.isZoomed = showToolkit; this.showZoomingToolkit(chart); } else { this.toolkit.removeTooltip(); this.isPanning = false; this.isZoomed = false; chart.isZoomed = false; chart.svgObject.setAttribute('cursor', 'auto'); } }; /** * Cancels the zoom action. * * @param {AxisModel[]} axes - The axis models. * @param {IZoomCompleteEventArgs[]} zoomCompleteEventCollection - The collection of zoom complete events. * @returns {void} * @private */ Zoom.prototype.zoomCancel = function (axes, zoomCompleteEventCollection) { for (var _i = 0, _a = zoomCompleteEventCollection; _i < _a.length; _i++) { var zoomCompleteEvent = _a[_i]; for (var _b = 0, _c = axes; _b < _c.length; _b++) { var axis = _c[_b]; if (axis.name === zoomCompleteEvent.axis.name) { axis.zoomFactor = zoomCompleteEvent.previousZoomFactor; axis.zoomPosition = zoomCompleteEvent.previousZoomPosition; axis.visibleRange = zoomCompleteEvent.previousVisibleRange; break; } } } }; /** * Checks if any of the axes is zoomed. * * @param {AxisModel[]} axes - The axis models. * @returns {boolean} - True if any axis is zoomed; otherwise, false. * @private */ Zoom.prototype.isAxisZoomed = function (axes) { var showToolkit = false; for (var _i = 0, _a = axes; _i < _a.length; _i++) { var axis = _a[_i]; showToolkit = (showToolkit || (axis.zoomFactor !== 1 || axis.zoomPosition !== 0)); } return showToolkit; }; Zoom.prototype.zoomToolkitMove = function () { var element = this.toolkitElements; this.zoomkitOpacity = 1; element.setAttribute('opacity', '' + this.zoomkitOpacity); return false; }; Zoom.prototype.zoomToolkitLeave = function () { var element = this.toolkitElements; this.zoomkitOpacity = 1; element.setAttribute('opacity', '' + this.zoomkitOpacity); return false; }; /** * Adds event listeners for the chart. * * @returns {void} * @private */ Zoom.prototype.addEventListener = function () { if (this.chart.isDestroyed) { return; } EventHandler.add(this.chart.element, this.wheelEvent, this.chartMouseWheel, this); this.chart.on(Browser.touchMoveEvent, this.mouseMoveHandler, this); this.chart.on(Browser.touchStartEvent, this.mouseDownHandler, this); this.chart.on(Browser.touchEndEvent, this.mouseUpHandler, this); this.chart.on(this.cancelEvent, this.mouseCancelHandler, this); }; /** * Remove event listeners for the chart. * * @returns {void} * @private */ Zoom.prototype.removeEventListener = function () { EventHandler.remove(this.chart.element, this.wheelEvent, this.chartMouseWheel); if (this.chart.isDestroyed) { return; } this.chart.off(Browser.touchMoveEvent, this.mouseMoveHandler); this.chart.off(Browser.touchStartEvent, this.mouseDownHandler); this.chart.off(Browser.touchEndEvent, this.mouseUpHandler); this.chart.off(this.cancelEvent, this.mouseCancelHandler); }; /** * Handles the mouse wheel event on the chart. * * @param {WheelEvent} e - The wheel event. * @returns {boolean} - Returns false. * @private */ Zoom.prototype.chartMouseWheel = function (e) { var chart = this.chart; var offset = chart.element.getBoundingClientRect(); var svgRect = getElement(chart.svgId).getBoundingClientRect(); var mouseX = (e.clientX - offset.left) - Math.max(svgRect.left - offset.left, 0); var mouseY = (e.clientY - offset.top) - Math.max(svgRect.top - offset.top, 0); if (this.zooming.enableMouseWheelZooming && withInBounds(mouseX, mouseY, chart.chartAxisLayoutPanel.seriesClipRect)) { e.preventDefault(); this.performMouseWheelZooming(e, mouseX, mouseY, chart, chart.axisCollections); } return false; }; /** * Handles the mouse move event on the chart. * * @param {PointerEvent | TouchEvent} e - The mouse move event or touch event. * @returns {void} * @private */ Zoom.prototype.mouseMoveHandler = function (e) { //Zooming for chart var chart = this.chart; var touches = null; if (e.type === 'touchmove') { if (e.preventDefault && this.isIOS && (this.isPanning || (chart.isDoubleTap) || (this.zooming.enablePinchZooming && this.touchStartList.length > 1))) { e.preventDefault(); } touches = e.touches; } if (chart.isChartDrag) { if (chart.isTouch) { this.touchMoveList = this.addTouchPointer(this.touchMoveList, e, touches); if (this.zooming.enablePinchZooming && this.touchMoveList.length > 1 && this.touchStartList.length > 1) { this.performPinchZooming(e, chart); } } this.renderZooming(e, chart, chart.isTouch); } }; /** * Handles the mouse down event on the chart. * * @param {PointerEvent} e - The mouse down event. * @returns {void} * @private */ Zoom.prototype.mouseDownHandler = function (e) { //Zooming for chart var chart = this.chart; var touches = null; var target; if (e.type === 'touchstart') { touches = e.touches; target = e.target; } else { target = e.target; } if (target.id.indexOf(chart.element.id + '_Zooming_') === -1 && (chart.zoomSettings.enablePinchZooming || chart.zoomSettings.enableSelectionZooming || this.chart.zoomModule.isPanning) && withInBounds(chart.previousMouseMoveX, chart.previousMouseMoveY, chart.chartAxisLayoutPanel.seriesClipRect)) { chart.isChartDrag = true; } if (chart.isTouch) { this.touchStartList = this.addTouchPointer(this.touchStartList, e, touches); } }; /** * Handles the mouse up event on the chart. * * @param {PointerEvent} e - The mouse up event. * @returns {void} * @private */ Zoom.prototype.mouseUpHandler = function (e) { var chart = this.chart; var performZoomRedraw = e.target.id.indexOf(chart.element.id + '_ZoomOut_') === -1 || e.target.id.indexOf(chart.element.id + '_ZoomIn_') === -1; if (chart.isChartDrag || performZoomRedraw) { this.redrawOnZooming(chart, true, true); } if (chart.isTouch) { if (chart.isDoubleTap && withInBounds(chart.mouseX, chart.mouseY, chart.chartAxisLayoutPanel.seriesClipRect) && this.touchStartList.length === 1 && this.isZoomed) { this.toolkit.reset(e); } this.touchStartList = []; chart.isDoubleTap = false; } }; /** * Handles the mouse cancel event on the chart. * * @returns {void} * @private */ Zoom.prototype.mouseCancelHandler = function () { if (this.isZoomed) { this.performZoomRedraw(this.chart); } this.pinchTarget = null; this.touchStartList = []; this.touchMoveList = []; }; /** * Adds touch pointer to the touch list. * * @param {ITouches[]} touchList - The touch list. * @param {PointerEvent} e - The pointer event. * @param {TouchList} touches - The touch list. * @returns {ITouches[]} - The updated touch list. * @private */ Zoom.prototype.addTouchPointer = function (touchList, e, touches) { if (touches) { touchList = []; for (var i = 0, length_1 = touches.length; i < length_1; i++) {