@amcharts/amcharts4
Version:
amCharts 4
1,262 lines • 87.8 kB
JavaScript
/**
* 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