; import { XYChartDefaultTheme } from "./XYChartDefaultTheme"; import { Container } from "../../core/render/Container"; import { Rectangle } from "../../core/render/Rectangle"; import { SerialChart } from "../../core/render/SerialChart"; import { ListAutoDispose } from "../../core/util/List"; import { p100 } from "../../core/util/Percent"; import { Color } from "../../core/util/Color"; import { Button } from "../../core/render/Button"; import { Graphics } from "../../core/render/Graphics"; import { Percent } from "../../core/util/Percent"; import * as $array from "../../core/util/Array"; import * as $type from "../../core/util/Type"; import * as $order from "../../core/util/Order"; import * as $object from "../../core/util/Object"; import * as $utils from "../../core/util/Utils"; /** * Creates an XY chart. * * @see {@link https://www.amcharts.com/docs/v5/charts/xy-chart/} for more info * @important */ export class XYChart extends SerialChart { constructor() { super(...arguments); /** * A list of horizontal axes. */ Object.defineProperty(this, "xAxes", { enumerable: true, configurable: true, writable: true, value: new ListAutoDispose() }); /** * A list of vertical axes. */ Object.defineProperty(this, "yAxes", { enumerable: true, configurable: true, writable: true, value: new ListAutoDispose() }); /** * A [[Container]] located on top of the chart, used to store top horizontal * axes. * * @see {@link https://www.amcharts.com/docs/v5/charts/xy-chart/xy-chart-containers/} for more info * @default Container.new() */ Object.defineProperty(this, "topAxesContainer", { enumerable: true, configurable: true, writable: true, value: this.chartContainer.children.push(Container.new(this._root, { width: p100, layout: this._root.verticalLayout })) }); /** * A [[Container]] located in the middle the chart, used to store vertical axes * and plot area container. * * @see {@link https://www.amcharts.com/docs/v5/charts/xy-chart/xy-chart-containers/} for more info * @default Container.new() */ Object.defineProperty(this, "yAxesAndPlotContainer", { enumerable: true, configurable: true, writable: true, value: this.chartContainer.children.push(Container.new(this._root, { width: p100, height: p100, layout: this._root.horizontalLayout })) }); /** * A [[Container]] located on bottom of the chart, used to store bottom * horizontal axes. * * @see {@link https://www.amcharts.com/docs/v5/charts/xy-chart/xy-chart-containers/} for more info * @default Container.new() */ Object.defineProperty(this, "bottomAxesContainer", { enumerable: true, configurable: true, writable: true, value: this.chartContainer.children.push(Container.new(this._root, { width: p100, layout: this._root.verticalLayout })) }); /** * A [[Container]] located on left of the chart, used to store left-hand * vertical axes. * * @see {@link https://www.amcharts.com/docs/v5/charts/xy-chart/xy-chart-containers/} for more info * @default Container.new() */ Object.defineProperty(this, "leftAxesContainer", { enumerable: true, configurable: true, writable: true, value: this.yAxesAndPlotContainer.children.push(Container.new(this._root, { height: p100, layout: this._root.horizontalLayout })) }); /** * A [[Container]] located in the middle of the chart, used to store plotContainer and topPlotContainer * * @see {@link https://www.amcharts.com/docs/v5/charts/xy-chart/xy-chart-containers/} for more info * @default Container.new() */ Object.defineProperty(this, "plotsContainer", { enumerable: true, configurable: true, writable: true, value: this.yAxesAndPlotContainer.children.push(Container.new(this._root, { width: p100, height: p100, maskContent: false })) }); /** * A [[Container]] located in the middle of the chart, used to store actual * plots (series). * * NOTE: `plotContainer` will automatically have its `background` preset. If * you need to modify background or outline for chart's plot area, you can * use `plotContainer.get("background")` for that.* * * @see {@link https://www.amcharts.com/docs/v5/charts/xy-chart/xy-chart-containers/} for more info * @default Container.new() */ Object.defineProperty(this, "plotContainer", { enumerable: true, configurable: true, writable: true, value: this.plotsContainer.children.push(Container.new(this._root, { width: p100, height: p100 })) }); /** * A [[Container]] used for any elements that need to be displayed over * regular `plotContainer`. * * @see {@link https://www.amcharts.com/docs/v5/charts/xy-chart/xy-chart-containers/} for more info * @default Container.new() */ Object.defineProperty(this, "topPlotContainer", { enumerable: true, configurable: true, writable: true, value: this.plotsContainer.children.push(Container.new(this._root, { width: p100, height: p100 })) }); /** * A [[Container]] axis grid elements are stored in. * * @see {@link https://www.amcharts.com/docs/v5/charts/xy-chart/xy-chart-containers/} for more info * @default Container.new() */ Object.defineProperty(this, "gridContainer", { enumerable: true, configurable: true, writable: true, value: this.plotContainer.children.push(Container.new(this._root, { width: p100, height: p100, isMeasured: false })) }); /** * A [[Container]] axis background grid elements are stored in. * * @see {@link https://www.amcharts.com/docs/v5/charts/xy-chart/xy-chart-containers/} for more info * @default Container.new() */ Object.defineProperty(this, "topGridContainer", { enumerable: true, configurable: true, writable: true, value: Container.new(this._root, { width: p100, height: p100, isMeasured: false }) }); /** * A [[Container]] located on right of the chart, used to store right-hand * vertical axes. * * @see {@link https://www.amcharts.com/docs/v5/charts/xy-chart/xy-chart-containers/} for more info * @default Container.new() */ Object.defineProperty(this, "rightAxesContainer", { enumerable: true, configurable: true, writable: true, value: this.yAxesAndPlotContainer.children.push(Container.new(this._root, { height: p100, layout: this._root.horizontalLayout })) }); /** * A [[Container]] axis headers are stored in. * * @see {@link https://www.amcharts.com/docs/v5/charts/xy-chart/axes/axis-headers/} for more info * @default Container.new() */ Object.defineProperty(this, "axisHeadersContainer", { enumerable: true, configurable: true, writable: true, value: this.plotContainer.children.push(Container.new(this._root, {})) }); /** * A button that is shown when chart is not fully zoomed out. * * @see {@link https://www.amcharts.com/docs/v5/charts/xy-chart/zoom-and-pan/#Zoom_out_button} for more info * @default Button.new() */ Object.defineProperty(this, "zoomOutButton", { enumerable: true, configurable: true, writable: true, value: this.topPlotContainer.children.push(Button.new(this._root, { themeTags: ["zoom"], icon: Graphics.new(this._root, { themeTags: ["button", "icon"] }) })) }); Object.defineProperty(this, "_movePoint", { enumerable: true, configurable: true, writable: true, value: { x: 0, y: 0 } }); Object.defineProperty(this, "_wheelDp", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "_otherCharts", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "_movePoints", { enumerable: true, configurable: true, writable: true, value: {} }); Object.defineProperty(this, "_downStartX", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "_downEndX", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "_downStartY", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "_downEndY", { enumerable: true, configurable: true, writable: true, value: void 0 }); } _afterNew() { this._defaultThemes.push(XYChartDefaultTheme.new(this._root)); super._afterNew(); this._disposers.push(this.xAxes); this._disposers.push(this.yAxes); const root = this._root; let verticalLayout = this._root.verticalLayout; const zoomOutButton = this.zoomOutButton; zoomOutButton.events.on("click", () => { this.zoomOut(); }); zoomOutButton.hide(0); zoomOutButton.states.lookup("default").set("opacity", 1); this.chartContainer.set("layout", verticalLayout); const plotContainer = this.plotContainer; plotContainer.children.push(this.seriesContainer); this._disposers.push(this._processAxis(this.xAxes, this.bottomAxesContainer)); this._disposers.push(this._processAxis(this.yAxes, this.leftAxesContainer)); plotContainer.children.push(this.topGridContainer); plotContainer.children.push(this.bulletsContainer); // Setting trasnparent background so that full body of the plot container // is interactive plotContainer.set("interactive", true); plotContainer.set("interactiveChildren", false); plotContainer.set("background", Rectangle.new(root, { themeTags: ["xy", "background"], fill: Color.fromHex(0x000000), fillOpacity: 0 })); this._disposers.push(plotContainer.events.on("pointerdown", (event) => { this._handlePlotDown(event); })); this._disposers.push(plotContainer.events.on("globalpointerup", (event) => { this._handlePlotUp(event); })); this._disposers.push(plotContainer.events.on("globalpointermove", (event) => { this._handlePlotMove(event); })); this._maskGrid(); this._setUpTouch(); } _beforeChanged() { super._beforeChanged(); if (this.isDirty("pinchZoomX") || this.isDirty("pinchZoomY") || this.get("panX") || this.get("panY")) { this._setUpTouch(); } } _setUpTouch() { if (!this.plotContainer._display.cancelTouch) { this.plotContainer._display.cancelTouch = (this.get("pinchZoomX") || this.get("pinchZoomY") || this.get("panX") || this.get("panY")) ? true : false; } } _maskGrid() { this.gridContainer.set("maskContent", true); this.topGridContainer.set("maskContent", true); } _removeSeries(series) { series._unstack(); if (series._posXDp) { series._posXDp.dispose(); } if (series._posYDp) { series._posYDp.dispose(); } series.set("baseAxis", undefined); const xAxis = series.get("xAxis"); if (xAxis) { $array.remove(xAxis.series, series); xAxis.markDirtyExtremes(); } const yAxis = series.get("yAxis"); if (yAxis) { $array.remove(yAxis.series, series); yAxis.markDirtyExtremes(); } const cursor = this.get("cursor"); if (cursor) { const snapToSeries = cursor.get("snapToSeries"); if (snapToSeries) { $array.remove(snapToSeries, series); } } super._removeSeries(series); } /** * This method is invoked when mouse wheel is used over chart's plot * container, and handles zooming/pan. * * You can invoke this method manually, if you need to mimic chart's wheel * behavior over other elements of the chart. */ handleWheel(event) { const wheelX = this.get("wheelX"); const wheelY = this.get("wheelY"); const plotContainer = this.plotContainer; const wheelEvent = event.originalEvent; // Ignore wheel event if it is happening on a non-chart element, e.g. if // some page element is over the chart. if ($utils.isLocalEvent(wheelEvent, this)) { wheelEvent.preventDefault(); } else { return; } const plotPoint = plotContainer.toLocal(event.point); const wheelStep = this.get("wheelStep", 0.2); const shiftY = wheelEvent.deltaY / 100; const shiftX = wheelEvent.deltaX / 100; const wheelZoomPositionX = this.get("wheelZoomPositionX"); const wheelZoomPositionY = this.get("wheelZoomPositionY"); if ((wheelX === "zoomX" || wheelX === "zoomXY") && shiftX != 0) { this.xAxes.each((axis) => { if (axis.get("zoomX")) { let start = axis.get("start"); let end = axis.get("end"); let position = axis.fixPosition(plotPoint.x / plotContainer.width()); if (wheelZoomPositionX != null) { position = wheelZoomPositionX; } let newStart = start - wheelStep * (end - start) * shiftX * position; let newEnd = end + wheelStep * (end - start) * shiftX * (1 - position); if (1 / (newEnd - newStart) < axis.getPrivate("maxZoomFactor", Infinity) / axis.get("minZoomCount", 1)) { this._handleWheelAnimation(axis.zoom(newStart, newEnd)); } } }); } if ((wheelY === "zoomX" || wheelY === "zoomXY") && shiftY != 0) { this.xAxes.each((axis) => { if (axis.get("zoomX")) { let start = axis.get("start"); let end = axis.get("end"); let position = axis.fixPosition(plotPoint.x / plotContainer.width()); if (wheelZoomPositionX != null) { position = wheelZoomPositionX; } let newStart = start - wheelStep * (end - start) * shiftY * position; let newEnd = end + wheelStep * (end - start) * shiftY * (1 - position); if (1 / (newEnd - newStart) < axis.getPrivate("maxZoomFactor", Infinity) / axis.get("minZoomCount", 1)) { this._handleWheelAnimation(axis.zoom(newStart, newEnd)); } } }); } if ((wheelX === "zoomY" || wheelX === "zoomXY") && shiftX != 0) { this.yAxes.each((axis) => { if (axis.get("zoomY")) { let start = axis.get("start"); let end = axis.get("end"); let position = axis.fixPosition(plotPoint.y / plotContainer.height()); if (wheelZoomPositionY != null) { position = wheelZoomPositionY; } let newStart = start - wheelStep * (end - start) * shiftX * position; let newEnd = end + wheelStep * (end - start) * shiftX * (1 - position); if (1 / (newEnd - newStart) < axis.getPrivate("maxZoomFactor", Infinity) / axis.get("minZoomCount", 1)) { this._handleWheelAnimation(axis.zoom(newStart, newEnd)); } } }); } if ((wheelY === "zoomY" || wheelY === "zoomXY") && shiftY != 0) { this.yAxes.each((axis) => { if (axis.get("zoomY")) { let start = axis.get("start"); let end = axis.get("end"); let position = axis.fixPosition(plotPoint.y / plotContainer.height()); if (wheelZoomPositionY != null) { position = wheelZoomPositionY; } let newStart = start - wheelStep * (end - start) * shiftY * position; let newEnd = end + wheelStep * (end - start) * shiftY * (1 - position); if (1 / (newEnd - newStart) < axis.getPrivate("maxZoomFactor", Infinity) / axis.get("minZoomCount", 1)) { this._handleWheelAnimation(axis.zoom(newStart, newEnd)); } } }); } if ((wheelX === "panX" || wheelX === "panXY") && shiftX != 0) { this.xAxes.each((axis) => { if (axis.get("panX")) { let start = axis.get("start"); let end = axis.get("end"); let delta = this._getWheelSign(axis) * wheelStep * (end - start) * shiftX; let newStart = start + delta; let newEnd = end + delta; let se = this._fixWheel(newStart, newEnd); newStart = se[0]; newEnd = se[1]; this._handleWheelAnimation(axis.zoom(newStart, newEnd)); } }); } if ((wheelY === "panX" || wheelY === "panXY") && shiftY != 0) { this.xAxes.each((axis) => { if (axis.get("panX")) { let start = axis.get("start"); let end = axis.get("end"); let delta = this._getWheelSign(axis) * wheelStep * (end - start) * shiftY; let newStart = start + delta; let newEnd = end + delta; let se = this._fixWheel(newStart, newEnd); newStart = se[0]; newEnd = se[1]; this._handleWheelAnimation(axis.zoom(newStart, newEnd)); } }); } if ((wheelX === "panY" || wheelX === "panXY") && shiftX != 0) { this.yAxes.each((axis) => { if (axis.get("panY")) { let start = axis.get("start"); let end = axis.get("end"); let delta = this._getWheelSign(axis) * wheelStep * (end - start) * shiftX; let newStart = start + delta; let newEnd = end + delta; let se = this._fixWheel(newStart, newEnd); newStart = se[0]; newEnd = se[1]; this._handleWheelAnimation(axis.zoom(newStart, newEnd)); } }); } if ((wheelY === "panY" || wheelY === "panXY") && shiftY != 0) { this.yAxes.each((axis) => { if (axis.get("panY")) { let start = axis.get("start"); let end = axis.get("end"); let delta = this._getWheelSign(axis) * wheelStep * (end - start) * shiftY; let newStart = start - delta; let newEnd = end - delta; let se = this._fixWheel(newStart, newEnd); newStart = se[0]; newEnd = se[1]; this._handleWheelAnimation(axis.zoom(newStart, newEnd)); } }); } } _handleSetWheel() { const wheelX = this.get("wheelX"); const wheelY = this.get("wheelY"); const plotContainer = this.plotContainer; if (wheelX !== "none" || wheelY !== "none") { this._wheelDp = plotContainer.events.on("wheel", (event) => { const wheelEvent = event.originalEvent; if ((wheelX !== "none" && Math.abs(wheelEvent.deltaX) != 0) || (wheelY !== "none" && Math.abs(wheelEvent.deltaY) != 0)) { this.handleWheel(event); } }); this._disposers.push(this._wheelDp); } else { if (this._wheelDp) { this._wheelDp.dispose(); } } } _getWheelSign(axis) { let sign = 1; if (axis.get("renderer").get("inversed")) { sign = -1; } return sign; } _fixWheel(start, end) { const diff = end - start; if (start < 0) { start = 0; end = start + diff; } if (end > 1) { end = 1; start = end - diff; } return [start, end]; } _handlePlotDown(event) { const originalEvent = event.originalEvent; if (originalEvent.button == 2) { return; } const plotContainer = this.plotContainer; let local = plotContainer.toLocal(event.point); if (this.get("pinchZoomX") || this.get("pinchZoomY")) { const pointerId = originalEvent.pointerId; if (pointerId) { if ($object.keys(plotContainer._downPoints).length > 0) { const xAxis = this.xAxes.getIndex(0); const yAxis = this.yAxes.getIndex(0); if (xAxis) { this._downStartX = xAxis.get("start", 0); this._downEndX = xAxis.get("end", 1); } if (yAxis) { this._downStartY = yAxis.get("start", 0); this._downEndY = yAxis.get("end", 1); } } } } if (this.get("panX") || this.get("panY")) { if (local.x >= 0 && local.y >= 0 && local.x <= plotContainer.width() && local.y <= this.height()) { //this._downPoint = local; this._downPoint = { x: originalEvent.clientX, y: originalEvent.clientY }; const panX = this.get("panX"); const panY = this.get("panY"); if (panX) { this.xAxes.each((axis) => { axis._panStart = axis.get("start"); axis._panEnd = axis.get("end"); }); } if (panY) { this.yAxes.each((axis) => { axis._panStart = axis.get("start"); axis._panEnd = axis.get("end"); }); } const eventType = "panstarted"; if (this.events.isEnabled(eventType)) { this.events.dispatch(eventType, { type: eventType, target: this, originalEvent: event.originalEvent }); } } } } _handleWheelAnimation(animation) { if (animation) { animation.events.on("stopped", () => { this._dispatchWheelAnimation(); }); } else { this._dispatchWheelAnimation(); } } _dispatchWheelAnimation() { const eventType = "wheelended"; if (this.events.isEnabled(eventType)) { this.events.dispatch(eventType, { type: eventType, target: this }); } } _handlePlotUp(event) { const downPoint = this._downPoint; if (downPoint) { if (this.get("panX") || this.get("panY")) { let local = this.plotContainer.toLocal(event.point); if (local.x == downPoint.x && local.y == downPoint.y) { const eventType = "pancancelled"; if (this.events.isEnabled(eventType)) { this.events.dispatch(eventType, { type: eventType, target: this, originalEvent: event.originalEvent }); } } const eventType = "panended"; if (this.events.isEnabled(eventType)) { this.events.dispatch(eventType, { type: eventType, target: this, originalEvent: event.originalEvent }); } } } // TODO: handle multitouch this._downPoint = undefined; this.xAxes.each((xAxis) => { xAxis._isPanning = false; }); this.yAxes.each((yAxis) => { yAxis._isPanning = false; }); } _handlePlotMove(event) { const plotContainer = this.plotContainer; if (this.get("pinchZoomX") || this.get("pinchZoomY")) { const touchEvent = event.originalEvent; const pointerId = touchEvent.pointerId; if (pointerId) { this._movePoints[pointerId] = event.point; if ($object.keys(plotContainer._downPoints).length > 1) { this._handlePinch(); return; } } } let downPoint = this._downPoint; if (downPoint) { downPoint = plotContainer.toLocal(this._root.documentPointToRoot(downPoint)); let local = plotContainer.toLocal(event.point); const panX = this.get("panX"); const panY = this.get("panY"); if (panX) { let scrollbarX = this.get("scrollbarX"); if (scrollbarX) { scrollbarX.events.disableType("rangechanged"); } this.xAxes.each((axis) => { if (axis.get("panX")) { axis._isPanning = true; //const maxDeviation = axis.get("maxDeviation", 0); let panStart = axis._panStart; let panEnd = axis._panEnd; let difference = (panEnd - panStart); let deltaX = difference * (downPoint.x - local.x) / plotContainer.width(); if (axis.get("renderer").get("inversed")) { deltaX *= -1; } let start = panStart + deltaX; let end = panEnd + deltaX; if (end - start < 1 + axis.get("maxDeviation", 1) * 2) { axis.set("start", start); axis.set("end", end); } } }); if (scrollbarX) { scrollbarX.events.enableType("rangechanged"); } } if (panY) { let scrollbarY = this.get("scrollbarY"); if (scrollbarY) { scrollbarY.events.disableType("rangechanged"); } this.yAxes.each((axis) => { if (axis.get("panY")) { axis._isPanning = true; //const maxDeviation = axis.get("maxDeviation", 0); let panStart = axis._panStart; let panEnd = axis._panEnd; let difference = (panEnd - panStart); let deltaY = difference * (downPoint.y - local.y) / plotContainer.height(); if (axis.get("renderer").get("inversed")) { deltaY *= -1; } let start = panStart - deltaY; let end = panEnd - deltaY; if (end - start < 1 + axis.get("maxDeviation", 1) * 2) { axis.set("start", start); axis.set("end", end); } } }); if (scrollbarY) { scrollbarY.events.enableType("rangechanged"); } } } } _handlePinch() { const plotContainer = this.plotContainer; let i = 0; let downPoints = []; let movePoints = []; $object.each(plotContainer._downPoints, (k, point) => { downPoints[i] = point; let movePoint = this._movePoints[k]; if (movePoint) { movePoints[i] = movePoint; } i++; }); if (downPoints.length > 1 && movePoints.length > 1) { const w = plotContainer.width(); const h = plotContainer.height(); let downPoint0 = downPoints[0]; let downPoint1 = downPoints[1]; let movePoint0 = movePoints[0]; let movePoint1 = movePoints[1]; if (downPoint0 && downPoint1 && movePoint0 && movePoint1) { movePoint0 = plotContainer.toLocal(movePoint0); movePoint1 = plotContainer.toLocal(movePoint1); downPoint0 = plotContainer.toLocal(downPoint0); downPoint1 = plotContainer.toLocal(downPoint1); if (this.get("pinchZoomX")) { const downStartX = this._downStartX; const downEndX = this._downEndX; if (downStartX != null && downEndX != null) { if (downPoint0.x > downPoint1.x) { [downPoint0, downPoint1] = [downPoint1, downPoint0]; [movePoint0, movePoint1] = [movePoint1, movePoint0]; } let downPos0 = downStartX + (downPoint0.x / w) * (downEndX - downStartX); let downPos1 = downStartX + (downPoint1.x / w) * (downEndX - downStartX); let movePos0 = downStartX + (movePoint0.x / w) * (downEndX - downStartX); let movePos1 = downStartX + (movePoint1.x / w) * (downEndX - downStartX); let initialDistance = Math.max(0.001, downPos1 - downPos0); let currentDistance = Math.max(0.001, movePos1 - movePos0); let d = initialDistance / currentDistance; let s = downStartX * d + downPos0 - movePos0 * d; let e = downEndX * d + downPos1 - movePos1 * d; this.xAxes.each((xAxis) => { let sa = xAxis.fixPosition(s); let ea = xAxis.fixPosition(e); xAxis.zoom(sa, ea, 0); }); } } if (this.get("pinchZoomY")) { const downStartY = this._downStartY; const downEndY = this._downEndY; if (downStartY != null && downEndY != null) { if (downPoint0.y < downPoint1.y) { [downPoint0, downPoint1] = [downPoint1, downPoint0]; [movePoint0, movePoint1] = [movePoint1, movePoint0]; } let downPos0 = downStartY + (1 - downPoint0.y / h) * (downEndY - downStartY); let downPos1 = downStartY + (1 - downPoint1.y / h) * (downEndY - downStartY); let movePos0 = downStartY + (1 - movePoint0.y / h) * (downEndY - downStartY); let movePos1 = downStartY + (1 - movePoint1.y / h) * (downEndY - downStartY); let initialDistance = Math.max(0.001, downPos1 - downPos0); let currentDistance = Math.max(0.001, movePos1 - movePos0); let d = initialDistance / currentDistance; let s = downStartY * d + downPos0 - movePos0 * d; let e = downEndY * d + downPos1 - movePos1 * d; this.yAxes.each((yAxis) => { let sa = yAxis.fixPosition(s); let ea = yAxis.fixPosition(e); yAxis.zoom(sa, ea, 0); }); } } } } } _handleCursorPosition() { const cursor = this.get("cursor"); if (cursor) { const cursorPoint = cursor.getPrivate("point"); let snapToSeries = cursor.get("snapToSeries"); if (cursor._downPoint) { snapToSeries = undefined; } if (snapToSeries && cursorPoint) { const snapToSeriesBy = cursor.get("snapToSeriesBy"); const dataItems = []; $array.each(snapToSeries, (series) => { if (!series.isHidden() && !series.isHiding()) { if (snapToSeriesBy != "x!" && snapToSeriesBy != "y!") { const startIndex = series.startIndex(); const endIndex = series.endIndex(); for (let i = startIndex; i < endIndex; i++) { const dataItem = series.dataItems[i]; if (dataItem && !dataItem.isHidden()) { dataItems.push(dataItem); } } } else { const tooltipDataItem = series.get("tooltipDataItem"); if (tooltipDataItem) { dataItems.push(tooltipDataItem); } } } }); let minDistance = Infinity; let closestItem; $array.each(dataItems, (dataItem) => { const point = dataItem.get("point"); if (point) { let distance = 0; if (snapToSeriesBy == "x" || snapToSeriesBy == "x!") { distance = Math.abs(cursorPoint.x - point.x); } else if (snapToSeriesBy == "y" || snapToSeriesBy == "y!") { distance = Math.abs(cursorPoint.y - point.y); } else { distance = Math.hypot(cursorPoint.x - point.x, cursorPoint.y - point.y); } if (distance < minDistance) { minDistance = distance; closestItem = dataItem; } } }); $array.each(snapToSeries, (series) => { const tooltip = series.get("tooltip"); if (tooltip) { tooltip._setDataItem(undefined); } }); if (closestItem) { let series = closestItem.component; series.showDataItemTooltip(closestItem); const point = closestItem.get("point"); if (point) { // removing x and y to solve #72225 cursor.handleMove(series.toGlobal({ x: point.x - series.x(), y: point.y - series.y() }), true); } } } } } _updateCursor() { let cursor = this.get("cursor"); if (cursor) { cursor.updateCursor(); } } _addCursor(cursor) { this.plotContainer.children.push(cursor); } _prepareChildren() { super._prepareChildren(); this.series.each((series) => { this._colorize(series); }); if (this.isDirty("wheelX") || this.isDirty("wheelY")) { this._handleSetWheel(); } if (this.isDirty("cursor")) { const previous = this._prevSettings.cursor; const cursor = this.get("cursor"); if (cursor !== previous) { this._disposeProperty("cursor"); if (previous) { previous.dispose(); } if (cursor) { cursor._setChart(this); this._addCursor(cursor); this._pushPropertyDisposer("cursor", cursor.events.on("selectended", () => { this._handleCursorSelectEnd(); })); } //this.setRaw("cursor", cursor) // to reset previous value this._prevSettings.cursor = cursor; } } if (this.isDirty("scrollbarX")) { const previous = this._prevSettings.scrollbarX; const scrollbarX = this.get("scrollbarX"); if (scrollbarX !== previous) { this._disposeProperty("scrollbarX"); if (previous) { previous.dispose(); } if (scrollbarX) { if (!scrollbarX.parent) { this.topAxesContainer.children.push(scrollbarX); } this._pushPropertyDisposer("scrollbarX", scrollbarX.events.on("rangechanged", (e) => { this._handleScrollbar(this.xAxes, e.start, e.end, e.grip); })); // Used to populate `ariaLabel` with meaningful values scrollbarX.setPrivate("positionTextFunction", (position) => { const axis = this.xAxes.getIndex(0); return axis ? axis.getTooltipText(position, false) || "" : ""; }); } this._prevSettings.scrollbarX = scrollbarX; } } if (this.isDirty("scrollbarY")) { const previous = this._prevSettings.scrollbarY; const scrollbarY = this.get("scrollbarY"); if (scrollbarY !== previous) { this._disposeProperty("scrollbarY"); if (previous) { previous.dispose(); } if (scrollbarY) { if (!scrollbarY.parent) { this.rightAxesContainer.children.push(scrollbarY); } this._pushPropertyDisposer("scrollbarY", scrollbarY.events.on("rangechanged", (e) => { this._handleScrollbar(this.yAxes, e.start, e.end, e.grip); })); // Used to populate `ariaLabel` with meaningful values scrollbarY.setPrivate("positionTextFunction", (position) => { const axis = this.yAxes.getIndex(0); return axis ? axis.getTooltipText(position, false) || "" : ""; }); } this._prevSettings.scrollbarY = scrollbarY; } } this._handleZoomOut(); } _processSeries(series) { super._processSeries(series); const xAxis = series.get("xAxis"); const yAxis = series.get("yAxis"); $array.move(xAxis.series, series); $array.move(yAxis.series, series); series._posXDp = series.addDisposer(xAxis.events.on("positionchanged", () => { series._fixPosition(); })); series._posXDp = series.addDisposer(yAxis.events.on("positionchanged", () => { series._fixPosition(); })); if (!series.get("baseAxis")) { if (yAxis.isType("CategoryAxis") || yAxis.isType("DateAxis")) { series.set("baseAxis", yAxis); } else { series.set("baseAxis", xAxis); } } if (series.get("stacked")) { series._markDirtyKey("stacked"); $array.each(series.dataItems, (dataItem) => { dataItem.set("stackToItemY", undefined); dataItem.set("stackToItemX", undefined); }); } series._markDirtyAxes(); yAxis.markDirtyExtremes(); xAxis.markDirtyExtremes(); this._colorize(series); } _colorize(series) { const colorSet = this.get("colors"); if (colorSet) { if (series.get("fill") == null) { const color = colorSet.next(); series._setSoft("stroke", color); series._setSoft("fill", color); } } } _handleCursorSelectEnd() { const cursor = this.get("cursor"); const behavior = cursor.get("behavior"); const downPositionX = cursor.getPrivate("downPositionX", 0); const downPositionY = cursor.getPrivate("downPositionY", 0); const positionX = Math.min(1, Math.max(0, cursor.getPrivate("positionX", 0.5))); const positionY = Math.min(1, Math.max(0, cursor.getPrivate("positionY", 0.5))); this.xAxes.each((axis) => { if (behavior === "zoomX" || behavior === "zoomXY") { let position0 = axis.toAxisPosition(downPositionX); let position1 = axis.toAxisPosition(positionX); axis.zoom(position0, position1); } axis.setPrivate("updateScrollbar", true); }); this.yAxes.each((axis) => { if (behavior === "zoomY" || behavior === "zoomXY") { let position0 = axis.toAxisPosition(downPositionY); let position1 = axis.toAxisPosition(positionY); axis.zoom(position0, position1); } axis.setPrivate("updateScrollbar", true); }); } _handleScrollbar(axes, start, end, priority) { axes.each((axis) => { let axisStart = axis.fixPosition(start); let axisEnd = axis.fixPosition(end); let zoomAnimation = axis.zoom(axisStart, axisEnd, undefined, priority); const updateScrollbar = "updateScrollbar"; axis.setPrivateRaw(updateScrollbar, false); if (zoomAnimation) { zoomAnimation.events.on("stopped", () => { axis.setPrivateRaw(updateScrollbar, true); }); } else { axis.setPrivateRaw(updateScrollbar, true); } }); } _processAxis(axes, container) { return axes.events.onAll((change) => { if (change.type === "clear") { $array.each(change.oldValues, (axis) => { this._removeAxis(axis); }); } else if (change.type === "push") { container.children.push(change.newValue); change.newValue.processChart(this); } else if (change.type === "setIndex") { container.children.setIndex(change.index, change.newValue); change.newValue.processChart(this); } else if (change.type === "insertIndex") { container.children.insertIndex(change.index, change.newValue); change.newValue.processChart(this); } else if (change.type === "removeIndex") { this._removeAxis(change.oldValue); } else if (change.type === "moveIndex") { container.children.moveValue(change.value, change.newIndex); change.value.processChart(this); } else { throw new Error("Unknown IListEvent type"); } }); } _removeAxis(axis) { if (!axis.isDisposed()) { const axisParent = axis.parent; if (axisParent) { axisParent.children.removeValue(axis); } const gridContainer = axis.gridContainer; const gridParent = gridContainer.parent; if (gridParent) { gridParent.children.removeValue(gridContainer); } const topGridContainer = axis.topGridContainer; const topGridParent = topGridContainer.parent; if (topGridParent) { topGridParent.children.removeValue(topGridContainer); } } } _updateChartLayout() { const left = this.leftAxesContainer.width(); const right = this.rightAxesContainer.width(); const bottomAxesContainer = this.bottomAxesContainer; bottomAxesContainer.set("paddingLeft", left); bottomAxesContainer.set("paddingRight", right); const topAxesContainer = this.topAxesContainer; topAxesContainer.set("paddingLeft", left); topAxesContainer.set("paddingRight", right); } /** * @ignore */ processAxis(axis) { var cursor = this.get("cursor"); if (cursor) { this.addDisposer(axis.on("start", () => { this._updateCursor(); })); this.addDisposer(axis.on("end", () => { this._updateCursor(); })); } } _handleAxisSelection(axis, force) { let start = axis.fixPosition(axis.get("start", 0)); let end = axis.fixPosition(axis.get("end", 1)); if (start > end) { [start, end] = [end, start]; } if (this.xAxes.indexOf(axis) != -1) { if (force || axis.getPrivate("updateScrollbar")) { let scrollbarX = this.get("scrollbarX"); if (scrollbarX && (!scrollbarX.getPrivate("isBusy") || force)) { scrollbarX.setRaw("start", start); scrollbarX.setRaw("end", end); scrollbarX.updateGrips(); } } } else if (this.yAxes.indexOf(axis) != -1) { if (force || axis.getPrivate("updateScrollbar")) { let scrollbarY = this.get("scrollbarY"); if (scrollbarY && (!scrollbarY.getPrivate("isBusy") || force)) { scrollbarY.setRaw("start", start); scrollbarY.setRaw("end", end); scrollbarY.updateGrips(); } } } this._handleZoomOut(); } _handleZoomOut() { let zoomOutButton = this.zoomOutButton; if (zoomOutButton && zoomOutButton.parent) { let visible = false; this.xAxes.each((axis) => { if (axis.get("start") != 0 || axis.get("end") != 1) { visible = true; } }); this.yAxes.each((axis) => { if (axis.get("start") != 0 || axis.get("end") != 1) { visible = true; } }); if (visible) { if (zoomOutButton.isHidden()) { zoomOutButton.show(); } } else { zoomOutButton.hide(); } } } /** * Checks if point is within plot area. * * @param point Reference point * @return Is within plot area? */ inPlot(point) { const plotContainer = this.plotContainer; const otherCharts = this.getPrivate("otherCharts", this._otherCharts); const global = plotContainer.toGlobal(point); if (point.x >= -0.5 && point.y >= -0.5 && point.x <= plotContainer.width() + 0.5 && point.y <= plotContainer.height() + 0.5) { return true; } if (otherCharts) { for (let i = otherCharts.length - 1; i >= 0; i--) { const chart = otherCharts[i]; if (chart != this) { const chartPlotContainer = chart.plotContainer; const documentPoint = this._root.rootPointToDocument(global); const chartRoot = chart._root.documentPointToRoot(documentPoint); const local = chartPlotContainer.toLocal(chartRoot); if (local.x >= -0.1 && local.y >= -0.1 && local.x <= chartPlotContainer.width() + 0.1 && local.y <= chartPlotContainer.height() + 0.1) { return true; } } } } return false; } /** * @ignore */ arrangeTooltips() { const plotContainer = this.plotContainer; const w = plotContainer.width(); const h = plotContainer.height(); const hh = this.height(); let plotT = plotContainer._display.toGlobal({ x: 0, y: 0 }); let plotB = plotContainer._display.toGlobal({ x: w, y: h }); const tooltips = []; let sum = 0; let minDistance = Infinity; let movePoint = this._movePoint; let maxTooltipDistance = this.get("maxTooltipDistance"); let maxTooltipDistanceBy = this.get("maxTooltipDistanceBy", "xy"); let closest; let closestPoint; if ($type.isNumber(maxTooltipDistance)) { this.series.each((series) => { if (!series.isHidden()) { const tooltip = series.get("tooltip"); if (tooltip) { let point = tooltip.get("pointTo"); if (point) { let distance = Math.hypot(movePoint.x - point.x, movePoint.y - point.y); if (maxTooltipDistanceBy == "x") { distance = Math.abs(movePoint.x - point.x); } else if (maxTooltipDistanceBy == "y") { distance = Math.abs(movePoint.y - point.y); } if (distance < minDistance) { minDistance = distance; closest = series; closestPoint = point; } } } } }); } const tooltipSeries = []; this.series.each((series) => { const tooltip = series.get("tooltip"); if (tooltip && !tooltip.get("forceHidden")) { let hidden = false; let point = tooltip.get("pointTo"); if (point) { if (maxTooltipDistance >= 0) { let point = tooltip.get("pointTo"); if (point && closestPoint) { if (series != closest) { let distance = Math.hypot(closestPoint.x - point.x, closestPoint.y - point.y); if (maxTooltipDistanceBy == "x") { distance = Math.abs(closestPoint.x - point.x); } else if (maxTooltipDistanceBy == "y") { distance = Math.abs(closestPoint.y - point.y); } if (distance > maxTooltipDistance) { hidden = true; } } } } else if (maxTooltipDistance == -1) { if (series != closest) { hidden = true; } } if (!this.inPlot(this._tooltipToLocal(point)) || !tooltip.dataItem) { hidden = true; } else { if (!hidden) { sum += point.y; } } if (hidden || series.isHidden() || series.isHiding()) { tooltip.hide(0); } else { tooltip.show(); tooltips.push(tooltip); tooltipSeries.push(series); } } } }); this.setPrivate("tooltipSeries", tooltipSeries); if (this.get("arrangeTooltips")) { const tooltipContainer = this._root.tooltipContainer; const count = tooltips.length; const average = sum / count; if (average > h / 2 + plotT.y) { tooltips.sort((a, b) => $order.compareNumber(b.get("pointTo").y, a.get("pointTo").y)); let prevY = plotB.y; $array.each(tooltips, (tooltip) => { let height = tooltip.height(); let centerY = tooltip.get("centerY"); if (centerY instanceof Percent) { height *= centerY.value; } height += tooltip.get("marginBottom", 0); tooltip.set("bounds", { left: plotT.x, top: plotT.y, right: plotB.x, bottom: prevY }); tooltip.setPrivate("customData", { left: plotT.x, top: plotT.y, right: plotB.x, bottom: prevY }); prevY = Math.min(prevY - height, tooltip._fy - height); if (tooltip.parent == tooltipContainer) { tooltipContainer.children.moveValue(tooltip, 0); } }); if (prevY < 0) { tooltips.reverse(); let prevBottom = prevY; $array.each(tooltips, (tooltip) => { let bounds = tooltip.get("bounds"); if (bounds) { let top = bounds.top - prevY; let bottom = bounds.bottom - prevY; if (top < prevBottom) { top = prevBottom; bottom = top + tooltip.height(); } tooltip.set("bounds", { left: bounds.left, top: top, right: bounds.right, bottom: bottom }); prevBottom = bounds.bottom - prevY + tooltip.get("marginBottom", 0); } }); } } else { tooltips.reverse(); tooltips.sort((a, b) => $order.compareNumber(a.get("pointTo").y, b.get("pointTo").y)); let prevY = 0; $array.each(tooltips, (tooltip) => { let height = tooltip.height(); let centerY = tooltip.get("centerY"); if (centerY instanceof Percent) { height *= centerY.value; } height += tooltip.get("marginBottom", 0); tooltip.set("bounds", { left: plotT.x, top: prevY, right: plotB.x, bottom: Math.max(plotT.y + hh, prevY + height) }); if (tooltip.parent == tooltipContainer) { tooltipContainer.children.moveValue(tooltip, 0); } prevY = Math.max(prevY + height, tooltip._fy + height); }); if (prevY > hh) { tooltips.reverse(); let prevBottom = hh; $array.each(tooltips, (tooltip) => { let bounds = tooltip.get("bounds"); if (bounds) { let top = bounds.top - (hh - prevY); let bottom = bounds.bottom - (hh - prevY); if (bottom > prevBottom) { bottom = prevBottom; top = bottom - tooltip.height(); } tooltip.set("bounds", { left: bounds.left, top: top, right: bounds.right, bottom: bottom }); prevBottom = bottom - tooltip.height() - tooltip.get("marginBottom", 0); } }); } } } } _tooltipToLocal(point) { return this.plotContainer.toLocal(point); } /** * Fully zooms out the chart. */ zoomOut() { this.xAxes.each((axis) => { axis.setPrivate("updateScrollbar", true); axis.zoom(0, 1); }); this.yAxes.each((axis) => { axis.setPrivate("updateScrollbar", true); axis.zoom(0, 1); }); } } Object.defineProperty(XYChart, "className", { enumerable: true, configurable: true, writable: true, value: "XYChart" }); Object.defineProperty(XYChart, "classNames", { enumerable: true, configurable: true, writable: true, value: SerialChart.classNames.concat([XYChart.className]) }); //# sourceMappingURL=XYChart.js.map