@amcharts/amcharts5
Version:
amCharts 5
1,916 lines • 67.8 kB
JavaScript
import { __awaiter } from "tslib";
import { Entity } from "../util/Entity";
import { Template } from "../util/Template";
import { Percent } from "../util/Percent";
import { EventDispatcher } from "../util/EventDispatcher";
import { MultiDisposer, CounterDisposer } from "../util/Disposer";
import { waitForAnimations } from "../util/Animation";
import * as $utils from "../util/Utils";
import * as $array from "../util/Array";
import * as $type from "../util/Type";
import * as $object from "../util/Object";
import * as $math from "../util/Math";
//import { populateString } from "../util/PopulateString";
/**
* An [[EventDispatcher]] for [[Sprite]].
*
* @see {@link https://www.amcharts.com/docs/v5/concepts/events/} for more info
*/
class SpriteEventDispatcher extends EventDispatcher {
constructor(sprite) {
super();
this._rendererDisposers = {};
this._dispatchParents = true;
this._sprite = sprite;
}
_makePointerEvent(key, event) {
return {
type: key,
originalEvent: event.event,
point: event.point,
simulated: event.simulated,
native: event.native,
target: this._sprite
};
}
_onRenderer(key, dispatch) {
// TODO: is this OK? it'd be good not to require to set this on each individual element
this._sprite._setC("interactive", true);
this._sprite._display.interactive = true;
let events = this._rendererDisposers[key];
if (events === undefined) {
const disposer = this._sprite._display.on(key, (e) => {
dispatch.call(this, e);
});
events = this._rendererDisposers[key] = new CounterDisposer(() => {
delete this._rendererDisposers[key];
disposer.dispose();
});
}
return events.increment();
}
_on(once, type, callback, context, shouldClone, dispatch, debounceDelay) {
const info = super._on(once, type, callback, context, shouldClone, dispatch, debounceDelay);
const rendererEvent = SpriteEventDispatcher.RENDERER_EVENTS[type];
if (rendererEvent !== undefined) {
info.disposer = new MultiDisposer([
info.disposer,
this._onRenderer(type, rendererEvent),
]);
}
return info;
}
/**
* Will stop any bubbling up of the event to element's parents.
*
* Should be called in an event handler, e.g.:
*
* ```TypeScript
* element.events.on("pointerdown", function(ev) {
* // Do something here and prevent from "pointerdown" bubbling up
* // ...
* ev.target.events.stopParentDispatch();
* });
* ```
* ```JavaScript
* element.events.on("pointerdown", function(ev) {
* // Do something here and prevent from "pointerdown" bubbling up
* // ...
* ev.target.events.stopParentDispatch();
* });
* ```
*/
stopParentDispatch() {
this._dispatchParents = false;
}
/**
* @ignore
*/
dispatchParents(type, event) {
const old = this._dispatchParents;
this._dispatchParents = true;
try {
this.dispatch(type, event);
if (this._dispatchParents && this._sprite.parent) {
this._sprite.parent.events.dispatchParents(type, event);
}
}
finally {
this._dispatchParents = old;
}
}
}
SpriteEventDispatcher.RENDERER_EVENTS = {
"click": function (event) {
if (this.isEnabled("click") && !this._sprite.isDragging() && this._sprite._hasDown() && !this._sprite._hasMoved(this._makePointerEvent("click", event))) {
this.dispatch("click", this._makePointerEvent("click", event));
}
},
"rightclick": function (event) {
if (this.isEnabled("rightclick")) {
this.dispatch("rightclick", this._makePointerEvent("rightclick", event));
}
},
"middleclick": function (event) {
if (this.isEnabled("middleclick")) {
this.dispatch("middleclick", this._makePointerEvent("middleclick", event));
}
},
"dblclick": function (event) {
this.dispatchParents("dblclick", this._makePointerEvent("dblclick", event));
},
"pointerover": function (event) {
const sprite = this._sprite;
let dispatch = true;
if (sprite.getPrivate("trustBounds")) {
sprite._getBounds();
const bounds = sprite.globalBounds();
if (sprite.isType("Graphics")) {
const strokeWidth = sprite.get("strokeWidth", 1) / 2;
if (strokeWidth >= 1) {
bounds.left -= strokeWidth;
bounds.right += strokeWidth;
bounds.top -= strokeWidth;
bounds.bottom += strokeWidth;
}
}
if (!$math.inBounds(event.point, bounds)) {
dispatch = false;
sprite._root._renderer.removeHovering(sprite._display);
}
}
if (dispatch && this.isEnabled("pointerover")) {
this.dispatch("pointerover", this._makePointerEvent("pointerover", event));
}
},
"pointerout": function (event) {
if (this.isEnabled("pointerout")) {
this.dispatch("pointerout", this._makePointerEvent("pointerout", event));
}
},
"pointerdown": function (event) {
this.dispatchParents("pointerdown", this._makePointerEvent("pointerdown", event));
},
"pointerup": function (event) {
if (this.isEnabled("pointerup")) {
this.dispatch("pointerup", this._makePointerEvent("pointerup", event));
}
},
"globalpointerup": function (event) {
if (this.isEnabled("globalpointerup")) {
this.dispatch("globalpointerup", this._makePointerEvent("globalpointerup", event));
}
},
"globalpointermove": function (event) {
if (this.isEnabled("globalpointermove")) {
this.dispatch("globalpointermove", this._makePointerEvent("globalpointermove", event));
}
},
"globalpointerdown": function (event) {
if (this.isEnabled("globalpointerdown")) {
this.dispatch("globalpointerdown", this._makePointerEvent("globalpointerdown", event));
}
},
"wheel": function (event) {
this.dispatchParents("wheel", {
type: "wheel",
target: this._sprite,
originalEvent: event.event,
point: event.point,
});
},
};
/**
* A base class for all visual elements.
*
* @important
*/
export class Sprite extends Entity {
constructor() {
super(...arguments);
this._adjustedLocalBounds = { left: 0, right: 0, top: 0, bottom: 0 };
this._localBounds = { left: 0, right: 0, top: 0, bottom: 0 };
this._sizeDirty = false;
// Will be true only when dragging
this._isDragging = false;
this._isHidden = false;
this._isShowing = false;
this._isHiding = false;
this._isDown = false;
this._downPoints = {};
}
_afterNew() {
this.setPrivateRaw("visible", true);
super._afterNew();
}
/**
* Marks some setting as dirty. Could be used to trigger adapter.
* @param key
*/
markDirtyKey(key) {
this._markDirtyKey(key);
}
_markDirtyKey(key) {
super._markDirtyKey(key);
if (key == "x" || key == "y" || key == "dx" || key == "dy") {
this.markDirtyBounds();
this._addPercentagePositionChildren();
this.markDirtyPosition();
}
}
_markDirtyPrivateKey(key) {
super._markDirtyPrivateKey(key);
if (key == "x" || key == "y") {
this.markDirtyPosition();
}
}
_removeTemplateField() {
if (this._templateField) {
this._templateField._removeObjectTemplate(this);
}
}
_createEvents() {
return new SpriteEventDispatcher(this);
}
_processTemplateField() {
let template;
const field = this.get("templateField");
if (field) {
const dataItem = this.dataItem;
if (dataItem) {
const context = dataItem.dataContext;
if (context) {
template = context[field];
if (!(template instanceof Template) && template) {
template = Template.new(template);
}
}
}
}
if (this._templateField !== template) {
this._removeTemplateField();
this._templateField = template;
if (template) {
template._setObjectTemplate(this);
}
this._applyTemplates();
}
}
// TODO change this to run before the element is added to the parent, so that way
// it doesn't need to apply the themes twice
_setDataItem(dataItem) {
const oldDataItem = this._dataItem;
this._dataItem = dataItem;
this._processTemplateField();
const eventType = "dataitemchanged";
if (dataItem != oldDataItem) {
if (this.events.isEnabled(eventType)) {
this.events.dispatch(eventType, {
type: eventType,
target: this,
oldDataItem: oldDataItem,
newDataItem: dataItem
});
}
}
}
/**
* A [[DataItem]] used for this element.
*
* NOTE: data item is being assigned automatically in most cases where it
* matters. Use this accessor to set data item only if you know what you're
* doing.
*
* @param value Data item
*/
set dataItem(value) {
this._setDataItem(value);
}
/**
* @return DataItem
*/
get dataItem() {
if (this._dataItem) {
return this._dataItem;
}
else {
let parent = this._parent;
while (parent) {
if (parent._dataItem) {
return parent._dataItem;
}
else {
parent = parent._parent;
}
}
}
}
_addPercentageSizeChildren() {
let parent = this.parent;
if (parent) {
if (this.get("width") instanceof Percent || this.get("height") instanceof Percent) {
$array.pushOne(parent._percentageSizeChildren, this);
}
else {
$array.removeFirst(parent._percentageSizeChildren, this);
}
}
}
_addPercentagePositionChildren() {
let parent = this.parent;
if (parent) {
if (this.get("x") instanceof Percent || this.get("y") instanceof Percent) {
$array.pushOne(parent._percentagePositionChildren, this);
}
else {
$array.removeFirst(parent._percentagePositionChildren, this);
}
}
}
/**
* @ignore
*/
markDirtyPosition() {
this._root._addDirtyPosition(this);
}
updatePivotPoint() {
const bounds = this._localBounds;
if (bounds) {
const centerX = this.get("centerX");
if (centerX != null) {
this._display.pivot.x = bounds.left + $utils.relativeToValue(centerX, bounds.right - bounds.left);
}
const centerY = this.get("centerY");
if (centerY != null) {
this._display.pivot.y = bounds.top + $utils.relativeToValue(centerY, bounds.bottom - bounds.top);
}
}
}
_beforeChanged() {
super._beforeChanged();
// handling states in beforeChanged, otherwise states is not applied without animated theme
this._handleStates();
if (this.isDirty("tooltip")) {
const previous = this._prevSettings.tooltip;
if (previous) {
previous.dispose();
}
}
if (this.isDirty("layer") || this.isDirty("layerMargin")) {
this._display.setLayer(this.get("layer"), this.get("layerMargin"));
this.markDirtyLayer();
}
if (this.isDirty("tooltipPosition")) {
const tooltipMoveDp = this._tooltipMoveDp;
if (tooltipMoveDp) {
tooltipMoveDp.dispose();
this._tooltipMoveDp = undefined;
}
const tooltipPointerDp = this._tooltipPointerDp;
if (tooltipPointerDp) {
tooltipPointerDp.dispose();
this._tooltipPointerDp = undefined;
}
if (this.get("tooltipPosition") == "pointer") {
if (this.isHover()) {
this._tooltipMoveDp = this.events.on("globalpointermove", (e) => {
this.showTooltip(e.point);
});
}
this._tooltipPointerDp = new MultiDisposer([
this.events.on("pointerover", () => {
const tooltipMoveDp = this._tooltipMoveDp;
if (tooltipMoveDp) {
tooltipMoveDp.dispose();
this._tooltipMoveDp = undefined;
}
this._tooltipMoveDp = this.events.on("globalpointermove", (e) => {
this.showTooltip(e.point);
});
}),
this.events.on("pointerout", () => {
const tooltipMoveDp = this._tooltipMoveDp;
if (tooltipMoveDp) {
tooltipMoveDp.dispose();
this._tooltipMoveDp = undefined;
}
})
]);
}
}
}
_handleStates() {
//if (!this._statesHandled) {
if (this.isDirty("active")) {
if (this.get("active")) {
this.states.applyAnimate("active");
this._setC("ariaChecked", true);
}
else {
if (!this.isHidden()) {
this.states.applyAnimate("default");
}
this._setC("ariaChecked", false);
}
this.markDirtyAccessibility();
}
if (this.isDirty("disabled")) {
if (this.get("disabled")) {
this.states.applyAnimate("disabled");
this._setC("ariaChecked", false);
}
else {
if (!this.isHidden()) {
this.states.applyAnimate("default");
}
this._setC("ariaChecked", true);
}
this.markDirtyAccessibility();
}
// this._statesHandled = true;
//}
}
_changed() {
super._changed();
const display = this._display;
const events = this.events;
if (this.isDirty("draggable")) {
const draggable = this.get("draggable");
if (draggable) {
this._setC("interactive", true);
this._dragDp = new MultiDisposer([
events.on("pointerdown", (ev) => {
this.dragStart(ev);
}),
events.on("globalpointermove", (ev) => {
this.dragMove(ev);
}),
events.on("globalpointerup", (ev) => {
this.dragStop(ev);
})
]);
}
else {
if (this._dragDp) {
this._dragDp.dispose();
this._dragDp = undefined;
}
}
display.cancelTouch = draggable ? true : false;
}
if (this.isDirty("tooltipText") || this.isDirty("tooltipHTML") || this.isDirty("showTooltipOn")) {
const tooltipText = this.get("tooltipText");
const tooltipHTML = this.get("tooltipHTML");
const showTooltipOn = this.get("showTooltipOn", "hover");
if (this._tooltipDp) {
this._tooltipDp.dispose();
this._tooltipDp = undefined;
}
if (tooltipText || tooltipHTML) {
if (showTooltipOn == "click") {
this._tooltipDp = new MultiDisposer([
events.on("click", () => {
this.setTimeout(() => {
const tooltip = this.getTooltip();
if (tooltip && !tooltip.isHidden() && tooltip.get("tooltipTarget") === this) {
this.hideTooltip();
}
else {
this.showTooltip();
}
}, 10);
}),
$utils.addEventListener(document, "click", (_ev) => {
this.hideTooltip();
})
]);
this._disposers.push(this._tooltipDp);
}
else if (showTooltipOn == "always") {
// nothing
}
else {
this._tooltipDp = new MultiDisposer([
events.on("pointerover", () => {
this.showTooltip();
}),
events.on("pointerout", () => {
this.hideTooltip();
})
]);
this._disposers.push(this._tooltipDp);
}
}
}
if (this.isDirty("toggleKey")) {
let toggleKey = this.get("toggleKey");
if (toggleKey && toggleKey != "none") {
this._toggleDp = events.on("click", () => {
if (!this._isDragging) {
this.set(toggleKey, !this.get(toggleKey));
}
});
}
else {
if (this._toggleDp) {
this._toggleDp.dispose();
this._toggleDp = undefined;
}
}
}
if (this.isDirty("opacity")) {
display.alpha = Math.max(0, this.get("opacity", 1));
if (this.get("focusable")) {
this.markDirtyAccessibility();
}
}
if (this.isDirty("rotation")) {
this.markDirtyBounds();
display.angle = this.get("rotation", 0);
}
if (this.isDirty("scale")) {
this.markDirtyBounds();
display.scale = this.get("scale", 0);
}
if (this.isDirty("centerX") || this.isDirty("centerY")) {
this.markDirtyBounds();
this.updatePivotPoint();
}
if (this.isDirty("visible") || this.isPrivateDirty("visible") || this.isDirty("forceHidden")) {
if (!this.get("visible") || !this.getPrivate("visible") || this.get("forceHidden")) {
display.visible = false;
this.hideTooltip();
}
else {
display.visible = true;
}
this.markDirtyBounds();
if (this.get("focusable")) {
this.markDirtyAccessibility();
}
}
if (this.isDirty("width") || this.isDirty("height")) {
this.markDirtyBounds();
this._addPercentageSizeChildren();
const parent = this.parent;
if (parent) {
if ((this.isDirty("width") && this.get("width") instanceof Percent) || (this.isDirty("height") && this.get("height") instanceof Percent)) {
parent.markDirty();
parent._prevWidth = 0;
}
}
this._sizeDirty = true;
}
if (this.isDirty("maxWidth") || this.isDirty("maxHeight") || this.isPrivateDirty("width") || this.isPrivateDirty("height") || this.isDirty("minWidth") || this.isDirty("minHeight") || this.isPrivateDirty("maxWidth") || this.isPrivateDirty("maxHeight") || this.isPrivateDirty("minWidth") || this.isPrivateDirty("minHeight") || this.isDirty("marginLeft") || this.isDirty("marginTop") || this.isDirty("marginRight") || this.isDirty("marginBottom")) {
this.markDirtyBounds();
this._sizeDirty = true;
}
if (this._sizeDirty) {
this._updateSize();
}
if (this.isDirty("wheelable")) {
const wheelable = this.get("wheelable");
if (wheelable) {
this._setC("interactive", true);
}
display.wheelable = wheelable ? true : false;
}
// Accessibility
if (this.isDirty("tabindexOrder") || this.isDirty("focusableGroup")) {
if (this.get("focusable")) {
this._root._registerTabindexOrder(this);
}
else {
this._root._unregisterTabindexOrder(this);
}
}
if (this.isDirty("filter")) {
//this.markDirtyBounds();
display.filter = this.get("filter");
}
let filter = this.get("filter", "");
if (this.isDirty("blur")) {
const blur = this.get("blur", 0);
if (blur != 0) {
filter += " blur(" + blur + "px)";
}
}
if (this.isDirty("saturate")) {
const saturate = this.get("saturate", 1);
if (saturate != 1) {
filter += " saturate(" + saturate + ")";
}
}
if (this.isDirty("brightness")) {
const brightness = this.get("brightness", 1);
if (brightness != 1) {
filter += " brightness(" + brightness + ")";
}
}
if (this.isDirty("contrast")) {
const contrast = this.get("contrast", 1);
if (contrast != 1) {
filter += " contrast(" + contrast + ")";
}
}
if (this.isDirty("sepia")) {
const sepia = this.get("sepia", 0);
if (sepia != 0) {
filter += " sepia(" + sepia + ")";
}
}
if (this.isDirty("hue")) {
const hue = this.get("hue", 0);
if (hue != 0) {
filter += " hue-rotate(" + hue + "deg)";
}
}
if (this.isDirty("invert")) {
const invert = this.get("invert", 0);
if (invert != 0) {
filter += " invert(" + invert + ")";
}
}
if (filter) {
display.filter = filter;
}
if (this.isDirty("cursorOverStyle")) {
display.cursorOverStyle = this.get("cursorOverStyle");
}
if (this.isDirty("hoverOnFocus")) {
if (this.get("hoverOnFocus")) {
this._focusDp = new MultiDisposer([
events.on("focus", () => {
// TODO: proper hover, not just tooltip
this.showTooltip();
}),
events.on("blur", () => {
// TODO: proper hover, not just tooltip
this.hideTooltip();
})
]);
}
else {
if (this._focusDp) {
this._focusDp.dispose();
this._focusDp = undefined;
}
}
}
if (this.isDirty("focusable")) {
if (this.get("focusable")) {
this._root._registerTabindexOrder(this);
}
else {
this._root._unregisterTabindexOrder(this);
}
this.markDirtyAccessibility();
this._disposers.push(events.on("blur", () => {
this.setPrivateRaw("touchHovering", false);
}));
this._disposers.push(events.once("boundschanged", () => {
// We might need to do this to re-evaluate accessibility attributes
// once the element gains size (bounds)
this.markDirtyAccessibility();
}));
}
if (this.isPrivateDirty("focusable")) {
this.markDirtyAccessibility();
}
if (this.isDirty("role") || this.isDirty("ariaLive") || this.isDirty("ariaChecked") || this.isDirty("ariaHidden") || this.isDirty("ariaOrientation") || this.isDirty("ariaValueNow") || this.isDirty("ariaValueMin") || this.isDirty("ariaValueMax") || this.isDirty("ariaValueText") || this.isDirty("ariaLabel") || this.isDirty("ariaControls")) {
// display.accessibility.ariaLabel = populateString(this, this.get("ariaLabel", ""));
// @todo make sure ariaLabel gets populated in Root
this.markDirtyAccessibility();
}
if (this.isDirty("exportable")) {
display.exportable = this.get("exportable");
}
if (this.isDirty("interactive")) {
const events = this.events;
if (this.get("interactive") && !events.isDisposed()) {
this._hoverDp = new MultiDisposer([
events.on("click", (ev) => {
if ($utils.isTouchEvent(ev.originalEvent)) {
if (!this.getPrivate("touchHovering")) {
this.setTimeout(() => {
this._handleOver();
if (this.get("tooltipText") || this.get("tooltipHTML")) {
this.showTooltip();
}
this.setPrivateRaw("touchHovering", true);
this.events.dispatch("pointerover", {
type: "pointerover",
target: ev.target,
originalEvent: ev.originalEvent,
point: ev.point,
simulated: ev.simulated
});
}, 10);
}
}
}),
events.on("globalpointerup", (ev) => {
if ($utils.isTouchEvent(ev.originalEvent)) {
// Only "out" if the tap landed away from this element. A tap
// on the same element keeps it hovered (isHover), so the tooltip
// stays put and simply repositions, instead of hiding.
if (this.getPrivate("touchHovering") && !this.isHover()) {
this._handleOut();
if (this.get("tooltipText") || this.get("tooltipHTML")) {
this.hideTooltip();
}
this.setPrivateRaw("touchHovering", false);
this.events.dispatch("pointerout", {
type: "pointerout",
target: ev.target,
originalEvent: ev.originalEvent,
point: ev.point,
simulated: ev.simulated
});
}
}
if (this._isDown) {
this._handleUp(ev);
}
//this._isDown = false;
}),
events.on("pointerover", () => {
this._handleOver();
}),
events.on("pointerout", () => {
this._handleOut();
}),
events.on("pointerdown", (e) => {
this._handleDown(e);
})
]);
}
else {
this._display.interactive = false;
if (this._hoverDp) {
this._hoverDp.dispose();
this._hoverDp = undefined;
}
}
}
if (this.isDirty("forceInactive")) {
this._display.inactive = this.get("forceInactive", null);
}
if (this.get("showTooltipOn") == "always" && this._display.visible) {
this.showTooltip();
}
}
/**
* @ignore
* @todo should this be user-accessible?
*/
dragStart(e) {
this._dragEvent = e;
this.events.stopParentDispatch();
}
/**
* @ignore
* @todo should this be user-accessible?
*/
dragStop(e) {
this._dragEvent = undefined;
this._dragPoint = undefined;
this.events.stopParentDispatch();
if (this._isDragging) {
this._isDragging = false;
const type = "dragstop";
if (this.events.isEnabled(type)) {
this.events.dispatch(type, {
type: type,
target: this,
originalEvent: e.originalEvent,
point: e.point,
simulated: e.simulated,
});
}
}
}
_handleOver() {
if (!this.isHidden()) {
if (this.get("active") && this.states.lookup("hoverActive")) {
this.states.applyAnimate("hoverActive");
}
else if (this.get("disabled") && this.states.lookup("hoverDisabled")) {
this.states.applyAnimate("hoverDisabled");
}
else {
this.states.applyAnimate("hover");
}
if (this.get("draggable") && this._isDown && this.states.lookup("down")) {
this.states.applyAnimate("down");
}
}
}
_handleOut() {
if (!this.isHidden()) {
if (this.get("active") && this.states.lookup("active")) {
this.states.applyAnimate("active");
}
else if (this.get("disabled") && this.states.lookup("disabled")) {
this.states.applyAnimate("disabled");
}
else {
if (this.states.lookup("hover") || this.states.lookup("hoverActive")) {
this.states.applyAnimate("default");
}
}
if (this.get("draggable") && this._isDown && this.states.lookup("down")) {
this.states.applyAnimate("down");
}
}
}
_handleUp(e) {
if (!this.isHidden()) {
if (this.get("active") && this.states.lookup("active")) {
this.states.applyAnimate("active");
}
else if (this.get("disabled") && this.states.lookup("disabled")) {
this.states.applyAnimate("disabled");
}
else if (this.states.lookup("down")) {
if (this.isHover()) {
this.states.applyAnimate("hover");
}
else {
this.states.applyAnimate("default");
}
}
// @todo remove this once migrated to _downPoints
this._downPoint = undefined;
const pointerId = $utils.getPointerId(e.originalEvent);
delete this._downPoints[pointerId];
if ($object.keys(this._downPoints).length == 0) {
this._isDown = false;
}
}
}
_hasMoved(e) {
// @todo remove this once migrated to _downPoints
// if (this._downPoint) {
// const x = Math.abs(this._downPoint.x - e.point.x);
// const y = Math.abs(this._downPoint.y - e.point.y);
// return (x > 5) || (y > 5);
// }
const pointerId = $utils.getPointerId(e.originalEvent);
const downPoint = this._downPoints[pointerId];
if (downPoint) {
const x = Math.abs(downPoint.x - e.point.x);
const y = Math.abs(downPoint.y - e.point.y);
return (x > 5) || (y > 5);
}
return false;
}
_hasDown() {
return $object.keys(this._downPoints).length > 0;
}
_handleDown(e) {
const parent = this.parent;
if (parent && !this.get("draggable")) {
parent._handleDown(e);
}
if (this.get("interactive") && !this.isHidden()) {
if (this.states.lookup("down")) {
this.states.applyAnimate("down");
}
this._downPoint = {
x: e.point.x,
y: e.point.y
};
// @todo remove this once migrated to _downPoints
this._isDown = true;
const pointerId = $utils.getPointerId(e.originalEvent);
this._downPoints[pointerId] = {
x: e.point.x,
y: e.point.y
};
}
}
/**
* @ignore
* @todo should this be user-accessible?
*/
dragMove(e) {
let dragEvent = this._dragEvent;
if (dragEvent) {
if (dragEvent.simulated && !e.simulated) {
return true;
}
let angle = 0;
let parent = this.parent;
let scale = 1;
while (parent != null) {
angle += parent.get("rotation", 0);
parent = parent.parent;
if (parent) {
scale *= parent.get("scale", 1);
}
}
let x = (e.point.x - dragEvent.point.x) / scale;
let y = (e.point.y - dragEvent.point.y) / scale;
const events = this.events;
if (dragEvent.simulated && !this._isDragging) {
this._isDragging = true;
this._dragEvent = e;
this._dragPoint = {
x: this.x(),
y: this.y()
};
const type = "dragstart";
if (events.isEnabled(type)) {
events.dispatch(type, {
type: type,
target: this,
originalEvent: e.originalEvent,
point: e.point,
simulated: e.simulated,
});
}
}
if (this._isDragging) {
let dragPoint = this._dragPoint;
this.set("x", dragPoint.x + x * $math.cos(angle) + y * $math.sin(angle));
this.set("y", dragPoint.y + y * $math.cos(angle) - x * $math.sin(angle));
const type = "dragged";
if (events.isEnabled(type)) {
events.dispatch(type, {
type: type,
target: this,
originalEvent: e.originalEvent,
point: e.point,
simulated: e.simulated,
});
}
}
else {
if (Math.hypot(x, y) > 5) {
this._isDragging = true;
this._dragEvent = e;
this._dragPoint = {
x: this.x(),
y: this.y()
};
const type = "dragstart";
if (events.isEnabled(type)) {
events.dispatch(type, {
type: type,
target: this,
originalEvent: e.originalEvent,
point: e.point,
simulated: e.simulated
});
}
}
}
}
}
_updateSize() {
}
_getBounds() {
this._localBounds = this._display.getLocalBounds();
}
/**
* Returns depth (how deep in the hierachy of the content tree) of this
* element.
*
* @return Depth
*/
depth() {
let self = this.parent;
let depth = 0;
while (true) {
if (self) {
++depth;
self = self.parent;
}
else {
return depth;
}
}
}
/**
* @ignore
*/
markDirtySize() {
this._sizeDirty = true;
this.markDirty();
}
/**
* @ignore
*/
markDirtyBounds() {
const display = this._display;
if (this.get("isMeasured")) {
this._root._addDirtyBounds(this);
display.isMeasured = true;
display.invalidateBounds();
const parent = this.parent;
if (parent && this.get("position") != "absolute") {
if (parent.get("width") == null || parent.get("height") == null || parent.get("layout")) {
parent.markDirtyBounds();
}
}
if (this.get("focusable")) {
this.markDirtyAccessibility();
}
}
}
/**
* @ignore
*/
markDirtyAccessibility() {
//if (this._root.focused(this)) {
this._root._invalidateAccessibility(this);
//}
}
/**
* @ignore
*/
markDirtyLayer() {
//this._display.markDirtyLayer(this.isDirty("opacity") || this.isDirty("visible")); https://codepen.io/team/amcharts/pen/gOWZPmP <- problems
this._display.markDirtyLayer(true);
}
/**
* @ignore
*/
markDirty() {
super.markDirty();
this.markDirtyLayer();
}
_updateBounds() {
const oldBounds = this._adjustedLocalBounds;
let newBounds;
// if display.visible == false, it still returns bounds
if (!this.get("visible") || !this.getPrivate("visible") || this.get("forceHidden")) {
newBounds = {
left: 0,
right: 0,
top: 0,
bottom: 0
};
this._localBounds = newBounds;
this._adjustedLocalBounds = newBounds;
}
else {
this._getBounds();
this._fixMinBounds(this._localBounds);
this.updatePivotPoint();
this._adjustedLocalBounds = this._display.getAdjustedBounds(this._localBounds);
newBounds = this._adjustedLocalBounds;
}
if (!oldBounds || (oldBounds.left !== newBounds.left || oldBounds.top !== newBounds.top || oldBounds.right !== newBounds.right || oldBounds.bottom !== newBounds.bottom)) {
const eventType = "boundschanged";
if (this.events.isEnabled(eventType)) {
this.events.dispatch(eventType, { type: eventType, target: this });
}
if (this.parent) {
this.parent.markDirty();
this.parent.markDirtyBounds();
}
// Update tooltip position together with the Sprite
if (this.getPrivate("showingTooltip")) {
this.showTooltip();
}
}
}
_fixMinBounds(bounds) {
let minWidth = this.get("minWidth", this.getPrivate("minWidth"));
let minHeight = this.get("minHeight", this.getPrivate("minHeight"));
if ($type.isNumber(minWidth)) {
if (bounds.right - bounds.left < minWidth) {
bounds.right = bounds.left + minWidth;
}
}
if ($type.isNumber(minHeight)) {
if (bounds.bottom - bounds.top < minHeight) {
bounds.bottom = bounds.top + minHeight;
}
}
let privateWidth = this.getPrivate("width");
let privateHeight = this.getPrivate("height");
if ($type.isNumber(privateWidth)) {
if (privateWidth > 0) {
bounds.right = bounds.left + privateWidth;
}
else {
bounds.left = bounds.right + privateWidth;
}
}
if ($type.isNumber(privateHeight)) {
if (privateHeight > 0) {
bounds.bottom = bounds.top + privateHeight;
}
else {
bounds.top = bounds.bottom + privateHeight;
}
}
}
_removeParent(parent) {
if (parent) {
parent.children.removeValue(this);
$array.removeFirst(parent._percentageSizeChildren, this);
$array.removeFirst(parent._percentagePositionChildren, this);
}
}
_clearDirty() {
super._clearDirty();
this._sizeDirty = false;
//this._statesHandled = false;
}
/**
* Simulate hover over element.
*/
hover() {
if (!this.isDisposed()) {
this.showTooltip();
this._handleOver();
}
}
/**
* Simulate unhover over element.
*/
unhover() {
if (!this.isDisposed()) {
this.hideTooltip();
this._handleOut();
}
}
/**
* Shows element's [[Tooltip]].
*/
showTooltip(point) {
if (!this.isDisposed()) {
const tooltip = this.getTooltip();
const tooltipText = this.get("tooltipText");
const tooltipHTML = this.get("tooltipHTML");
if ((tooltipText || tooltipHTML) && tooltip) {
const tooltipPosition = this.get("tooltipPosition");
const tooltipTarget = this.getPrivate("tooltipTarget", this);
// A point-less call on a "pointer"-positioned tooltip (e.g. from the
// boundschanged/positionchanged handlers keeping it glued to a moving
// target) must keep it at the last pointer location, not snap it to the
// element's anchor.
if (tooltipPosition == "pointer" && !point) {
point = this.getPrivate("lastTooltipCoords");
}
if (tooltipPosition == "fixed" || !point) {
this._display._setMatrix();
point = this.toGlobal(tooltipTarget._getTooltipPoint());
}
if (tooltipPosition == "pointer") {
const lastTooltipCoords = this.getPrivate("lastTooltipCoords");
if (lastTooltipCoords && lastTooltipCoords.x == point.x && lastTooltipCoords.y == point.y) {
return;
}
else {
this.setPrivate("lastTooltipCoords", point);
}
}
tooltip.set("pointTo", point);
tooltip.set("tooltipTarget", tooltipTarget);
if (!tooltip.get("x")) {
tooltip.set("x", point.x);
}
if (!tooltip.get("y")) {
tooltip.set("y", point.y);
}
if (tooltipText) {
tooltip.label.set("text", tooltipText);
}
if (tooltipHTML) {
tooltip.label.set("html", tooltipHTML);
}
const dataItem = this.dataItem;
if (dataItem) {
tooltip.label._setDataItem(dataItem);
}
if (this.get("showTooltipOn") == "always" && (point.x < 0 || point.x > this._root.width() || point.y < 0 || point.y > this._root.height())) {
this.hideTooltip();
return;
}
tooltip.label.text.markDirtyText();
const promise = tooltip.show();
this.setPrivateRaw("showingTooltip", true);
return promise;
}
}
}
/**
* Hides element's [[Tooltip]].
*/
hideTooltip() {
const tooltip = this.getTooltip();
if (tooltip) {
this.removePrivate("lastTooltipCoords");
if (tooltip.get("tooltipTarget") == this.getPrivate("tooltipTarget", this) || this.get("tooltip") == tooltip) {
let timeout = tooltip.get("keepTargetHover") && tooltip.get("stateAnimationDuration", 0) == 0 ? 400 : undefined;
const promise = tooltip.hide(timeout);
this.setPrivateRaw("showingTooltip", false);
return promise;
}
}
}
_getTooltipPoint() {
const bounds = this._localBounds;
if (bounds) {
let x = 0;
let y = 0;
if (!this.get("isMeasured")) {
x = $utils.relativeToValue(this.get("tooltipX", 0), this.width());
y = $utils.relativeToValue(this.get("tooltipY", 0), this.height());
}
else {
x = bounds.left + $utils.relativeToValue(this.get("tooltipX", 0), bounds.right - bounds.left);
y = bounds.top + $utils.relativeToValue(this.get("tooltipY", 0), bounds.bottom - bounds.top);
}
return { x, y };
}
return { x: 0, y: 0 };
}
/**
* Returns [[Tooltip]] used for this element.
*
* @return Tooltip
*/
getTooltip() {
let tooltip = this.get("tooltip");
if (!tooltip) {
let parent = this.parent;
if (parent) {
return parent.getTooltip();
}
}
else {
return tooltip;
}
}
_updatePosition() {
const parent = this.parent;
let dx = this.get("dx", 0);
let dy = this.get("dy", 0);
let x = this.get("x");
let _x = this.getPrivate("x");
let xx = 0;
let yy = 0;
const position = this.get("position");
if (x instanceof Percent) {
if (parent) {
x = parent.innerWidth() * x.value + parent.get("paddingLeft", 0);
}
else {
x = 0;
}
}
if ($type.isNumber(x)) {
xx = x + dx;
}
else {
if (_x != null) {
xx = _x;
}
else if (parent) {
if (position == "relative") {
xx = parent.get("paddingLeft", 0) + dx;
}
}
}
let y = this.get("y");
let _y = this.getPrivate("y");
if (y instanceof Percent) {
if (parent) {
y = parent.innerHeight() * y.value + parent.get("paddingTop", 0);
}
else {
y = 0;
}
}
if ($type.isNumber(y)) {
yy = y + dy;
}
else {
if (_y != null) {
yy = _y;
}
else if (parent) {
if (position == "relative") {
yy = parent.get("paddingTop", 0) + dy;
}
}
}
const display = this._display;
if (display.x != xx || display.y != yy) {
display.invalidateBounds();
display.x = xx;
display.y = yy;
const eventType = "positionchanged";
if (this.events.isEnabled(eventType)) {
this.events.dispatch(eventType, { type: eventType, target: this });
}
}
// Update tooltip position together with the Sprite
if (this.getPrivate("showingTooltip")) {
this.showTooltip();
}
}
/**
* Returns element's actual X position in pixels.
*
* @return X (px)
*/
x() {
let x = this.get("x");
let _x = this.getPrivate("x");
const parent = this.parent;
if (parent) {
if (x instanceof Percent) {
return $utils.relativeToValue(x, parent.innerWidth()) + parent.get("paddingLeft", 0);
}
else {
if (!$type.isNumber(x)) {
if (_x != null) {
return _x;
}
else {
return parent.get("paddingLeft", this._display.x);
}
}
else {
return x;
}
}
}
return this._display.x;
}
/**
* Returns element's actual Y position in pixels.
*
* @return Y (px)
*/
y() {
let _y = this.getPrivate("y");
if (_y != null) {
return _y;
}
let y = this.get("y");
const parent = this.parent;
if (parent) {
if (y instanceof Percent) {
return $utils.relativeToValue(y, parent.innerHeight()) + parent.get("paddingTop", 0);
}
else {
if (!$type.isNumber(y)) {
if (_y != null) {
return _y;
}
else {
return parent.get("paddingTop", this._display.y);
}
}
else {
return y;
}
}
}
return this._display.y;
}
_dispose() {
super._dispose();
this._display.dispose();
this._removeTemplateField();
this._removeParent(this.parent);
this._root._removeFocusElement(this);
const tooltip = this.get("tooltip");
if (tooltip) {
tooltip.dispose();
}
this.markDirty();
}
/**
* @ignore
*/
adjustedLocalBounds() {
this._fixMinBounds(this._adjustedLocalBounds);
return this._adjustedLocalBounds;
}
/**
* Returns local coordinates of the element's bounds.
*
* @ignore
* @return Global bounds
*/
localBounds() {
return this._localBounds;
}
/**
* Returns adjusted local coordinates of the element's bounds.
*
* @ignore
* @return Global bounds
*/
bounds() {
const bounds = this._adjustedLocalBounds;
const x = this.x();
const y = this.y();
return { left: bounds.left + x, right: bounds.right + x, top: bounds.top + y, bottom: bounds.bottom + y };
}
/**
* Returns global coordinates of the element's bounds.
*
* @ignore
* @return Global bounds
*/
globalBounds() {
const bounds = this.localBounds();
const p0 = this.toGlobal({ x: bounds.left, y: bounds.top });
const p1 = this.toGlobal({ x: bounds.right, y: bounds.top });
const p2 = this.toGlobal({ x: bounds.right, y: bounds.bottom });
const p3 = this.toGlobal({ x: bounds.left, y: bounds.bottom });
return {
left: Math.min(p0.x, p1.x, p2.x, p3.x),
top: Math.min(p0.y, p1.y, p2.y, p3.y),
right: Math.max(p0.x, p1.x, p2.x, p3.x),
bottom: Math.max(p0.y, p1.y, p2.y, p3.y)
};
}
_onShow(_duration) {
}
_onHide(_duration) {
}
/**
* Plays initial reveal animation regardless if element is currently hidden
* or visible.
*
* @param duration Duration of the animation in milliseconds
* @param delay Delay showing of the element by X milliseconds
* @return Promise
*/
appear(duration, delay) {
return __awaiter(this, void 0, void 0, function* () {
yield this.hide(0);
if (delay) {
return new Promise((success, _error) => {
this.setTimeout(() => {
success(this.show(duration));
}, delay);
});
}
else {
return this.show(duration);
}
});
}
/**
* Shows currently hidden element and returns a `Promise` which completes
* when all showing animations are finished.
*
* ```TypeScript
* series.show().then(function(ev) {
* console.log("Series is now fully visible");
* })
* ```
* ```JavaScript
* series.show().then(function(ev) {
* console.log("Series is now fully visible");
* })
* ```
*
* @return Promise
*/
show(duration) {
return __awaiter(this, void 0, void 0, function* () {
if (!this._isShowing) {
this._isHidden = false;
this._isShowing = true;
this._isHiding = false;
if (this.states.lookup("default").get("visible")) {
this._setC("visible", true);
}
this._onShow(duration);
const animations = this.states.applyAnimate("default", duration);
yield waitForAnimations(animations);
this._isShowing = false;
}
});
}
/**
* Hides the element and returns a `Promise` which completes when all hiding
* animations are finished.
*
* ```TypeScript
* series.hide().then(function(ev) {
* console.log("Series finished hiding");
* })
* ```
* ```JavaScript
* series.hide().then(function(ev) {
* console.log("Series finished hiding");
* })
* ```
*
* @return Promise
*/
hide(duration) {
return __awaiter(this, void 0, void 0, function* () {
if (!this._isHiding && !this._isHidden) {
this._isHiding = true;
this._isShowing = false;
let state = this.states.lookup("hidden");
if (!state) {
state = this.states._createC("hidden", {
"opacity": 0,
"visible": false
});
}
this._isHidden = true;
this._onHide(duration);
const animations = this.states.applyAnimate("hidden", duration);
yield waitForAnimations(animations);
this._isHiding = false;
}
});
}
/**
* Returns `true` if this element is currently hidden.
*
* @return Is hidden?
*/
isHidden() {
return this._isHidden;
}
/**
* Returns `true` if this element is currently animating to a default state.
*
* @return Is showing?
*/
isShowing() {
return this._isShowing;
}
/**
* Returns `true` if this element is currently animating to a hidden state.
*
* @return Is hiding?
*/
isHiding() {
return this._isHiding;
}
/**
* Returns `true` if this element is currently hovered by a pointer.
*
* @return Is hovered?
*/
isHover() {
return this._display.hovering();
}
/**
* Returns `true` if this element does currently have focus.
*
* @return Is focused?
*/
isFocus() {
return this._root.focused(this);
}
/**
* Returns `true` if this element is currently being dragged.
*
* @return Is dragged?
*/
isDragging() {
return this._isDragging;
}
/**
* Returns `false` if if either public or private setting `visible` is set
* to `false`, or `forceHidden` is set to `true`.
*
* @return Visible?
*/
isVisible() {
if (this.get("visible") && this.getPrivate("visible") && !this.get("forceHidden")) {
return true;
}
return false;
}
/**
* Same as `isVisible()`, except it checks all ascendants, too.
*
* @since 5.2.7
* @return Visible?
*/
isVisibleDeep() {
return this._parent ? (this._parent.isVisibleDeep() && this.isVisible()) : this.isVisible();
}
/**
* Returns an actual opacity of the element, taking into account all parents.
*
* @return Opacity
* @since 5.2.11
*/
compositeOpacity() {
const opacity = this.get("opacity", 1);
return this._parent ? (this._parent.compositeOpacity() * opacity) : opacity;
}
/**
* Returns an actual scale of the element, taking into account all parents.
*
* @return Opacity
* @since 5.9.2
*/
compositeScale() {
const scale = this.get("scale", 1);
return this._parent ? (this._parent.compositeScale() * scale) : scale;
}
/**
* Returns an actual roation of the element, taking into account all parents.
*
* @return Opacity
* @since 5.9.2
*/
compositeRotation() {
const rotation = this.get("rotation", 0);
return this._parent ? (this._parent.compositeRotation() + rotation) : rotation;
}
/**
* Returns width of this element in pixels.
*
* @return Width (px)
*/
width() {
let width = this.get("width");
let maxWidth = this.get("maxWidth", this.getPrivate("maxWidth"));
let minWidth = this.get("minWidth", this.getPrivate("minWidth"));
let privateWidth = this.getPrivate("width");
let w = 0;
if ($type.isNumber(privateWidth)) {
w = privateWidth;
}
else {
if (width == null) {
if (this._adjustedLocalBounds) {
w = this._adjustedLocalBounds.right - this._adjustedLocalBounds.left;
}
}
else {
if (width instanceof Percent) {
const parent = this.parent;
if (parent) {
w = parent.innerWidth() * width.value;
}
else {
w = this._root.width() * width.value;
}
}
else if ($type.isNumber(width)) {
w = width;
}
}
}
if ($type.isNumber(minWidth)) {
w = Math.max(minWidth, w);
}
if ($type.isNumber(maxWidth)) {
w = Math.min(maxWidth, w);
}
return w;
}
/**
* Returns maximum allowed width of this element in pixels.
*
* @return Maximum width (px)
*/
maxWidth() {
let maxWidth = this.get("maxWidth", this.getPrivate("maxWidth"));
if ($type.isNumber(maxWidth)) {
return maxWidth;
}
else {
let width = this.get("width");
if ($type.isNumber(width)) {
return width;
}
}
const parent = this.parent;
if (parent) {
return parent.innerWidth();
}
return this._root.width();
}
/**
* Returns maximum allowed height of this element in pixels.
*
* @return Maximum height (px)
*/
maxHeight() {
let maxHeight = this.get("maxHeight", this.getPrivate("maxHeight"));
if ($type.isNumber(maxHeight)) {
return maxHeight;
}
else {
let height = this.get("height");
if ($type.isNumber(height)) {
return height;
}
}
const parent = this.parent;
if (parent) {
return parent.innerHeight();
}
return this._root.height();
}
/**
* Returns height of this element in pixels.
*
* @return Height (px)
*/
height() {
let height = this.get("height");
let maxHeight = this.get("maxHeight", this.getPrivate("maxHeight"));
let minHeight = this.get("minHeight", this.getPrivate("minHeight"));
let privateHeight = this.getPrivate("height");
let h = 0;
if ($type.isNumber(privateHeight)) {
h = privateHeight;
}
else {
if (height == null) {
if (this._adjustedLocalBounds) {
h = this._adjustedLocalBounds.bottom - this._adjustedLocalBounds.top;
}
}
else {
if (height instanceof Percent) {
const parent = this.parent;
if (parent) {
h = parent.innerHeight() * height.value;
}
else {
h = this._root.height() * height.value;
}
}
else if ($type.isNumber(height)) {
h = height;
}
}
}
if ($type.isNumber(minHeight)) {
h = Math.max(minHeight, h);
}
if ($type.isNumber(maxHeight)) {
h = Math.min(maxHeight, h);
}
return h;
}
_findStaticTemplate(f) {
// templateField overrides template
if (this._templateField && f(this._templateField)) {
return this._templateField;
}
return super._findStaticTemplate(f);
}
_walkParents(f) {
if (this._parent || this._virtualParent) {
this._walkParent(f);
}
}
_walkParent(f) {
if (this._parent) {
this._parent._walkParent(f);
}
else {
if (this._virtualParent) {
this._virtualParent._walkParent(f);
}
}
f(this);
}
set virtualParent(parent) {
if (this._virtualParent !== parent) {
this._virtualParent = parent;
this._applyThemes();
}
}
/**
* Parent [[Container]] of this element.
*
* @return Parent container
*/
get parent() {
return this._parent;
}
_setParent(parent, updateChildren = false) {
const prevParent = this._parent;
if (parent !== prevParent) {
this.markDirtyBounds();
parent.markDirty();
this._parent = parent;
if (updateChildren) {
this._removeParent(prevParent);
if (parent) {
this._addPercentageSizeChildren();
this._addPercentagePositionChildren();
}
}
this.markDirtyPosition();
this._applyThemes();
}
}
/**
* Returns an instance of [[NumberFormatter]] used in this element.
*
* If this element does not have it set, global one form [[Root]] is used.
*
* @see {@link https://www.amcharts.com/docs/v5/concepts/using-formatters/} for more info
* @return NumberFormatter instace
*/
getNumberFormatter() {
return this.get("numberFormatter", this._root.numberFormatter);
}
/**
* Returns an instance of [[DateFormatter]] used in this element.
*
* If this element does not have it set, global one form [[Root]] is used.
*
* @see {@link https://www.amcharts.com/docs/v5/concepts/using-formatters/} for more info
* @return DateFormatter instace
*/
getDateFormatter() {
return this.get("dateFormatter", this._root.dateFormatter);
}
/**
* Returns an instance of [[DurationFormatter]] used in this element.
*
* If this element does not have it set, global one form [[Root]] is used.
*
* @see {@link https://www.amcharts.com/docs/v5/concepts/using-formatters/} for more info
* @return DurationFormatter instace
*/
getDurationFormatter() {
return this.get("durationFormatter", this._root.durationFormatter);
}
/**
* Converts X/Y coordinate within this element to a global coordinate.
*
* @param point Local coordinate
* @return Global coordinate
*/
toGlobal(point) {
return this._display.toGlobal(point);
}
/**
* Converts global X/Y coordinate to a coordinate within this element.
*
* @param point Global coordinate
* @return Local coordinate
*/
toLocal(point) {
return this._display.toLocal(point);
}
_getDownPoint() {
const id = this._getDownPointId();
if (id) {
return this._downPoints[id];
}
}
_getDownPointId() {
if (this._downPoints) {
return $object.keysOrdered(this._downPoints, (a, b) => {
if (a > b) {
return 1;
}
if (a < b) {
return -1;
}
return 0;
})[0];
}
}
/**
* Moves sprite to the end of the parent's children array.
*
* Depending on `layout` setting of the parten container, it may effect the
* positioning or overlapping order of the elements.
*/
toFront() {
const parent = this.parent;
if (parent) {
parent.children.moveValue(this, parent.children.length - 1);
}
}
/**
* Moves sprite to the beginning of the parent's children array.
*
* Depending on `layout` setting of the parten container, it may effect the
* positioning or overlapping order of the elements.
*/
toBack() {
const parent = this.parent;
if (parent) {
parent.children.moveValue(this, 0);
}
}
}
Sprite.className = "Sprite";
Sprite.classNames = Entity.classNames.concat([Sprite.className]);
//# sourceMappingURL=Sprite.js.map