UNPKG

@amcharts/amcharts4

Version:
1,262 lines 87.8 kB
/** * XY Chart module. */ import { __extends } from "tslib"; /** * ============================================================================ * IMPORTS * ============================================================================ * @hidden */ import { SerialChart, SerialChartDataItem } from "./SerialChart"; import { Container } from "../../core/Container"; import { List, ListDisposer } from "../../core/utils/List"; import { Color } from "../../core/utils/Color"; import { ValueAxis } from "../axes/ValueAxis"; import { DateAxis } from "../axes/DateAxis"; import { AxisRendererX } from "../axes/AxisRendererX"; import { AxisRendererY } from "../axes/AxisRendererY"; import { CategoryAxis } from "../axes/CategoryAxis"; import { XYSeries } from "../series/XYSeries"; import { Disposer } from "../../core/utils/Disposer"; import { ZoomOutButton } from "../../core/elements/ZoomOutButton"; import { percent } from "../../core/utils/Percent"; import { registry } from "../../core/Registry"; import { XYChartScrollbar } from "../elements/XYChartScrollbar"; import * as $math from "../../core/utils/Math"; import * as $iter from "../../core/utils/Iterator"; import * as $type from "../../core/utils/Type"; import * as $utils from "../../core/utils/Utils"; import * as $array from "../../core/utils/Array"; import * as $number from "../../core/utils/Number"; import { defaultRules, ResponsiveBreakpoints } from "../../core/utils/Responsive"; /** * ============================================================================ * DATA ITEM * ============================================================================ * @hidden */ /** * Defines a [[DataItem]] for [[XYChart]]. * * @see {@link DataItem} */ var XYChartDataItem = /** @class */ (function (_super) { __extends(XYChartDataItem, _super); /** * Constructor */ function XYChartDataItem() { var _this = _super.call(this) || this; _this.className = "XYChartDataItem"; _this.applyTheme(); return _this; } return XYChartDataItem; }(SerialChartDataItem)); export { XYChartDataItem }; /** * ============================================================================ * MAIN CLASS * ============================================================================ * @hidden */ /** * Creates an XY chart, and any derivative chart, like Serial, Date-based, etc. * * Basically this is a chart type, that is used to display any chart * information in a square plot area. * * The horizontal and vertical scale is determined by the type of Axis. * * The plot types are determined by type of Series. * * ```TypeScript * // Includes * import * as am4core from "@amcharts/amcharts4/core"; * import * as am4charts from "@amcharts/amcharts4/charts"; * * // Create chart * let chart = am4core.create("chartdiv", am4charts.XYChart); * * // Add Data * chart.data = [{ * "country": "USA", * "visits": 3025 * }, { * "country": "China", * "visits": 1882 * }, { * "country": "Japan", * "visits": 1809 * }]; * * // Add category axis * let categoryAxis = chart.xAxes.push(new am4charts.CategoryAxis()); * categoryAxis.dataFields.category = "country"; * * // Add value axis * let valueAxis = chart.yAxes.push(new am4charts.ValueAxis()); * * // Add series * let series = chart.series.push(new am4charts.ColumnSeries()); * series.name = "Web Traffic"; * series.dataFields.categoryX = "country"; * series.dataFields.valueY = "visits"; * ``` * ```JavaScript * // Create chart * var chart = am4core.create("chartdiv", am4charts.XYChart); * * // The following would work as well: * // var chart = am4core.create("chartdiv", "XYChart"); * * // Add Data * chart.data = [{ * "country": "USA", * "visits": 3025 * }, { * "country": "China", * "visits": 1882 * }, { * "country": "Japan", * "visits": 1809 * }]; * * // Add category axis * var categoryAxis = chart.xAxes.push(new am4charts.CategoryAxis()); * categoryAxis.dataFields.category = "country"; * * // Add value axis * var valueAxis = chart.yAxes.push(new am4charts.ValueAxis()); * * // Add series * var series = chart.series.push(new am4charts.ColumnSeries()); * series.name = "Web Traffic"; * series.dataFields.categoryX = "country"; * series.dataFields.valueY = "visits"; * ``` * ```JSON * var chart = am4core.createFromConfig({ * * // Category axis * "xAxes": [{ * "type": "CategoryAxis", * "dataFields": { * "category": "country" * } * }], * * // Value axis * "yAxes": [{ * "type": "ValueAxis" * }], * * // Series * "series": [{ * "type": "ColumnSeries", * "dataFields": { * "categoryX": "country", * "valueY": "visits" * }, * "name": "Web Traffic" * }], * * // Cursor * "cursor": {}, * * // Data * "data": [{ * "country": "USA", * "visits": 3025 * }, { * "country": "China", * "visits": 1882 * }, { * "country": "Japan", * "visits": 1809 * }] * * }, "chartdiv", "XYChart"); * ``` * * * @see {@link IXYChartEvents} for a list of available Events * @see {@link IXYChartAdapters} for a list of available Adapters * @see {@link https://www.amcharts.com/docs/v4/chart-types/xy-chart/} for documentation * @important */ var XYChart = /** @class */ (function (_super) { __extends(XYChart, _super); /** * Constructor */ function XYChart() { var _this = // Init _super.call(this) || this; /** * Defines the type of horizontal axis rederer. */ _this._axisRendererX = AxisRendererX; /** * Defines the type of vertical axis rederer. */ _this._axisRendererY = AxisRendererY; /** * @ignore */ _this._seriesPoints = []; _this.className = "XYChart"; // Set defaults //this.margin(10, 10, 10, 10); _this.maskBullets = true; _this.arrangeTooltips = true; // Create main chart container var chartContainer = _this.chartContainer; chartContainer.layout = "vertical"; _this.padding(15, 15, 15, 15); // Create top axes container var topAxesCont = chartContainer.createChild(Container); topAxesCont.shouldClone = false; topAxesCont.layout = "vertical"; topAxesCont.width = percent(100); topAxesCont.zIndex = 1; _this.topAxesContainer = topAxesCont; // Create vertical axes and plot area container // Plot area and vertical axes share the whole width of the chart, // so we need to put then into a separate container so that layouting // engine takes care of the positioning var yAxesAndPlotCont = chartContainer.createChild(Container); yAxesAndPlotCont.shouldClone = false; yAxesAndPlotCont.layout = "horizontal"; yAxesAndPlotCont.width = percent(100); yAxesAndPlotCont.height = percent(100); yAxesAndPlotCont.zIndex = 0; _this.yAxesAndPlotContainer = yAxesAndPlotCont; // Create a container for bottom axes var bottomAxesCont = chartContainer.createChild(Container); bottomAxesCont.shouldClone = false; bottomAxesCont.width = percent(100); bottomAxesCont.layout = "vertical"; bottomAxesCont.zIndex = 1; _this.bottomAxesContainer = bottomAxesCont; // Create a container for left-side axes var leftAxesCont = yAxesAndPlotCont.createChild(Container); leftAxesCont.shouldClone = false; leftAxesCont.layout = "horizontal"; leftAxesCont.height = percent(100); leftAxesCont.contentAlign = "right"; leftAxesCont.events.on("transformed", _this.updateXAxesMargins, _this, false); leftAxesCont.zIndex = 1; _this.leftAxesContainer = leftAxesCont; // Create a container for plot area var plotCont = yAxesAndPlotCont.createChild(Container); plotCont.shouldClone = false; plotCont.height = percent(100); plotCont.width = percent(100); // Create transparend background for plot container so that hover works // on all of it plotCont.background.fillOpacity = 0; _this.plotContainer = plotCont; // must go below plot container _this.mouseWheelBehavior = "none"; _this._cursorContainer = plotCont; // Create a container for right-side axes var rightAxesCont = yAxesAndPlotCont.createChild(Container); rightAxesCont.shouldClone = false; rightAxesCont.layout = "horizontal"; rightAxesCont.height = percent(100); rightAxesCont.zIndex = 1; rightAxesCont.events.on("transformed", _this.updateXAxesMargins, _this, false); _this.rightAxesContainer = rightAxesCont; _this.seriesContainer.parent = plotCont; _this.bulletsContainer.parent = plotCont; var zoomOutButton = plotCont.createChild(ZoomOutButton); zoomOutButton.shouldClone = false; zoomOutButton.align = "right"; zoomOutButton.valign = "top"; zoomOutButton.zIndex = Number.MAX_SAFE_INTEGER; zoomOutButton.marginTop = 5; zoomOutButton.marginRight = 5; zoomOutButton.hide(0); _this.zoomOutButton = zoomOutButton; // Create a container for bullets var axisBulletsContainer = _this.plotContainer.createChild(Container); axisBulletsContainer.shouldClone = false; axisBulletsContainer.width = percent(100); axisBulletsContainer.height = percent(100); axisBulletsContainer.isMeasured = false; axisBulletsContainer.zIndex = 4; axisBulletsContainer.layout = "none"; _this.axisBulletsContainer = axisBulletsContainer; _this._bulletMask = _this.plotContainer; _this.events.on("beforedatavalidated", function () { _this.series.each(function (series) { series.dataGrouped = false; series._baseInterval = {}; }); }, _this, false); // Apply theme _this.applyTheme(); return _this; } /** * Sets defaults that instantiate some objects that rely on parent, so they * cannot be set in constructor. */ XYChart.prototype.applyInternalDefaults = function () { _super.prototype.applyInternalDefaults.call(this); this.zoomOutButton.exportable = false; // Add a default screen reader title for accessibility // This will be overridden in screen reader if there are any `titles` set if (!$type.hasValue(this.readerTitle)) { this.readerTitle = this.language.translate("X/Y chart"); } }; /** * Draws the chart. * * @ignore Exclude from docs */ XYChart.prototype.draw = function () { _super.prototype.draw.call(this); this.seriesContainer.toFront(); this.bulletsContainer.toFront(); if (this.maskBullets) { this.bulletsContainer.mask = this._bulletMask; } this.updateSeriesLegend(); }; /** * Triggers a redrawing of all chart's series. * * @ignore Exclude from docs */ XYChart.prototype.updatePlotElements = function () { $iter.each(this.series.iterator(), function (series) { series.invalidate(); }); }; /** * Triggers data (re)validation which in turn can cause a redraw of the * whole chart or just aprticular series / elements. * * @ignore Exclude from docs */ XYChart.prototype.validateData = function () { // tell axes that data changed if (this._parseDataFrom == 0) { $iter.each(this.xAxes.iterator(), function (axis) { axis.dataChangeUpdate(); }); $iter.each(this.yAxes.iterator(), function (axis) { axis.dataChangeUpdate(); }); $iter.each(this.series.iterator(), function (series) { series.dataChangeUpdate(); }); } _super.prototype.validateData.call(this); }; /** * Updates margins for horizontal axes based on settings and available space. * * @ignore Exclude from docs */ XYChart.prototype.updateXAxesMargins = function () { var leftAxesWidth = this.leftAxesContainer.measuredWidth; var rightAxesWidth = this.rightAxesContainer.measuredWidth; var bottomAxesCont = this.bottomAxesContainer; if (bottomAxesCont.paddingLeft != leftAxesWidth || bottomAxesCont.paddingRight != rightAxesWidth) { bottomAxesCont.paddingLeft = leftAxesWidth; bottomAxesCont.paddingRight = rightAxesWidth; } var topAxesCont = this.topAxesContainer; if (topAxesCont.paddingLeft != leftAxesWidth || topAxesCont.paddingRight != rightAxesWidth) { topAxesCont.paddingLeft = leftAxesWidth; topAxesCont.paddingRight = rightAxesWidth; } }; /** * Triggers a re-initialization of this element. * * Will result in complete redrawing of the element. * * @ignore Exclude from docs */ XYChart.prototype.reinit = function () { _super.prototype.reinit.call(this); this.series.each(function (series) { series.appeared = false; }); }; /** * Triggers an update on the horizontal axis when one of its properties * change. * * @ignore Exclude from docs * @param event An event object */ XYChart.prototype.handleXAxisChange = function (event) { this.updateXAxis(event.target); }; /** * Triggers an update on the vertical axis when one of its properties * change. * * @ignore Exclude from docs * @param event An event object */ XYChart.prototype.handleYAxisChange = function (event) { this.updateYAxis(event.target); }; /** * Sets up a new horizontal (X) axis when it is added to the chart. * * @ignore Exclude from docs * @param event Axis insert event */ XYChart.prototype.processXAxis = function (event) { var axis = event.newValue; axis.chart = this; if (!axis.renderer) { axis.renderer = new this._axisRendererX(); axis.renderer.observe(["opposite", "inside", "inversed", "minGridDistance"], this.handleXAxisChange, this, false); } axis.axisLetter = "X"; axis.events.on("startendchanged", this.handleXAxisRangeChange, this, false); //axis.events.on("endchanged", this.handleXAxisRangeChange, this, false); // Although axis does not use data directly, we set dataProvider here // (but not add to chart data users) to hold up rendering before data // is parsed (system handles this) axis.dataProvider = this; this.updateXAxis(axis.renderer); this.processAxis(axis); }; /** * Sets up a new vertical (Y) axis when it is added to the chart. * * @ignore Exclude from docs * @param event Axis insert event */ XYChart.prototype.processYAxis = function (event) { var axis = event.newValue; axis.chart = this; if (!axis.renderer) { axis.renderer = new this._axisRendererY(); axis.renderer.observe(["opposite", "inside", "inversed", "minGridDistance"], this.handleYAxisChange, this, false); } axis.axisLetter = "Y"; axis.events.on("startendchanged", this.handleYAxisRangeChange, this, false); //axis.events.on("endchanged", this.handleYAxisRangeChange, this, false); // Although axis does not use data directly, we set dataProvider here // (but not add to chart data users) to hold up rendering before data // is parsed (system handles this) axis.dataProvider = this; this.updateYAxis(axis.renderer); this.processAxis(axis); }; /** * Updates horizontal (X) scrollbar and other horizontal axis whenever axis' * value range changes. */ XYChart.prototype.handleXAxisRangeChange = function () { var range = this.getCommonAxisRange(this.xAxes); if (this.scrollbarX) { this.zoomAxes(this.xAxes, range, true); } this.toggleZoomOutButton(); this.updateScrollbar(this.scrollbarX, range); }; /** * Shows or hides the Zoom Out button depending on whether the chart is fully * zoomed out or not. */ XYChart.prototype.toggleZoomOutButton = function () { if (this.zoomOutButton) { var show_1 = false; $iter.eachContinue(this.xAxes.iterator(), function (axis) { if (axis.toggleZoomOutButton) { if (axis.maxZoomCount > 0) { var minZoomFactor = axis.maxZoomFactor / axis.maxZoomCount; if ($math.round(axis.end - axis.start, 3) < 1 / minZoomFactor) { show_1 = true; return false; } } else { if ($math.round(axis.start, 3) > 0 || $math.round(axis.end, 3) < 1) { show_1 = true; return false; } } } return true; }); $iter.eachContinue(this.yAxes.iterator(), function (axis) { if (axis.toggleZoomOutButton) { if (axis.maxZoomCount > 0) { var minZoomFactor = axis.maxZoomFactor / axis.maxZoomCount; if ($math.round(axis.end - axis.start, 3) < 1 / minZoomFactor) { show_1 = true; return false; } } else { if ($math.round(axis.start, 3) > 0 || $math.round(axis.end, 3) < 1) { show_1 = true; return false; } } return true; } }); if (!this.seriesAppeared) { show_1 = false; } if (show_1) { this.zoomOutButton.show(); } else { this.zoomOutButton.hide(); } } }; /** * @ignore * moved this check to a separate method so that we could override it in TreeMapSeries */ XYChart.prototype.seriesAppeared = function () { var appeared = false; $iter.each(this.series.iterator(), function (series) { if (!series.appeared) { appeared = false; return false; } }); return appeared; }; /** * Updates vertical (Y) scrollbar and other horizontal axis whenever axis' * value range changes. */ XYChart.prototype.handleYAxisRangeChange = function () { var range = this.getCommonAxisRange(this.yAxes); if (this.scrollbarY) { this.zoomAxes(this.yAxes, range, true); } this.toggleZoomOutButton(); this.updateScrollbar(this.scrollbarY, range); }; /** * Updates a relative scrollbar whenever data range of the axis changes. * * @param scrollbar Scrollbar instance * @param range New data (values) range of the axis */ XYChart.prototype.updateScrollbar = function (scrollbar, range) { if (scrollbar) { scrollbar.skipRangeEvents(); scrollbar.start = range.start; scrollbar.end = range.end; } }; /** * Returns a common range of values between a list of axes. * * This is used to synchronize the zoom between multiple axes. * * @ignore Exclude from docs * @param axes A list of axes * @return Common value range */ XYChart.prototype.getCommonAxisRange = function (axes) { var start; var end; axes.each(function (axis) { if (!axis.zoomable || (axis instanceof ValueAxis && axis.syncWithAxis)) { } else { var axisStart = axis.start; var axisEnd = axis.end; if (axis.renderer.inversed) { axisStart = 1 - axis.end; axisEnd = 1 - axis.start; } if (!$type.isNumber(start) || (axisStart < start)) { start = axisStart; } if (!$type.isNumber(end) || (axisEnd > end)) { end = axisEnd; } } }); return { start: start, end: end }; }; /** * Triggers (re)rendering of the horizontal (X) axis. * * @ignore Exclude from docs * @param axis Axis */ XYChart.prototype.updateXAxis = function (renderer) { var axis = renderer.axis; if (renderer.opposite) { axis.parent = this.topAxesContainer; axis.toFront(); } else { axis.parent = this.bottomAxesContainer; axis.toBack(); } if (axis.renderer) { axis.renderer.processRenderer(); } }; /** * Triggers (re)rendering of the vertical (Y) axis. * * @ignore Exclude from docs * @param axis Axis */ XYChart.prototype.updateYAxis = function (renderer) { var axis = renderer.axis; if (renderer.opposite) { axis.parent = this.rightAxesContainer; axis.toBack(); } else { axis.parent = this.leftAxesContainer; axis.toFront(); } if (axis.renderer) { axis.renderer.processRenderer(); } }; /** * Decorates an Axis for use with this chart, e.g. sets proper renderer * and containers for placement. * * @param axis Axis */ XYChart.prototype.processAxis = function (axis) { var _this = this; // Value axis does not use data directly, only category axis does if (axis instanceof CategoryAxis) { this._dataUsers.moveValue(axis); } var renderer = axis.renderer; renderer.gridContainer.parent = this.plotContainer; renderer.gridContainer.toBack(); renderer.breakContainer.parent = this.plotContainer; renderer.breakContainer.toFront(); renderer.breakContainer.zIndex = 10; axis.addDisposer(new Disposer(function () { _this.dataUsers.removeValue(axis); })); renderer.bulletsContainer.parent = this.axisBulletsContainer; this._disposers.push(axis.events.on("positionchanged", function () { var point = $utils.spritePointToSprite({ x: 0, y: 0 }, axis, _this.axisBulletsContainer); if (axis.renderer instanceof AxisRendererY) { renderer.bulletsContainer.y = point.y; } if (axis.renderer instanceof AxisRendererX) { renderer.bulletsContainer.x = point.x; } }, undefined, false)); this.plotContainer.events.on("maxsizechanged", function () { if (_this.inited) { axis.invalidateDataItems(); _this.updateSeriesMasks(); } }, axis, false); }; /** * This is done because for some reason IE doesn't change mask if path of a * mask changes. */ XYChart.prototype.updateSeriesMasks = function () { if ($utils.isIE()) { this.series.each(function (series) { var mask = series.mainContainer.mask; series.mainContainer.mask = undefined; series.mainContainer.mask = mask; }); } }; XYChart.prototype.handleSeriesRemoved = function (event) { var series = event.oldValue; if (series) { if (series.xAxis) { series.xAxis.series.removeValue(series); series.xAxis.invalidateProcessedData(); } if (series.yAxis) { series.yAxis.series.removeValue(series); series.yAxis.invalidateProcessedData(); } // otherwise extremes won't change this.series.each(function (series) { series.resetExtremes(); }); } _super.prototype.handleSeriesRemoved.call(this, event); }; Object.defineProperty(XYChart.prototype, "xAxes", { /** * A list of horizontal (X) axes. * * @return List of axes */ get: function () { if (!this._xAxes) { this._xAxes = new List(); this._xAxes.events.on("inserted", this.processXAxis, this, false); this._xAxes.events.on("removed", this.handleAxisRemoval, this, false); this._disposers.push(new ListDisposer(this._xAxes, false)); } return this._xAxes; }, enumerable: true, configurable: true }); /** * @ignore */ XYChart.prototype.handleAxisRemoval = function (event) { var axis = event.oldValue; this.dataUsers.removeValue(axis); // need to remove, as it might not be disposed if (axis.autoDispose) { axis.dispose(); } }; Object.defineProperty(XYChart.prototype, "yAxes", { /** * A list of vertical (Y) axes. * * @return List of axes */ get: function () { if (!this._yAxes) { this._yAxes = new List(); this._yAxes.events.on("inserted", this.processYAxis, this, false); this._yAxes.events.on("removed", this.handleAxisRemoval, this, false); this._disposers.push(new ListDisposer(this._yAxes, false)); } return this._yAxes; }, enumerable: true, configurable: true }); /** * Decorates a new [[XYSeries]] object with required parameters when it is * added to the chart. * * @ignore Exclude from docs * @param event Event */ XYChart.prototype.handleSeriesAdded = function (event) { try { _super.prototype.handleSeriesAdded.call(this, event); var series = event.newValue; if (this.xAxes.length == 0 || this.yAxes.length == 0) { registry.removeFromInvalidComponents(series); series.dataInvalid = false; } $utils.used(series.xAxis); // this is enough to get axis, handled in getter $utils.used(series.yAxis); // this is enough to get axis, handled in getter series.maskBullets = series.maskBullets; if (series.fill == undefined) { if (this.patterns) { if (!$type.hasValue(series.stroke)) { series.stroke = this.colors.next(); } series.fill = this.patterns.next(); if ($type.hasValue(series.fillOpacity)) { series.fill.backgroundOpacity = series.fillOpacity; } if (series.stroke instanceof Color) { series.fill.stroke = series.stroke; series.fill.fill = series.stroke; } } else { series.fill = this.colors.next(); } } if (!$type.hasValue(series.stroke)) { series.stroke = series.fill; } } catch (e) { this.raiseCriticalError(e); } }; Object.defineProperty(XYChart.prototype, "cursor", { /** * @return Cursor */ get: function () { return this._cursor; }, /** * Chart's [[Cursor]]. * * @param cursor Cursor */ set: function (cursor) { if (this._cursor != cursor) { if (this._cursor) { this.removeDispose(this._cursor); } this._cursor = cursor; if (cursor) { // TODO this is wrong, fix it this._disposers.push(cursor); cursor.chart = this; cursor.shouldClone = false; cursor.parent = this._cursorContainer; cursor.events.on("cursorpositionchanged", this.handleCursorPositionChange, this, false); cursor.events.on("zoomstarted", this.handleCursorZoomStart, this, false); cursor.events.on("zoomended", this.handleCursorZoomEnd, this, false); cursor.events.on("panstarted", this.handleCursorPanStart, this, false); cursor.events.on("panning", this.handleCursorPanning, this, false); cursor.events.on("panended", this.handleCursorPanEnd, this, false); cursor.events.on("behaviorcanceled", this.handleCursorCanceled, this, false); cursor.events.on("hidden", this.handleHideCursor, this, false); cursor.zIndex = Number.MAX_SAFE_INTEGER - 1; if (this.tapToActivate) { // We need this in order to setup cursor properly this.setTapToActivate(this.tapToActivate); } } } }, enumerable: true, configurable: true }); /** * Performs tasks when the cursor's position changes, e.g. shows proper * tooltips on axes and series. * * @ignore Exclude from docs */ XYChart.prototype.handleCursorPositionChange = function () { var cursor = this.cursor; if (cursor.visible && !cursor.isHiding) { var xPosition_1 = this.cursor.xPosition; var yPosition_1 = this.cursor.yPosition; this.showSeriesTooltip({ x: xPosition_1, y: yPosition_1 }); var exceptAxes_1 = []; var snapToSeries = cursor.snapToSeries; if (snapToSeries && !cursor.downPoint) { if (snapToSeries instanceof XYSeries) { snapToSeries = [snapToSeries]; } var dataItems_1 = []; $array.each(snapToSeries, function (snpSeries) { if (!snpSeries.isHidden && !snpSeries.isHiding) { var xAxis = snpSeries.xAxis; var yAxis = snpSeries.yAxis; if (xAxis instanceof ValueAxis && !(xAxis instanceof DateAxis) && yAxis instanceof ValueAxis && !(yAxis instanceof DateAxis)) { snpSeries.dataItems.each(function (dataItem) { dataItems_1.push(dataItem); }); $array.move(exceptAxes_1, snpSeries.yAxis); $array.move(exceptAxes_1, snpSeries.xAxis); } else { if (snpSeries.baseAxis == snpSeries.xAxis) { $array.move(exceptAxes_1, snpSeries.yAxis); dataItems_1.push(xAxis.getSeriesDataItem(snpSeries, xAxis.toAxisPosition(xPosition_1), true)); } if (snpSeries.baseAxis == snpSeries.yAxis) { $array.move(exceptAxes_1, snpSeries.xAxis); dataItems_1.push(yAxis.getSeriesDataItem(snpSeries, yAxis.toAxisPosition(yPosition_1), true)); } } } }); var closestDataItem_1 = this.getClosest(dataItems_1, xPosition_1, yPosition_1); if (closestDataItem_1) { this.series.each(function (series) { var closestSeries = closestDataItem_1.component; if (series != closestSeries) { series.hideTooltip(); if (series.xAxis != closestSeries.xAxis) { series.xAxis.hideTooltip(); exceptAxes_1.push(series.xAxis); } if (series.yAxis != closestSeries.yAxis) { series.yAxis.hideTooltip(); exceptAxes_1.push(series.yAxis); } } }); closestDataItem_1.component.showTooltipAtDataItem(closestDataItem_1); cursor.handleSnap(closestDataItem_1.component); } } //} this._seriesPoints = []; if (this._cursorXPosition != xPosition_1) { this.showAxisTooltip(this.xAxes, xPosition_1, exceptAxes_1); } if (this._cursorYPosition != yPosition_1) { this.showAxisTooltip(this.yAxes, yPosition_1, exceptAxes_1); } if (this.arrangeTooltips) { this.sortSeriesTooltips(this._seriesPoints); } if (this.legend) { this.legend.afterDraw(); } } }; /** * Finds closest data item to position out of the array of items. * * @since 4.9.29 * @param dataItems Array of items * @param xPosition X position * @param yPosition Y position * @return Data item */ XYChart.prototype.getClosest = function (dataItems, xPosition, yPosition) { var minDistance = Infinity; var closestDataItem; $array.eachContinue(dataItems, function (dataItem) { if (dataItem) { var xAxis = dataItem.component.xAxis; var yAxis = dataItem.component.yAxis; var xPos = xAxis.positionToCoordinate(xAxis.toGlobalPosition(xAxis.toAxisPosition(xPosition))); var yPos = yAxis.positionToCoordinate(yAxis.toGlobalPosition(yAxis.toAxisPosition(yPosition))); var xField = dataItem.component.xField; var yField = dataItem.component.yField; if (xAxis instanceof ValueAxis && !$type.isNumber(dataItem.getValue(xField))) { return true; } if (yAxis instanceof ValueAxis && !$type.isNumber(dataItem.getValue(yField))) { return true; } var dxPosition = xAxis.positionToCoordinate(xAxis.toGlobalPosition(xAxis.getPositionX(dataItem, xField, dataItem.locations[xField], "valueX"))); var dyPosition = yAxis.positionToCoordinate(yAxis.toGlobalPosition(yAxis.getPositionY(dataItem, yField, dataItem.locations[yField], "valueY"))); var distance = Math.sqrt(Math.pow(xPos - dxPosition, 2) + Math.pow(yPos - dyPosition, 2)); if (distance < minDistance) { minDistance = distance; closestDataItem = dataItem; } return true; } }); return closestDataItem; }; /** * Hides all cursor-related tooltips when the cursor itself is hidden. * * @ignore Exclude from docs */ XYChart.prototype.handleHideCursor = function () { this.hideObjectTooltip(this.xAxes); this.hideObjectTooltip(this.yAxes); this.hideObjectTooltip(this.series); this._cursorXPosition = undefined; this._cursorYPosition = undefined; this.updateSeriesLegend(); }; /** * Updates values for each series' legend item. * * @ignore Exclude from docs */ XYChart.prototype.updateSeriesLegend = function () { $iter.each(this.series.iterator(), function (series) { series.updateLegendValue(); }); }; /** * Hides a tooltip for a list of objects. * * @ignore Exclude from docs * @param sprites A list of sprites to hide tooltip for */ XYChart.prototype.hideObjectTooltip = function (sprites) { $iter.each(sprites.iterator(), function (sprite) { if (sprite.cursorTooltipEnabled) { sprite.hideTooltip(0); } }); }; /** * Shows a tooltip for all chart's series, using specific coordinates as a * reference point. * * The tooltip might be shown at different coordinates depending on the * actual data point's position, overlapping with other tooltips, etc. * * @ignore Exclude from docs * @param position Reference point */ XYChart.prototype.showSeriesTooltip = function (position) { var _this = this; if (!position) { this.series.each(function (series) { series.hideTooltip(); }); return; } var seriesPoints = []; this.series.each(function (series) { //if (series.tooltipText || series.tooltipHTML) { // not good, bullets are not hovered then if ((series.xAxis instanceof DateAxis && series.xAxis.snapTooltip) || (series.yAxis instanceof DateAxis && series.yAxis.snapTooltip)) { // void } else { var point = series.showTooltipAtPosition(position.x, position.y); if (point) { series.tooltip.setBounds($utils.spriteRectToSvg({ x: 0, y: 0, width: _this.pixelWidth, height: _this.pixelHeight }, _this)); seriesPoints.push({ series: series, point: point }); } } //} }); if (this.arrangeTooltips) { this.sortSeriesTooltips(seriesPoints); } }; /** * @ignore */ XYChart.prototype.sortSeriesTooltips = function (seriesPoints) { if (seriesPoints.length > 0) { var cursor_1 = this.cursor; if (cursor_1 && $type.isNumber(cursor_1.maxTooltipDistance)) { var cursorPoint_1 = $utils.spritePointToSvg({ x: cursor_1.point.x, y: cursor_1.point.y }, cursor_1); var nearestSeries_1; var nearestPoint_1; var smallestDistance_1 = Infinity; $array.each(seriesPoints, function (seriesPoint) { var series = seriesPoint.series; var fixedPoint = seriesPoint.point; if (fixedPoint) { var point = { x: fixedPoint.x, y: fixedPoint.y }; var distance = Math.abs($math.getDistance(point, cursorPoint_1)); if (distance < smallestDistance_1) { nearestPoint_1 = point; smallestDistance_1 = distance; nearestSeries_1 = series; } } }); var newSeriesPoints_1 = []; if (nearestSeries_1) { $array.each(seriesPoints, function (seriesPoint) { if (Math.abs($math.getDistance(seriesPoint.point, nearestPoint_1)) <= Math.abs(cursor_1.maxTooltipDistance)) { newSeriesPoints_1.push({ series: seriesPoint.series, point: seriesPoint.point }); } else { var tooltipDataItem = seriesPoint.series.tooltipDataItem; if (tooltipDataItem) { $array.each(tooltipDataItem.sprites, function (sprite) { sprite.isHover = false; sprite.handleOutReal(); // to avoid flicker }); } seriesPoint.series.hideTooltip(0); } }); if (cursor_1.maxTooltipDistance < 0) { if (newSeriesPoints_1.length > 0) { $array.each(newSeriesPoints_1, function (np) { if (nearestSeries_1 != np.series) { np.series.hideTooltip(0); } }); } newSeriesPoints_1 = [{ series: nearestSeries_1, point: nearestPoint_1 }]; } } seriesPoints = newSeriesPoints_1; } var topLeft_1 = $utils.spritePointToSvg({ x: -0.5, y: -0.5 }, this.plotContainer); var bottomRight_1 = $utils.spritePointToSvg({ x: this.plotContainer.pixelWidth + 0.5, y: this.plotContainer.pixelHeight + 0.5 }, this.plotContainer); var sum_1 = 0; var filteredSeriesPoints_1 = []; $array.each(seriesPoints, function (seriesPoint) { var point = seriesPoint.point; if (point && $math.isInRectangle(point, { x: topLeft_1.x, y: topLeft_1.y, width: bottomRight_1.x - topLeft_1.x, height: bottomRight_1.y - topLeft_1.y })) { filteredSeriesPoints_1.push({ point: point, series: seriesPoint.series }); sum_1 += point.y; } }); seriesPoints = filteredSeriesPoints_1; var firstSeries = this.series.getIndex(0); var inversed = false; if (firstSeries && firstSeries.yAxis && firstSeries.yAxis.renderer.inversed) { inversed = true; } if (inversed) { seriesPoints.sort(function (a, b) { return $number.order(a.point.y, b.point.y); }); } else { seriesPoints.sort(function (a, b) { return $number.order(b.point.y, a.point.y); }); seriesPoints.reverse(); } var averageY = sum_1 / seriesPoints.length; var maxY = $utils.svgPointToDocument({ x: 0, y: 0 }, this.svgContainer.SVGContainer).y; if (seriesPoints.length > 0) { var top_1 = topLeft_1.y; var bottom = bottomRight_1.y; // TODO is this needed ? $utils.spritePointToDocument({ x: 0, y: top_1 }, this); var dropped = false; if (averageY > top_1 + (bottom - top_1) / 2) { var nextHeight = bottom; for (var i = seriesPoints.length - 1; i >= 0; i--) { var series = seriesPoints[i].series; var tooltip = series.tooltip; var pointY = seriesPoints[i].point.y; tooltip.setBounds({ x: 0, y: -maxY, width: this.pixelWidth, height: nextHeight + maxY }); if (tooltip.invalid) { tooltip.validate(); } tooltip.toBack(); nextHeight = $utils.spritePointToSvg({ x: 0, y: tooltip.label.pixelY - tooltip.pixelY + pointY - tooltip.pixelMarginTop }, tooltip).y; if (nextHeight < -maxY) { dropped = true; break; } } } if (averageY <= top_1 + (bottom - top_1) / 2 || dropped) { var nextY = top_1; for (var i = 0, len = seriesPoints.length; i < len; i++) { var series = seriesPoints[i].series; var pointY = seriesPoints[i].point.y; var tooltip = series.tooltip; tooltip.setBounds({ x: 0, y: nextY, width: this.pixelWidth, height: bottom }); if (tooltip.invalid) { tooltip.validate(); } tooltip.toBack(); nextY = $utils.spritePointToSvg({ x: 0, y: tooltip.label.pixelY + tooltip.label.measuredHeight - tooltip.pixelY + pointY + tooltip.pixelMarginBottom }, tooltip).y; } } } } }; /** * Shows tooltips for a list of axes at specific position. * * Position might be X coordinate for horizontal axes, and Y coordinate for * vertical axes. * * @ignore Exclude from docs * @param axes List of axes to show tooltip on * @param position Position (px) */ XYChart.prototype.showAxisTooltip = function (axes, position, except) { var _this = this; $iter.each(axes.iterator(), function (axis) { if (!except || except.indexOf(axis) == -1) { if (_this.dataItems.length > 0 || axis.dataItems.length > 0) { axis.showTooltipAtPosition(position); } } }); }; /** * Recalculates the value range for the axis taking into account zoom level & inversed. * * @param axis Axis * @param range Range * @return Modified range */ XYChart.prototype.getUpdatedRange = function (axis, range) { if (!axis) { return; } var start; var end; var inversed = axis.renderer.inversed; if (inversed) { $math.invertRange(range); start = 1 - axis.end; end = 1 - axis.start; } else { start = axis.start; end = axis.end; } var difference = end - start; return { start: start + range.start * difference, end: start + range.end * difference }; }; /** * Performs zoom and other operations when user finishes zooming using chart * cursor, e.g. zooms axes. * * @param event Cursor's event */ XYChart.prototype.handleCursorZoomEnd = function (event) { var cursor = this.cursor; var behavior = cursor.behavior; if (behavior == "zoomX" || behavior == "zoomXY") { var xRange = cursor.xRange; if (xRange && this.xAxes.length > 0) { xRange = this.getUpdatedRange(this.xAxes.getIndex(0), xRange); xRange.priority = "start"; this.zoomAxes(this.xAxes, xRange); } } if (behavior == "zoomY" || behavior == "zoomXY") { var yRange = cursor.yRange; if (yRange && this.yAxes.length > 0) { yRange = this.getUpdatedRange(this.yAxes.getIndex(0), yRange); yRange.priority = "start"; this.zoomAxes(this.yAxes, yRange); } } this.handleHideCursor(); }; /** * Performs zoom and other operations when user is panning chart plot using chart cursor. * * @param event Cursor's event */ XYChart.prototype.handleCursorPanStart = function (event) { var xAxis = this.xAxes.getIndex(0); if (xAxis) { this._panStartXRange = { start: xAxis.start, end: xAxis.end }; if (xAxis.renderer.inversed) { this._panStartXRange = $math.invertRange(this._panStartXRange); } } var yAxis = this.yAxes.getIndex(0); if (yAxis) { this._panStartYRange = { start: yAxis.start, end: yAxis.end }; if (yAxis.renderer.inversed) { this._panStartYRange = $math.invertRange(this._panStartYRange); } } }; /** * Performs zoom and other operations when user ends panning * * @param event Cursor's event */ XYChart.prototype.handleCursorPanEnd = function (event) { var cursor = this.cursor; var behavior = cursor.behavior; if (this._panEndXRange && (behavior == "panX" || behavior == "panXY")) { var panEndRange = this._panEndXRange; var panStartRange = this._panStartXRange; var delta = 0; if (panEndRange.start < 0) { delta