@amcharts/amcharts5
Version:
amCharts 5
833 lines • 30.5 kB
JavaScript
import { DataItem } from "../../../core/render/Component";
import { Component } from "../../../core/render/Component";
import { Container } from "../../../core/render/Container";
import { p100 } from "../../../core/util/Percent";
import { List } from "../../../core/util/List";
import { Rectangle } from "../../../core/render/Rectangle";
import * as $array from "../../../core/util/Array";
import * as $type from "../../../core/util/Type";
import * as $utils from "../../../core/util/Utils";
/**
* A base class for all axes.
*
* @see {@link https://www.amcharts.com/docs/v5/charts/xy-chart/#Adding_axes} for more info
*/
export class Axis extends Component {
constructor() {
super(...arguments);
this._series = [];
this._isPanning = false;
/**
* Array of minor data items.
*/
this.minorDataItems = [];
/**
* A [[Container]] that holds all the axis label elements.
*
* @default Container.new()
*/
this.labelsContainer = this.children.push(Container.new(this._root, {}));
/**
* A [[Container]] that holds all the axis grid and fill elements.
*
* @default Container.new()
*/
this.gridContainer = Container.new(this._root, { width: p100, height: p100 });
/**
* A [[Container]] that holds axis grid elements which goes above the series.
*
* @default Container.new()
*/
this.topGridContainer = Container.new(this._root, { width: p100, height: p100 });
/**
* A [[Container]] that holds all the axis bullet elements.
*
* @default new Container
*/
this.bulletsContainer = this.children.push(Container.new(this._root, { isMeasured: false, width: p100, height: p100, position: "absolute" }));
this._rangesDirty = false;
this._panStart = 0;
this._panEnd = 1;
this._skipSync = false;
/**
* A list of axis ranges.
*
* @see {@link https://www.amcharts.com/docs/v5/charts/xy-chart/axes/axis-ranges/} for more info
* @default new List()
*/
this.axisRanges = new List();
this._seriesAxisRanges = [];
this._cursorPosition = -1;
this._seriesValuesDirty = false;
this._seriesAdded = false;
/**
* A container above the axis that can be used to add additional stuff into
* it. For example a legend, label, or an icon.
*
* @see {@link https://www.amcharts.com/docs/v5/charts/xy-chart/axes/axis-headers/} for more info
* @default new Container
*/
this.axisHeader = this.children.push(Container.new(this._root, {
themeTags: ["axis", "header"],
position: "absolute",
background: Rectangle.new(this._root, {
themeTags: ["header", "background"],
fill: this._root.interfaceColors.get("background")
})
}));
this._bullets = {};
}
/**
* @ignore
* Returns `true` if this axis is currently sync-driven (and therefore its
* `start`/`end` and rendered grid should reflect the synced bounds even
* when they exceed the axis data range).
*/
_isSynced() {
return false;
}
_dispose() {
// these could be in other parents, so disposing just in case
this.gridContainer.dispose();
this.topGridContainer.dispose();
this.bulletsContainer.dispose();
this.labelsContainer.dispose();
this.axisHeader.dispose();
super._dispose();
}
_afterNew() {
super._afterNew();
this.setPrivate("updateScrollbar", true);
this._disposers.push(this.axisRanges.events.onAll((change) => {
if (change.type === "clear") {
$array.each(change.oldValues, (dataItem) => {
dataItem.dispose();
});
}
else if (change.type === "push") {
this._processAxisRange(change.newValue, ["range"]);
}
else if (change.type === "setIndex") {
this._processAxisRange(change.newValue, ["range"]);
}
else if (change.type === "insertIndex") {
this._processAxisRange(change.newValue, ["range"]);
}
else if (change.type === "removeIndex") {
this.disposeDataItem(change.oldValue);
}
else if (change.type === "moveIndex") {
this._processAxisRange(change.value, ["range"]);
}
else {
throw new Error("Unknown IStreamEvent type");
}
}));
const renderer = this.get("renderer");
if (renderer) {
renderer.axis = this;
renderer.processAxis();
}
this.children.push(renderer);
this._createGhostLabel();
}
_createGhostLabel() {
const renderer = this.get("renderer");
const ghostLabel = renderer.makeLabel(new DataItem(this, undefined, {}), []);
ghostLabel.adapters.disable("text");
ghostLabel.setAll({ opacity: 0, tooltipText: undefined, tooltipHTML: undefined, interactive: false });
ghostLabel.events.disable();
this.ghostLabel = ghostLabel;
}
_updateFinals(_start, _end) {
}
/**
* Zooms the axis to relative locations.
*
* Both `start` and `end` are relative: 0 means start of the axis, 1 - end.
*
* @param start Relative start
* @param end Relative end
* @param duration Duration of the zoom animation in milliseconds
* @return Zoom animation
*/
zoom(start, end, duration, priority) {
if (this.get("zoomable", true)) {
this._updateFinals(start, end);
if (this.get("start") !== start || this.get("end") != end) {
let sAnimation = this._sAnimation;
let eAnimation = this._eAnimation;
if (start > end) {
[start, end] = [end, start];
}
if (!this._isSynced()) {
let maxDeviation = this.get("maxDeviation", 0.5) * Math.min(1, (end - start));
if (start < -maxDeviation) {
start = -maxDeviation;
}
if (end > 1 + maxDeviation) {
end = 1 + maxDeviation;
}
}
let maxZoomFactor = this.getPrivate("maxZoomFactor", this.get("maxZoomFactor", 100));
let maxZoomFactorReal = maxZoomFactor;
if (!$type.isNumber(duration)) {
duration = this.get("interpolationDuration", 0);
}
if (!priority) {
priority = "end";
}
if (end === 1 && start !== 0) {
if (start < this.get("start", 0)) {
priority = "start";
}
else {
priority = "end";
}
}
if (start === 0 && end !== 1) {
if (end > this.get("end", 1)) {
priority = "end";
}
else {
priority = "start";
}
}
let minZoomCount = this.get("minZoomCount", 0);
const dataItems = this.dataItems;
if (dataItems && dataItems.length < minZoomCount) {
minZoomCount = dataItems.length;
}
let maxZoomCount = this.get("maxZoomCount", Infinity);
if ($type.isNumber(minZoomCount)) {
maxZoomFactor = maxZoomFactorReal / minZoomCount;
}
let minZoomFactor = 1;
if ($type.isNumber(maxZoomCount)) {
minZoomFactor = maxZoomFactorReal / maxZoomCount;
}
// need one more
if (start > end) {
[start, end] = [end, start];
}
// most likely we are dragging left scrollbar grip here, so we tend to modify end
if (priority === "start") {
if (maxZoomCount > 0) {
// add to the end
if (1 / (end - start) < minZoomFactor) {
end = start + 1 / minZoomFactor;
}
}
// add to the end
if (1 / (end - start) > maxZoomFactor) {
end = start + 1 / maxZoomFactor;
}
//unless end is > 0
if (end > 1 && end - start < 1 / maxZoomFactor) {
//end = 1;
start = end - 1 / maxZoomFactor;
}
}
// most likely we are dragging right, so we modify left
else {
if (maxZoomCount > 0) {
// add to the end
if (1 / (end - start) < minZoomFactor) {
start = end - 1 / minZoomFactor;
}
}
// remove from start
if (1 / (end - start) > maxZoomFactor) {
start = end - 1 / maxZoomFactor;
}
if (start < 0 && end - start < 1 / maxZoomFactor) {
//start = 0;
end = start + 1 / maxZoomFactor;
}
}
if (1 / (end - start) > maxZoomFactor) {
end = start + 1 / maxZoomFactor;
}
if (1 / (end - start) > maxZoomFactor) {
start = end - 1 / maxZoomFactor;
}
if (maxZoomCount != null && minZoomCount != null && (start == this.get("start") && end == this.get("end"))) {
const chart = this.chart;
if (chart) {
chart._handleAxisSelection(this, true);
}
}
if (((sAnimation && sAnimation.playing && sAnimation.to == start) || this.get("start") == start) && ((eAnimation && eAnimation.playing && eAnimation.to == end) || this.get("end") == end)) {
return;
}
if (duration > 0) {
let easing = this.get("interpolationEasing");
let sAnimation, eAnimation;
if (this.get("start") != start) {
sAnimation = this.animate({ key: "start", to: start, duration: duration, easing: easing });
}
if (this.get("end") != end) {
eAnimation = this.animate({ key: "end", to: end, duration: duration, easing: easing });
}
this._sAnimation = sAnimation;
this._eAnimation = eAnimation;
if (sAnimation) {
return sAnimation;
}
else if (eAnimation) {
return eAnimation;
}
}
else {
this._setC("start", start);
this._setC("end", end);
this.root.events.once("frameended", () => {
this.markDirtyKey("start");
});
}
}
else {
if (this._sAnimation) {
this._sAnimation.stop();
}
if (this._eAnimation) {
this._eAnimation.stop();
}
}
}
}
/**
* A list of series using this axis.
*
* @return Series
*/
get series() {
return this._series;
}
_processAxisRange(dataItem, themeTags) {
dataItem.setRaw("isRange", true);
this._createAssets(dataItem, themeTags);
this._rangesDirty = true;
this._prepareDataItem(dataItem);
const above = dataItem.get("above");
const container = this.topGridContainer;
const grid = dataItem.get("grid");
if (above && grid) {
container.children.moveValue(grid);
}
const fill = dataItem.get("axisFill");
if (above && fill) {
container.children.moveValue(fill);
}
}
_prepareDataItem(_dataItem, _index) { }
/**
* @ignore
*/
markDirtyExtremes() {
}
/**
* @ignore
*/
markDirtySelectionExtremes() {
}
_calculateTotals() {
}
_updateAxisRanges() {
this._bullets = {};
this.axisRanges.each((axisRange) => {
this._prepareDataItem(axisRange);
});
$array.each(this._seriesAxisRanges, (axisRange) => {
this._prepareDataItem(axisRange);
});
}
_prepareChildren() {
super._prepareChildren();
const ghostLabel = this.ghostLabel;
if (ghostLabel) {
if (this.get("fixAxisSize")) {
ghostLabel.set("visible", true);
}
else {
ghostLabel.set("visible", false);
}
}
if (this.isDirty("start") || this.isDirty("end")) {
const chart = this.chart;
if (chart) {
chart._updateCursor();
}
let start = this.get("start", 0);
let end = this.get("end", 1);
if (!this._isSynced()) {
let maxDeviation = this.get("maxDeviation", 0.5) * Math.min(1, (end - start));
if (start < -maxDeviation) {
let delta = start + maxDeviation;
start = -maxDeviation;
this.setRaw("start", start);
if (this.isDirty("end")) {
this.setRaw("end", end - delta);
}
}
if (end > 1 + maxDeviation) {
let delta = end - 1 - maxDeviation;
end = 1 + maxDeviation;
this.setRaw("end", end);
if (this.isDirty("start")) {
this.setRaw("start", start - delta);
}
}
}
}
const renderer = this.get("renderer");
renderer._start = this.get("start");
renderer._end = this.get("end");
renderer._inversed = renderer.get("inversed", false);
renderer._axisLength = renderer.axisLength() / (renderer._end - renderer._start);
renderer._updateLC();
if (this.isDirty("tooltip")) {
const tooltip = this.get("tooltip");
if (tooltip) {
const rendererTags = renderer.get("themeTags");
tooltip.addTag("axis");
tooltip.addTag(this.className.toLowerCase());
tooltip._applyThemes();
if (rendererTags) {
tooltip.set("themeTags", $utils.mergeTags(tooltip.get("themeTags"), rendererTags));
tooltip.label._applyThemes();
}
}
}
}
_updateTooltipBounds() {
const tooltip = this.get("tooltip");
if (tooltip) {
this.get("renderer").updateTooltipBounds(tooltip);
}
}
_updateBounds() {
super._updateBounds();
this._updateTooltipBounds();
}
/**
* @ignore
*/
processChart(chart) {
this.chart = chart;
const renderer = this.get("renderer");
renderer.chart = chart;
chart.gridContainer.children.push(this.gridContainer);
chart.topGridContainer.children.push(this.topGridContainer);
chart.axisHeadersContainer.children.push(this.axisHeader);
this.on("start", () => {
chart._handleAxisSelection(this);
});
this.on("end", () => {
chart._handleAxisSelection(this);
});
chart.plotContainer.onPrivate("width", () => {
this.markDirtySize();
});
chart.plotContainer.onPrivate("height", () => {
this.markDirtySize();
});
chart.processAxis(this);
}
/**
* @ignore
*/
hideDataItem(dataItem) {
this._toggleFHDataItem(dataItem, true);
return super.hideDataItem(dataItem);
}
/**
* @ignore
*/
showDataItem(dataItem) {
this._toggleFHDataItem(dataItem, false);
return super.showDataItem(dataItem);
}
_toggleFHDataItem(dataItem, forceHidden) {
const fh = "forceHidden";
const label = dataItem.get("label");
if (label) {
label._setC(fh, forceHidden);
}
const grid = dataItem.get("grid");
if (grid) {
grid._setC(fh, forceHidden);
}
const tick = dataItem.get("tick");
if (tick) {
tick._setC(fh, forceHidden);
}
const axisFill = dataItem.get("axisFill");
if (axisFill) {
axisFill._setC(fh, forceHidden);
}
const bullet = dataItem.get("bullet");
if (bullet) {
const sprite = bullet.get("sprite");
if (sprite) {
sprite._setC(fh, forceHidden);
}
}
}
_toggleDataItem(dataItem, visible) {
const label = dataItem.get("label");
const v = "visible";
if (label) {
label.setPrivate(v, visible);
}
const grid = dataItem.get("grid");
if (grid) {
grid.setPrivate(v, visible);
}
const tick = dataItem.get("tick");
if (tick) {
tick.setPrivate(v, visible);
}
const axisFill = dataItem.get("axisFill");
if (axisFill) {
axisFill.setPrivate(v, visible);
}
const bullet = dataItem.get("bullet");
if (bullet) {
const sprite = bullet.get("sprite");
if (sprite) {
sprite.setPrivate(v, visible);
}
}
}
_createAssets(dataItem, tags, minor) {
var _a, _b, _c;
const renderer = this.get("renderer");
let m = "minor";
const label = dataItem.get("label");
if (!label) {
renderer.makeLabel(dataItem, tags);
}
else {
let themeTags = label.get("themeTags");
let remove = false;
if (minor) {
if ((themeTags === null || themeTags === void 0 ? void 0 : themeTags.indexOf(m)) == -1) {
remove = true;
}
}
else {
if ((themeTags === null || themeTags === void 0 ? void 0 : themeTags.indexOf(m)) != -1) {
remove = true;
}
}
if (remove) {
(_a = label.parent) === null || _a === void 0 ? void 0 : _a.children.removeValue(label);
renderer.makeLabel(dataItem, tags);
label.dispose();
renderer.labels.removeValue(label);
}
}
const grid = dataItem.get("grid");
if (!grid) {
renderer.makeGrid(dataItem, tags);
}
else {
let themeTags = grid.get("themeTags");
let remove = false;
if (minor) {
if ((themeTags === null || themeTags === void 0 ? void 0 : themeTags.indexOf(m)) == -1) {
remove = true;
}
}
else {
if ((themeTags === null || themeTags === void 0 ? void 0 : themeTags.indexOf(m)) != -1) {
remove = true;
}
}
if (remove) {
(_b = grid.parent) === null || _b === void 0 ? void 0 : _b.children.removeValue(grid);
renderer.makeGrid(dataItem, tags);
grid.dispose();
renderer.grid.removeValue(grid);
}
}
const tick = dataItem.get("tick");
if (!tick) {
renderer.makeTick(dataItem, tags);
}
else {
let remove = false;
let themeTags = tick.get("themeTags");
if (minor) {
if ((themeTags === null || themeTags === void 0 ? void 0 : themeTags.indexOf(m)) == -1) {
remove = true;
}
}
else {
if ((themeTags === null || themeTags === void 0 ? void 0 : themeTags.indexOf(m)) != -1) {
remove = true;
}
}
if (remove) {
(_c = tick.parent) === null || _c === void 0 ? void 0 : _c.children.removeValue(tick);
renderer.makeTick(dataItem, tags);
tick.dispose();
renderer.ticks.removeValue(tick);
}
}
if (!minor && !dataItem.get("axisFill")) {
renderer.makeAxisFill(dataItem, tags);
}
else if (minor && !dataItem.get("axisFill") && this.get("minorAxisFillsEnabled")) {
renderer.makeAxisFill(dataItem, tags);
}
this._processBullet(dataItem);
}
_processBullet(dataItem) {
let bullet = dataItem.get("bullet");
let axisBullet = this.get("bullet");
if (!bullet && axisBullet && !dataItem.get("isRange")) {
bullet = axisBullet(this._root, this, dataItem);
}
if (bullet) {
bullet.axis = this;
const sprite = bullet.get("sprite");
if (sprite) {
sprite._setDataItem(dataItem);
dataItem.setRaw("bullet", bullet);
if (!sprite.parent) {
this.bulletsContainer.children.push(sprite);
}
}
}
}
_afterChanged() {
super._afterChanged();
const chart = this.chart;
if (chart) {
chart._updateChartLayout();
chart.axisHeadersContainer.markDirtySize();
}
this.get("renderer")._updatePositions();
this._seriesAdded = false;
}
/**
* @ignore
*/
disposeDataItem(dataItem) {
super.disposeDataItem(dataItem);
const renderer = this.get("renderer");
const label = dataItem.get("label");
if (label) {
renderer.labels.removeValue(label);
label.dispose();
}
const tick = dataItem.get("tick");
if (tick) {
renderer.ticks.removeValue(tick);
tick.dispose();
}
const grid = dataItem.get("grid");
if (grid) {
renderer.grid.removeValue(grid);
grid.dispose();
}
const axisFill = dataItem.get("axisFill");
if (axisFill) {
renderer.axisFills.removeValue(axisFill);
axisFill.dispose();
}
const bullet = dataItem.get("bullet");
if (bullet) {
bullet.dispose();
}
}
_updateGhost() {
this.setPrivate("cellWidth", this.getCellWidthPosition() * this.get("renderer").axisLength());
const ghostLabel = this.ghostLabel;
if (ghostLabel) {
if (!ghostLabel.isHidden()) {
const bounds = ghostLabel.localBounds();
const gWidth = Math.ceil(bounds.right - bounds.left);
let text = ghostLabel.get("text");
$array.each(this.dataItems, (dataItem) => {
const label = dataItem.get("label");
if (label && !label.isHidden()) {
const bounds = label.localBounds();
const w = Math.ceil(bounds.right - bounds.left);
if (w > gWidth) {
text = label.text._getText();
}
}
});
ghostLabel.set("text", text);
}
let start = this.get("start", 0);
let end = this.get("end", 1);
this.get("renderer").updateLabel(ghostLabel, start + (end - start) * 0.5);
}
}
_handleCursorPosition(position, snapToSeries) {
const renderer = this.get("renderer");
position = renderer.toAxisPosition(position);
this._cursorPosition = position;
this._snapToSeries = snapToSeries;
this.updateTooltip();
}
/**
* Can be called when axis zoom changes and you need to update tooltip
* position.
*/
updateTooltip() {
const snapToSeries = this._snapToSeries;
let position = this._cursorPosition;
const tooltip = this.get("tooltip");
const renderer = this.get("renderer");
if ($type.isNumber(position)) {
$array.each(this.series, (series) => {
if (series.get("baseAxis") === this) {
const dataItem = this.getSeriesItem(series, position, this.get("tooltipLocation"));
if (snapToSeries && snapToSeries.indexOf(series) != -1) {
series.updateLegendMarker(dataItem);
series.updateLegendValue(dataItem);
series._settings.tooltipDataItem = dataItem;
}
else {
series.showDataItemTooltip(dataItem);
series.setRaw("tooltipDataItem", dataItem);
}
}
});
if (this.get("snapTooltip")) {
position = this.roundAxisPosition(position, this.get("tooltipLocation", 0.5));
}
this.setPrivateRaw("tooltipPosition", position);
if (tooltip) {
renderer.updateTooltipBounds(tooltip);
if (!$type.isNaN(position)) {
this._updateTooltipText(tooltip, position);
renderer.positionTooltip(tooltip, position);
if (position < this.get("start", 0) || position > this.get("end", 1)) {
tooltip.hide(0);
}
else {
tooltip.show(0);
}
}
else {
tooltip.hide(0);
}
}
}
}
_updateTooltipText(tooltip, position) {
tooltip.label.set("text", this.getTooltipText(position));
}
/**
* @ignore
*/
roundAxisPosition(position, _location) {
return position;
}
_handleSeriesRemoved() {
}
/**
* @ignore
*/
handleCursorShow() {
let tooltip = this.get("tooltip");
if (tooltip) {
tooltip.show();
}
}
/**
* @ignore
*/
handleCursorHide() {
let tooltip = this.get("tooltip");
if (tooltip) {
tooltip.hide();
}
}
/**
* @ignore
*/
processSeriesDataItem(_dataItem, _fields) {
}
_clearDirty() {
super._clearDirty();
this._sizeDirty = false;
this._rangesDirty = false;
}
/**
* Converts pixel coordinate to a relative position on axis.
*
* @param coordinate Coordinate
* @return Relative position
*/
coordinateToPosition(coordinate) {
const renderer = this.get("renderer");
return renderer.toAxisPosition(coordinate / renderer.axisLength());
}
/**
* Converts relative position of the plot area to relative position of the
* axis with zoom taken into account.
*
* @param position Position
* @return Relative position
*/
toAxisPosition(position) {
return this.get("renderer").toAxisPosition(position);
}
/**
* Converts relative position of the axis to a global position taking current
* zoom into account (opposite to what `toAxisPosition` does).
*
* @since 5.4.2
* @param position Position
* @return Global position
*/
toGlobalPosition(position) {
return this.get("renderer").toGlobalPosition(position);
}
/**
* Adjusts position with inversed taken into account.
*
* @ignore
*/
fixPosition(position) {
return this.get("renderer").fixPosition(position);
}
/**
* @ignore
*/
shouldGap(_dataItem, _nextItem, _autoGapCount, _fieldName) {
return false;
}
/**
* Creates and returns an axis range object.
*
* @see {@link https://www.amcharts.com/docs/v5/charts/xy-chart/axes/axis-ranges/} for more info
* @param axisDataItem Axis data item
* @return Axis range
*/
createAxisRange(axisDataItem) {
return this.axisRanges.push(axisDataItem);
}
/**
* @ignore
*/
_groupSeriesData(_series) { }
/**
* Returns relative position between two grid lines of the axis.
*
* @return Position
*/
getCellWidthPosition() {
return 0.05;
}
}
Axis.className = "Axis";
Axis.classNames = Component.classNames.concat([Axis.className]);
//# sourceMappingURL=Axis.js.map