@amcharts/amcharts5
Version:
amCharts 5
1,363 lines • 59.7 kB
JavaScript
;
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