@amcharts/amcharts5
Version:
amCharts 5
2,002 lines • 78.1 kB
JavaScript
import { AnimationState } from "./util/Animation";
import { Container } from "./render/Container";
import { Text } from "./render/Text";
import { HorizontalLayout } from "./render/HorizontalLayout";
import { VerticalLayout } from "./render/VerticalLayout";
import { GridLayout } from "./render/GridLayout";
import { Disposer } from "./util/Disposer";
import { ResizeSensor } from "./util/ResizeSensor";
import { InterfaceColors } from "./util/InterfaceColors";
import { Graphics } from "./render/Graphics";
import { Rectangle } from "./render/Rectangle";
import { Tooltip } from "./render/Tooltip";
import { NumberFormatter } from "./util/NumberFormatter";
import { DateFormatter } from "./util/DateFormatter";
import { DurationFormatter } from "./util/DurationFormatter";
import { Language } from "./util/Language";
import { EventDispatcher } from "./util/EventDispatcher";
import { DefaultTheme } from "../themes/DefaultTheme";
import { CanvasRenderer } from "./render/backend/CanvasRenderer";
import { p100, percent, isPercent, Percent } from "./util/Percent";
import { color } from "./util/Color";
import { populateString } from "./util/PopulateString";
import { registry } from "./Registry";
import * as $order from "./util/Order";
import * as $array from "./util/Array";
import * as $object from "./util/Object";
import * as $utils from "./util/Utils";
import * as $type from "./util/Type";
import * as $ease from "./util/Ease";
import en from "../../locales/en";
function rAF(fps, callback) {
if (fps == null) {
requestAnimationFrame(callback);
}
else {
setTimeout(() => {
requestAnimationFrame(callback);
}, 1000 / fps);
}
}
// TODO implement Disposer
/**
* Root element of the chart.
*
* @see {@link https://www.amcharts.com/docs/v5/getting-started/#Root_element} for more info
*/
export class Root {
constructor(id, settings = {}, isReal) {
/**
* Settings the Root filled in for itself, so that serializing can leave them
* out - a `Root` is not an `Entity`, so it has no `_computedProperties`.
*
* @ignore
*/
this._computedSettings = {};
this._isDirty = false;
this._isDirtyParents = false;
this._isDirtyAnimation = false;
this._dirty = {};
this._dirtyParents = {};
this._dirtyBounds = {};
this._dirtyPositions = {};
this._ticker = null;
this._tickers = [];
this._updateTick = true;
this.skipRenderFrame = false;
/**
* Root's event dispatcher.
*
* @see {@link https://www.amcharts.com/docs/v5/concepts/events/} for more info
*/
this.events = new EventDispatcher();
/**
* @ignore
* @todo needs description
*/
this.animationTime = null;
this._animations = [];
// Locale-related
/**
* @ignore
*/
this.language = Language.new(this, {});
/**
* Locale used by the chart.
*
* @see {@link https://www.amcharts.com/docs/v5/concepts/locales/}
*/
this.locale = en;
// Date-time related
/**
* Use UTC when formatting date/time.
*
* @see {@link https://www.amcharts.com/docs/v5/concepts/formatters/formatting-dates/#utc-and-time-zones} for more info
*/
this.utc = false;
/**
* Number formatter.
*
* @see {@link https://www.amcharts.com/docs/v5/concepts/formatters/formatting-numbers/} for more info
*/
this.numberFormatter = NumberFormatter.new(this, {});
/**
* Date/time formatter.
*
* @see {@link https://www.amcharts.com/docs/v5/concepts/formatters/formatting-dates/} for more info
*/
this.dateFormatter = DateFormatter.new(this, {});
/**
* Duration formatter.
*
* @see {@link https://www.amcharts.com/docs/v5/concepts/formatters/formatting-dates/} for more info
*/
this.durationFormatter = DurationFormatter.new(this, {});
// Accessibility
/**
* Global tab index for using for the whole chart
*
* @see {@link https://www.amcharts.com/docs/v5/concepts/accessibility/} for more info
*/
this.tabindex = 0;
//@todo maybe make this better
this._tabindexes = [];
this._a11yD = false;
this._focusElementDirty = false;
this._skipNextGroupJump = true;
/**
* An instance of vertical layout object that can be used to set `layout` setting
* of a [[Container]].
*
* @default VerticalLayout.new()
*/
this.verticalLayout = VerticalLayout.new(this, {});
/**
* An instance of horizontal layout object that can be used to set `layout` setting
* of a [[Container]].
*
* @default HorizontalLayout.new()
*/
this.horizontalLayout = HorizontalLayout.new(this, {});
/**
* An instance of grid layout object that can be used to set `layout` setting
* of a [[Container]].
*
* @default VerticalLayout.new()
*/
this.gridLayout = GridLayout.new(this, {});
this._paused = false;
/**
* Indicates whether chart should resized automatically when parent container
* width and/or height changes.
*
* If disabled (`autoResize = false`) you can make the chart resize manually
* by calling root element's `resize()` method.
*/
this.autoResize = true;
this._fontHash = [];
this._isDisposed = false;
this._disposers = [];
this._tooltips = [];
this._htmlEnabledContainers = [];
/**
* Entities that have their `id` setting set.
*
* @since 5.11.0
*/
this.entitiesById = {};
if (!isReal) {
throw new Error("You cannot use `new Class()`, instead use `Class.new()`");
}
this._settings = settings;
if (settings.accessible == false) {
this._a11yD = true;
}
if (settings.useSafeResolution == null) {
settings.useSafeResolution = true;
this._computedSettings.useSafeResolution = true;
}
if (settings.sanitizeHTML == null) {
settings.sanitizeHTML = true;
this._computedSettings.sanitizeHTML = true;
}
let resolution;
if (settings.useSafeResolution) {
resolution = $utils.getSafeResolution();
}
this._renderer = new CanvasRenderer(resolution);
let dom;
if (id instanceof HTMLElement) {
dom = id;
}
else {
dom = document.getElementById(id);
}
$array.each(registry.rootElements, (root) => {
if (root.dom === dom) {
if (registry.autoDispose) {
root.dispose();
}
else {
throw new Error("You cannot have multiple Roots on the same DOM node");
}
}
});
this.interfaceColors = InterfaceColors.new(this, {});
if (dom === null) {
throw new Error("Could not find HTML element with id `" + id + "`");
}
this.dom = dom;
let inner = document.createElement("div");
inner.style.position = "relative";
inner.style.width = "100%";
inner.style.height = "100%";
dom.appendChild(inner);
const tooltipContainerBounds = settings.tooltipContainerBounds;
if (tooltipContainerBounds) {
this._tooltipContainerSettings = tooltipContainerBounds;
}
this._inner = inner;
this._updateComputedStyles();
registry.rootElements.push(this);
}
static new(id, settings) {
const root = new Root(id, settings, true);
root._init();
return root;
}
moveDOM(id) {
let dom;
if (id instanceof HTMLElement) {
dom = id;
}
else {
dom = document.getElementById(id);
}
if (dom) {
while (this.dom.childNodes.length > 0) {
dom.appendChild(this.dom.childNodes[0]);
}
this.dom = dom;
this._initResizeSensor();
this.resize();
}
}
_handleLogo() {
if (this._logo) {
const w = this.dom.offsetWidth;
const h = this.dom.offsetHeight;
if ((w <= 150) || (h <= 60)) {
this._logo.hide();
}
else {
this._logo.show();
}
}
}
_showBranding() {
if (!this._logo) {
const logo = this.tooltipContainer.children.push(Container.new(this, {
interactive: true,
interactiveChildren: false,
position: "absolute",
setStateOnChildren: true,
paddingTop: 9,
paddingRight: 9,
paddingBottom: 9,
paddingLeft: 9,
scale: .6,
y: percent(100),
centerY: p100,
tooltipY: 10,
tooltipText: "Created using amCharts 5",
tooltipX: p100,
cursorOverStyle: "pointer",
background: Rectangle.new(this, {
fill: color(0x474758),
fillOpacity: 0,
tooltipY: 5
})
}));
logo.set("tooltip", this.systemTooltip);
logo.events.on("click", () => {
window.open("https://www.amcharts.com/", "_blank");
});
logo.states.create("hover", {});
const m = logo.children.push(Graphics.new(this, {
stroke: color(0xcccccc),
strokeWidth: 3,
svgPath: "M5 25 L13 25h13.6c3.4 0 6 0 10.3-4.3s5.2-12 8.6-12c3.4 0 4.3 8.6 7.7 8.6M83.4 25H79.8c-3.4 0-6 0-10.3-4.3s-5.2-12-8.6-12-4.3 8.6-7.7 8.6",
dx: -50,
strokeOpacity: 0
}));
m.states._createC("hover", { stroke: color(0x3CABFF) });
const a = logo.children.push(Graphics.new(this, {
stroke: color(0x888888),
strokeWidth: 3,
svgPath: "M83.4 25h-31C37 25 39.5 4.4 28.4 4.4S18.9 24.2 4.3 25H0",
dx: 50,
strokeOpacity: 0
}));
const easing = $ease.out($ease.cubic);
const duration = 1500;
logo.setTimeout(() => {
a.animate({
key: "strokeOpacity",
to: 1,
duration: duration,
easing: easing
});
a.animate({
key: "dx",
to: 0,
duration: duration,
easing: easing
});
m.animate({
key: "dx",
to: 0,
duration: duration,
easing: easing
});
m.animate({
key: "strokeOpacity",
to: 1,
duration: duration,
easing: easing
});
}, 3000);
a.states.create("hover", { stroke: color(0x474758) });
this._logo = logo;
this._handleLogo();
}
}
_getRealSize() {
return this.dom.getBoundingClientRect();
}
_getCalculatedSize(rect) {
if (this._settings.calculateSize) {
return this._settings.calculateSize(rect);
}
else {
return {
width: rect.width,
height: rect.height,
};
}
}
_init() {
const settings = this._settings;
if (settings.accessible !== false) {
if (settings.focusable) {
this._inner.setAttribute("focusable", "true");
this._inner.setAttribute("tabindex", this.tabindex + "");
}
if (settings.ariaLabel) {
this._inner.setAttribute("aria-label", settings.ariaLabel);
}
if (settings.role) {
this._inner.setAttribute("role", settings.role);
}
}
const renderer = this._renderer;
const rect = this._getRealSize();
const size = this._getCalculatedSize(rect);
const width = Math.floor(size.width);
const height = Math.floor(size.height);
const realWidth = Math.floor(rect.width);
const realHeight = Math.floor(rect.height);
const rootContainer = Container.new(this, {
visible: true,
width: width,
height: height,
});
this._rootContainer = rootContainer;
this._rootContainer._defaultThemes.push(DefaultTheme.new(this));
// the size is needed to rebuild this from a config, `visible` is the default
const container = rootContainer.children.push(Container.new(this, { width: p100, height: p100 }));
container._setC("visible", true);
this.container = container;
renderer.resize(realWidth, realHeight, width, height);
//@todo: better appendChild - refer
this._inner.appendChild(renderer.view);
// TODO: TMP TMP TMP for testing only, remove
//renderer.debugGhostView = true;
this._initResizeSensor();
// HTML content holder
const htmlElementContainer = document.createElement("div");
this._htmlElementContainer = htmlElementContainer;
htmlElementContainer.className = "am5-html-container";
htmlElementContainer.style.position = "absolute";
htmlElementContainer.style.pointerEvents = "none";
if (!this._tooltipContainerSettings) {
htmlElementContainer.style.overflow = "hidden";
}
this._inner.appendChild(htmlElementContainer);
if (this._a11yD !== true) {
// Create element which is used to make announcements to screen reader
const readerAlertElement = document.createElement("div");
readerAlertElement.className = "am5-reader-container";
readerAlertElement.setAttribute("role", "alert");
// readerAlertElement.style.zIndex = "-100000";
// readerAlertElement.style.opacity = "0";
// readerAlertElement.style.top = "0";
readerAlertElement.style.position = "absolute";
readerAlertElement.style.width = "1px";
readerAlertElement.style.height = "1px";
readerAlertElement.style.overflow = "hidden";
readerAlertElement.style.clip = "rect(1px, 1px, 1px, 1px)";
this._readerAlertElement = readerAlertElement;
this._inner.appendChild(this._readerAlertElement);
const focusElementContainer = document.createElement("div");
focusElementContainer.className = "am5-focus-container";
focusElementContainer.style.position = "absolute";
focusElementContainer.style.pointerEvents = "none";
focusElementContainer.style.top = "0px";
focusElementContainer.style.left = "0px";
focusElementContainer.style.overflow = "hidden";
focusElementContainer.style.width = width + "px";
focusElementContainer.style.height = height + "px";
focusElementContainer.setAttribute("role", "graphics-document");
if (settings.ariaLabel) {
focusElementContainer.setAttribute("aria-label", settings.ariaLabel);
}
$utils.setInteractive(focusElementContainer, false);
this._focusElementContainer = focusElementContainer;
this._inner.appendChild(this._focusElementContainer);
const tooltipElementContainer = document.createElement("div");
this._tooltipElementContainer = tooltipElementContainer;
tooltipElementContainer.className = "am5-tooltip-container";
this._inner.appendChild(tooltipElementContainer);
// Add keyboard events for accessibility, e.g. simulating drag with arrow
// keys and click with ENTER
if ($utils.supports("keyboardevents")) {
this._disposers.push($utils.addEventListener(window, "keydown", (ev) => {
const eventKey = $utils.getEventKey(ev);
if (eventKey == "Shift") {
this._isShift = true;
}
else if (eventKey == "Tab") {
this._isShift = ev.shiftKey;
}
}));
this._disposers.push($utils.addEventListener(window, "keyup", (ev) => {
const eventKey = $utils.getEventKey(ev);
if (eventKey == "Shift") {
this._isShift = false;
}
}));
this._disposers.push($utils.addEventListener(focusElementContainer, "click", () => {
// Some screen readers convert ENTER (and some SPACE) press whil on
// focused element to a "click" event, preventing actual "keydown"
// event from firing. We're using this "click" event to still
// generate internal click events.
const focusedSprite = this._focusedSprite;
if (focusedSprite) {
const announceText = focusedSprite.get("clickAnnounceText", "");
if (announceText !== "") {
this.readerAlert(announceText);
}
const downEvent = renderer.getEvent(new MouseEvent("click"));
focusedSprite.events.dispatch("click", {
type: "click",
originalEvent: downEvent.event,
point: downEvent.point,
simulated: true,
target: focusedSprite
});
}
}));
this._disposers.push($utils.addEventListener(focusElementContainer, "keydown", (ev) => {
const focusedSprite = this._focusedSprite;
if (focusedSprite) {
if (ev.key == "Escape") {
// ESC pressed - lose current focus
$utils.blur();
this._focusedSprite = undefined;
}
let dragOffsetX = 0;
let dragOffsetY = 0;
// TODO: figure out if using bogus MouseEvent is fine, or it will
// fail on some platforms
const eventKey = $utils.getEventKey(ev);
switch (eventKey) {
case "Enter":
case " ":
const announceText = focusedSprite.get("clickAnnounceText", "");
if (announceText !== "") {
this.readerAlert(announceText);
}
if (eventKey == " " && focusedSprite.get("role") != "checkbox") {
return;
}
ev.preventDefault();
const downEvent = renderer.getEvent(new MouseEvent("mouse"));
focusedSprite.events.dispatch("click", {
type: "click",
originalEvent: downEvent.event,
point: downEvent.point,
simulated: true,
target: focusedSprite
});
return;
case "ArrowLeft":
dragOffsetX = -6;
break;
case "ArrowRight":
dragOffsetX = 6;
break;
case "ArrowUp":
dragOffsetY = -6;
break;
case "ArrowDown":
dragOffsetY = 6;
break;
default:
return;
}
if (dragOffsetX != 0 || dragOffsetY != 0) {
ev.preventDefault();
if (!focusedSprite.isDragging()) {
// Start dragging
this._keyboardDragPoint = {
x: 0,
y: 0
};
const downEvent = renderer.getEvent(new MouseEvent("mousedown", {
clientX: 0,
clientY: 0
}));
downEvent.point = {
x: 0,
y: 0
};
if (focusedSprite.events.isEnabled("pointerdown")) {
focusedSprite.events.dispatch("pointerdown", {
type: "pointerdown",
originalEvent: downEvent.event,
point: downEvent.point,
simulated: true,
target: focusedSprite
});
}
}
else {
// Move focus marker
//this._positionFocusElement(focusedSprite);
}
// Move incrementally
const dragPoint = this._keyboardDragPoint;
dragPoint.x += dragOffsetX;
dragPoint.y += dragOffsetY;
const moveEvent = renderer.getEvent(new MouseEvent("mousemove", {
clientX: dragPoint.x,
clientY: dragPoint.y
}), false);
if (focusedSprite.events.isEnabled("globalpointermove")) {
focusedSprite.events.dispatch("globalpointermove", {
type: "globalpointermove",
originalEvent: moveEvent.event,
point: moveEvent.point,
simulated: true,
target: focusedSprite
});
}
}
}
}));
this._disposers.push($utils.addEventListener(focusElementContainer, "keyup", (ev) => {
if (this._focusedSprite) {
const focusedSprite = this._focusedSprite;
const eventKey = $utils.getEventKey(ev);
switch (eventKey) {
case "ArrowLeft":
case "ArrowRight":
case "ArrowUp":
case "ArrowDown":
if (focusedSprite.isDragging()) {
// Simulate drag stop
const dragPoint = this._keyboardDragPoint;
const upEvent = renderer.getEvent(new MouseEvent("mouseup", {
clientX: dragPoint.x,
clientY: dragPoint.y
}));
if (focusedSprite.events.isEnabled("globalpointerup")) {
focusedSprite.events.dispatch("globalpointerup", {
type: "globalpointerup",
originalEvent: upEvent.event,
point: upEvent.point,
simulated: true,
target: focusedSprite
});
}
//this._positionFocusElement(focusedSprite);
this._keyboardDragPoint = undefined;
// @todo dispatch mouseup event instead of calling dragStop?
// this._dispatchEvent("globalpointerup", target, upEvent);
return;
}
else if (focusedSprite.get("focusableGroup")) {
// Find next item in focusable group
const group = focusedSprite.get("focusableGroup");
const items = this._tabindexes.filter((item) => {
return item.get("focusableGroup") == group && item.getPrivate("focusable") !== false && item.isVisibleDeep() ? true : false;
});
let index = items.indexOf(focusedSprite);
const lastIndex = items.length - 1;
index += (eventKey == "ArrowRight" || eventKey == "ArrowDown") ? 1 : -1;
if (index < 0) {
index = lastIndex;
}
else if (index > lastIndex) {
index = 0;
}
$utils.focus(items[index].getPrivate("focusElement").dom);
}
break;
case "Tab":
const group = focusedSprite.get("focusableGroup");
if (group && this._isShift) {
if (!this._skipNextGroupJump) {
this._focusNext(focusedSprite.getPrivate("focusElement").dom, -1, group);
this._skipNextGroupJump = true;
}
return;
}
break;
}
}
}));
}
}
this._startTicker();
this.setThemes([]);
this._addTooltip();
if (!this._hasLicense()) {
this._showBranding();
}
}
/**
* Returns an instance of a universal [[Tooltip]] instance.
* @since 5.14.0
*/
get systemTooltip() {
if (!this._systemTooltip) {
const tooltip = Tooltip.new(this, {
themeTags: ["system"]
});
this._systemTooltip = tooltip;
}
return this._systemTooltip;
}
_initResizeSensor() {
if (this._resizeSensorDisposer) {
this._resizeSensorDisposer.dispose();
}
this._resizeSensorDisposer = new ResizeSensor(this.dom, () => {
if (this.autoResize) {
this.resize();
}
});
this._disposers.push(this._resizeSensorDisposer);
}
/**
* If automatic resizing of char is disabled (`root.autoResize = false`), it
* can be resized manually by calling this method.
*/
resize() {
const rect = this._getRealSize();
const size = this._getCalculatedSize(rect);
const w = Math.floor(size.width);
const h = Math.floor(size.height);
if (w > 0 && h > 0) {
const realWidth = Math.floor(rect.width);
const realHeight = Math.floor(rect.height);
const htmlElementContainer = this._htmlElementContainer;
htmlElementContainer.style.width = w + "px";
htmlElementContainer.style.height = h + "px";
if (this._a11yD !== true) {
const focusElementContainer = this._focusElementContainer;
focusElementContainer.style.width = w + "px";
focusElementContainer.style.height = h + "px";
}
this._renderer.resize(realWidth, realHeight, w, h);
const rootContainer = this._rootContainer;
rootContainer.setPrivate("width", w);
rootContainer.setPrivate("height", h);
this._render();
this._handleLogo();
}
}
/**
* Re-applies the current `_settings` to the live `Root` and its sub-objects
* without tearing anything down.
*
* Unlike `_init()`, this creates no new containers, DOM elements, or event
* listeners. Use it after changing settings on an already-initialized `Root`
* (e.g. from a parsed `root` config section).
*
* NOTE: `accessible` can only be turned off (matching constructor behavior);
* it cannot be re-enabled on a live `Root`.
*
* @ignore
*/
_reapplySettings() {
const settings = this._settings;
// Renderer resolution (`useSafeResolution`). `getSafeResolution()` returns
// `undefined` off iOS, which means "use the device pixel ratio".
const safeResolution = $utils.getSafeResolution();
this._renderer.resolution = (settings.useSafeResolution && safeResolution != null) ? safeResolution : window.devicePixelRatio;
// Tooltip container bounds.
if (settings.tooltipContainerBounds) {
this._tooltipContainerSettings = settings.tooltipContainerBounds;
}
if (this._htmlElementContainer) {
this._htmlElementContainer.style.overflow = settings.tooltipContainerBounds ? "" : "hidden";
}
// Accessibility can only be disabled, never re-enabled on a live `Root`.
if (settings.accessible === false) {
this._a11yD = true;
}
// Inner element ARIA / focus attributes.
if (!this._a11yD) {
const inner = this._inner;
if (settings.focusable) {
inner.setAttribute("focusable", "true");
inner.setAttribute("tabindex", this.tabindex + "");
}
else {
inner.removeAttribute("focusable");
inner.removeAttribute("tabindex");
}
if (settings.ariaLabel != null) {
inner.setAttribute("aria-label", settings.ariaLabel);
}
else {
inner.removeAttribute("aria-label");
}
if (settings.role != null) {
inner.setAttribute("role", settings.role);
}
else {
inner.removeAttribute("role");
}
}
// `focusPadding`, `sanitizeHTML` and `calculateSize` are read from
// `_settings` on demand, so they need no explicit re-application.
// Force every element to re-run its update cycle so values derived from
// Root settings downstream (e.g. label fonts pulled from `fontFamily`/
// `fontSize`/`fontWeight`) are re-applied. `markDirty()` on a `Container`
// re-runs its `_updateChildren()`, which is where those values are pushed.
if (this._rootContainer) {
this._rootContainer.markDirty();
this._rootContainer.walkChildren((child) => {
child.markDirty();
});
}
// Push the new resolution / calculated size to the renderer and re-render.
this._render();
}
/**
* Root's settings.
*/
get settings() {
return this._settings;
}
_render() {
if (this.skipRenderFrame) {
this.skipRenderFrame = false;
return;
}
this._renderer.render(this._rootContainer._display);
if (this._focusElementDirty) {
this._updateCurrentFocus();
this._focusElementDirty = false;
}
}
_runTickers(currentTime) {
$array.each(this._tickers, (f) => {
f(currentTime);
});
}
_runAnimations(currentTime) {
let running = 0;
$array.keepIf(this._animations, (animation) => {
const state = animation._runAnimation(currentTime);
if (state === AnimationState.Stopped) {
return false;
}
else if (state === AnimationState.Playing) {
++running;
return true;
}
else {
return true;
}
});
this._isDirtyAnimation = false;
return running === 0;
}
_runDirties() {
//console.time("tick");
let allParents = {};
while (this._isDirtyParents) {
// This must be before calling _prepareChildren
this._isDirtyParents = false;
$object.entries(this._dirtyParents).forEach(([key, parent]) => {
delete this._dirtyParents[key];
if (!parent.isDisposed()) {
allParents[parent.uid] = parent;
parent._prepareChildren();
}
});
}
$object.entries(allParents).forEach(([_key, parent]) => {
parent._updateChildren();
});
const objects = [];
// console.log("_beforeChanged")
$object.entries(this._dirty).forEach(([key, entity]) => {
if (entity.isDisposed()) {
delete this._dirty[key];
}
else {
objects.push(entity);
entity._beforeChanged();
}
});
// console.log("_changed")
objects.forEach((entity) => {
entity._changed();
delete this._dirty[entity.uid];
entity._clearDirty();
});
this._isDirty = false;
const depths = {};
const bounds = [];
$object.entries(this._dirtyBounds).forEach(([key, entity]) => {
delete this._dirtyBounds[key];
if (!entity.isDisposed()) {
depths[entity.uid] = entity.depth();
bounds.push(entity);
}
});
this._positionHTMLElements();
// High depth -> low depth
bounds.sort((x, y) => {
return $order.compare(depths[y.uid], depths[x.uid]);
});
// console.log("_updateBounds")
bounds.forEach((entity) => {
entity._updateBounds();
});
// console.log("_updatePosition")
const dirtyPositions = this._dirtyPositions;
$object.entries(dirtyPositions).forEach(([key, sprite]) => {
delete dirtyPositions[key];
if (!sprite.isDisposed()) {
sprite._updatePosition();
}
});
// console.log("_afterChanged")
objects.forEach((entity) => {
entity._afterChanged();
});
$object.entries(allParents).forEach(([_key, parent]) => {
parent._childrenPrep = false;
parent._childrenUpdt = false;
});
//console.timeEnd("tick");
}
_renderFrame(currentTime) {
if (this._updateTick) {
if (this.events.isEnabled("framestarted")) {
this.events.dispatch("framestarted", {
type: "framestarted",
target: this,
timestamp: currentTime,
});
}
this._checkComputedStyles();
this._runTickers(currentTime);
const animationDone = this._runAnimations(currentTime);
this._runDirties();
this._render();
this._renderer.resetImageArray();
this._positionHTMLElements();
if (this.events.isEnabled("frameended")) {
this.events.dispatch("frameended", {
type: "frameended",
target: this,
timestamp: currentTime,
});
}
return this._tickers.length === 0 &&
animationDone &&
!this._isDirtyAnimation &&
!this._isDirty;
}
else {
return true;
}
}
_runTicker(currentTime, now) {
if (!this.isDisposed()) {
this.animationTime = currentTime;
const done = this._renderFrame(currentTime);
// No more work to do
if (done) {
this._ticker = null;
this.animationTime = null;
}
else {
if (!this._paused) {
if (now) {
this._ticker;
}
else {
rAF(this.fps, this._ticker);
}
}
}
}
}
_runTickerNow(timeout = 10000) {
if (!this.isDisposed()) {
const endTime = performance.now() + timeout;
for (;;) {
const currentTime = performance.now();
if (currentTime >= endTime) {
this.animationTime = null;
break;
}
this.animationTime = currentTime;
const done = this._renderFrame(currentTime);
if (done) {
this.animationTime = null;
break;
}
}
}
}
_startTicker() {
if (this._ticker === null) {
this.animationTime = null;
this._ticker = (currentTime) => {
this._runTicker(currentTime);
};
rAF(this.fps, this._ticker);
}
}
/**
* Returns whether the root is updating or not.
*/
get updateTick() {
return this._updateTick;
}
/**
* Enables or disables the root updating.
*/
set updateTick(value) {
this._updateTick = value;
if (value) {
this._startTicker();
}
}
_addDirtyEntity(entity) {
this._isDirty = true;
if (this._dirty[entity.uid] === undefined) {
this._dirty[entity.uid] = entity;
}
this._startTicker();
}
_addDirtyParent(parent) {
this._isDirty = true;
this._isDirtyParents = true;
if (this._dirtyParents[parent.uid] === undefined) {
this._dirtyParents[parent.uid] = parent;
}
this._startTicker();
}
_addDirtyBounds(entity) {
this._isDirty = true;
if (this._dirtyBounds[entity.uid] === undefined) {
this._dirtyBounds[entity.uid] = entity;
}
this._startTicker();
}
_addDirtyPosition(sprite) {
this._isDirty = true;
if (this._dirtyPositions[sprite.uid] === undefined) {
this._dirtyPositions[sprite.uid] = sprite;
}
this._startTicker();
}
_addAnimation(animation) {
this._isDirtyAnimation = true;
// TODO use numeric id instead
if (this._animations.indexOf(animation) === -1) {
this._animations.push(animation);
}
this._startTicker();
}
_markDirty() {
this._isDirty = true;
}
_markDirtyRedraw() {
this.events.once("frameended", () => {
this._isDirty = true;
this._startTicker();
});
}
eachFrame(f) {
this._tickers.push(f);
this._startTicker();
return new Disposer(() => {
$array.removeFirst(this._tickers, f);
});
}
markDirtyGlobal(container) {
if (!container) {
container = this.container;
}
container.walkChildren((child) => {
if (child instanceof Container) {
this.markDirtyGlobal(child);
}
child.markDirty();
child.markDirtyBounds();
});
}
/**
* Returns width of the target container, in pixels.
*
* @return Width
*/
width() {
// TODO make this more efficient, maybe just return the renderer's width ?
return Math.floor(this._getCalculatedSize(this._getRealSize()).width);
}
/**
* Returns height of the target container, in pixels.
*
* @return Height
*/
height() {
// TODO make this more efficient, maybe just return the renderer's height ?
return Math.floor(this._getCalculatedSize(this._getRealSize()).height);
}
/**
* Disposes root and all the content in it.
*/
dispose() {
if (!this._isDisposed) {
this._isDisposed = true;
this._rootContainer.dispose();
this._renderer.dispose();
this.horizontalLayout.dispose();
this.verticalLayout.dispose();
this.interfaceColors.dispose();
$array.each(this._disposers, (x) => {
x.dispose();
});
if (this._inner) {
$utils.removeElement(this._inner);
}
$array.remove(registry.rootElements, this);
}
}
/**
* Returns `true` if root element is disposed.
*
* @return Disposed?
*/
isDisposed() {
return this._isDisposed;
}
/**
* Triggers screen reader read out a message.
*
* @see {@link https://www.amcharts.com/docs/v5/concepts/accessibility/} for more info
* @param text Alert text
*/
readerAlert(text) {
if (this._a11yD !== true) {
const element = this._readerAlertElement;
text = $utils.stripTags(text);
if (element.innerHTML == text) {
element.innerHTML = "";
}
element.innerHTML = text;
}
}
/**
* Sets themes to be used for the chart.
*
* @see {@link https://www.amcharts.com/docs/v5/concepts/themes/} for more info
* @param themes A list of themes
*/
setThemes(themes) {
this._rootContainer.set("themes", themes);
// otherwise new themes are not applied
const tooltipContainer = this.tooltipContainer;
if (tooltipContainer) {
tooltipContainer._applyThemes();
}
// @todo review this
const interfaceColors = this.interfaceColors;
if (interfaceColors) {
interfaceColors._applyThemes();
}
}
_addTooltip() {
if (!this.tooltipContainer) {
const tooltipContainerSettings = this._tooltipContainerSettings;
const tooltipContainer = this._rootContainer.children.push(Container.new(this, {
position: "absolute",
isMeasured: false,
width: p100,
height: p100,
layer: tooltipContainerSettings ? 35 : 30,
layerMargin: tooltipContainerSettings ? tooltipContainerSettings : undefined
}));
this.tooltipContainer = tooltipContainer;
const tooltip = Tooltip.new(this, {});
this.container._setC("tooltip", tooltip);
tooltip.hide(0);
this._tooltip = tooltip;
}
}
/**
* Accesibility
*/
_registerTabindexOrder(target) {
if (this._a11yD == true) {
return;
}
if (target.get("focusable")) {
$array.pushOne(this._tabindexes, target);
}
else {
$array.remove(this._tabindexes, target);
}
this._invalidateTabindexes();
}
_unregisterTabindexOrder(target) {
if (this._a11yD == true) {
return;
}
$array.remove(this._tabindexes, target);
this._invalidateTabindexes();
}
_invalidateTabindexes() {
if (this._a11yD == true) {
return;
}
this._tabindexes.sort((a, b) => {
const aindex = a.get("tabindexOrder", 0);
const bindex = b.get("tabindexOrder", 0);
if (aindex == bindex) {
return 0;
}
else if (aindex > bindex) {
return 1;
}
else {
return -1;
}
});
const groups = [];
$array.each(this._tabindexes, (item, index) => {
if (!item.getPrivate("focusElement")) {
this._makeFocusElement(index, item);
}
else {
this._moveFocusElement(index, item);
}
const group = item.get("focusableGroup");
if (group && item.getPrivate("focusable") !== false) {
if (groups.indexOf(group) !== -1) {
// Non-first element in the group, make it not directly focusable
item.getPrivate("focusElement").dom.setAttribute("tabindex", "-1");
}
else {
groups.push(group);
}
}
});
}
_updateCurrentFocus() {
if (this._a11yD == true) {
return;
}
if (this._focusedSprite) {
this._decorateFocusElement(this._focusedSprite);
this._positionFocusElement(this._focusedSprite);
}
}
_decorateFocusElement(target, focusElement) {
if (this._a11yD == true) {
return;
}
// Decorate with proper accessibility attributes
if (!focusElement) {
focusElement = target.getPrivate("focusElement").dom;
}
if (!focusElement) {
return;
}
const role = target.get("role");
if (role) {
focusElement.setAttribute("role", role);
}
else {
focusElement.removeAttribute("role");
}
const ariaLabel = target.get("ariaLabel");
if (ariaLabel) {
const label = populateString(target, ariaLabel);
focusElement.setAttribute("aria-label", label);
}
else {
focusElement.removeAttribute("aria-label");
}
const ariaLive = target.get("ariaLive");
if (ariaLive) {
focusElement.setAttribute("aria-live", ariaLive);
}
else {
focusElement.removeAttribute("aria-live");
}
const ariaChecked = target.get("ariaChecked");
if (ariaChecked != null && role && ["checkbox", "option", "radio", "menuitemcheckbox", "menuitemradio", "treeitem"].indexOf(role) !== -1) {
focusElement.setAttribute("aria-checked", ariaChecked ? "true" : "false");
}
else {
focusElement.removeAttribute("aria-checked");
}
const ariaCurrent = target.get("ariaCurrent");
if (ariaCurrent != null) {
focusElement.setAttribute("aria-current", ariaCurrent);
}
else {
focusElement.removeAttribute("aria-current");
}
const ariaSelected = target.get("ariaSelected");
if (ariaSelected != null && role && ["gridcell", "option", "row", "tab", "columnheader", "rowheader", "treeitem"].indexOf(role) !== -1) {
focusElement.setAttribute("aria-selected", ariaSelected ? "true" : "false");
}
else {
focusElement.removeAttribute("aria-selected");
}
if (target.get("ariaHidden")) {
focusElement.setAttribute("aria-hidden", "true");
}
else {
focusElement.removeAttribute("aria-hidden");
}
const ariaOrientation = target.get("ariaOrientation");
if (ariaOrientation) {
focusElement.setAttribute("aria-orientation", ariaOrientation);
}
else {
focusElement.removeAttribute("aria-orientation");
}
const ariaValueNow = target.get("ariaValueNow");
if (ariaValueNow) {
focusElement.setAttribute("aria-valuenow", ariaValueNow);
}
else {
focusElement.removeAttribute("aria-valuenow");
}
const ariaValueMin = target.get("ariaValueMin");
if (ariaValueMin) {
focusElement.setAttribute("aria-valuemin", ariaValueMin);
}
else {
focusElement.removeAttribute("aria-valuemin");
}
const ariaValueMax = target.get("ariaValueMax");
if (ariaValueMax) {
focusElement.setAttribute("aria-valuemax", ariaValueMax);
}
else {
focusElement.removeAttribute("aria-valuemax");
}
const ariaValueText = target.get("ariaValueText");
if (ariaValueText) {
focusElement.setAttribute("aria-valuetext", ariaValueText);
}
else {
focusElement.removeAttribute("aria-valuetext");
}
const ariaControls = target.get("ariaControls");
if (ariaControls) {
focusElement.setAttribute("aria-controls", ariaControls);
}
else {
focusElement.removeAttribute("aria-controls");
}
if (target.get("visible") && target.get("opacity") !== 0 && target.get("role") != "tooltip" && !target.isHidden() && target.getPrivate("focusable") !== false && (target.height() || target.width())) {
if (focusElement.getAttribute("tabindex") != "-1") {
focusElement.setAttribute("tabindex", "" + this.tabindex);
}
focusElement.removeAttribute("aria-hidden");
}
else {
focusElement.removeAttribute("tabindex");
focusElement.setAttribute("aria-hidden", "true");
}
}
_makeFocusElement(index, target) {
if (target.getPrivate("focusElement") || this._a11yD == true) {
return;
}
// Init
const focusElement = document.createElement("div");
if (target.get("role") != "tooltip") {
focusElement.tabIndex = this.tabindex;
}
focusElement.style.position = "absolute";
$utils.setInteractive(focusElement, false);
const disposers = [];
target.setPrivate("focusElement", {
dom: focusElement,
disposers,
});
this._decorateFocusElement(target);
disposers.push($utils.addEventListener(focusElement, "focus", (ev) => {
this._handleFocus(ev);
}));
disposers.push($utils.addEventListener(focusElement, "blur", (ev) => {
this._handleBlur(ev);
}));
this._moveFocusElement(index, target);
}
_removeFocusElement(target) {
if (this._a11yD == true) {
return;
}
$array.remove(this._tabindexes, target);
const focusElement = target.getPrivate("focusElement");
if (focusElement) {
const container = this._focusElementContainer;
container.removeChild(focusElement.dom);
$array.each(focusElement.disposers, (x) => {
x.dispose();
});
target.removePrivate("focusElement");
}
}
_hideFocusElement(target) {
if (this._a11yD == true) {
return;
}
const focusElement = target.getPrivate("focusElement");
focusElement.dom.style.display = "none";
}
_moveFocusElement(index, target) {
if (this._a11yD == true) {
return;
}
// Get container
const container = this._focusElementContainer;
const focusElement = target.getPrivate("focusElement").dom;
if (focusElement === this._focusElementContainer.children[index]) {
// Nothing to do
return;
}
const next = this._focusElementContainer.children[index + 1];
if (next) {
container.insertBefore(focusElement, next);
}
else {
container.append(focusElement);
}
}
_positionFocusElement(target) {
if (this._a11yD == true || target == undefined) {
return;
}
this._focusElementContainer.scrollTop = 0;
const bounds = target.globalBounds();
let width = bounds.right == bounds.left ? target.width() : bounds.right - bounds.left;
let height = bounds.top == bounds.bottom ? target.height() : bounds.bottom - bounds.top;
const padding = this._settings.focusPadding !== undefined ? this._settings.focusPadding : 2;
let x = bounds.left - padding;
let y = bounds.top - padding;
if (width < 0) {
x += width;
width = Math.abs(width);
}
if (height < 0) {
y += height;
height = Math.abs(height);
}
const focusElement = target.getPrivate("focusElement").dom;
focusElement.style.top = y + "px";
focusElement.style.left = x + "px";
focusElement.style.width = (width + padding * 2) + "px";
focusElement.style.height = (height + padding * 2) + "px";
}
_getSpriteByFocusElement(target) {
let found;
$array.eachContinue(this._tabindexes, (item, _index) => {
if (item.getPrivate("focusElement").dom === target) {
found = item;
return false;
}
return true;
});
return found;
}
_handleFocus(ev) {
if (this._a11yD == true) {
return;
}
// Get element
//const focused = this._tabindexes[index];
const focused = this._getSpriteByFocusElement(ev.target);
if (!focused) {
return;
}
// Jump over hidden elements
if (!focused.isVisibleDeep()) {
this._focusNext(ev.target, this._isShift ? -1 : 1);
return;
}
// Size and position
this._positionFocusElement(focused);
//this._decorateFocusElement(focused);
this._focusedSprite = focused;
if (focused.events.isEnabled("focus")) {
focused.events.dispatch("focus", {
type: "focus",
originalEvent: ev,
target: focused
});
}
if (focused.getPrivate("focusElement").dom.tabIndex == -1) {
this._skipNextGroupJump = false;
}
}
_focusNext(el, direction, group) {
if (this._a11yD == true) {
return;
}
const rootNode = this.dom.getRootNode ? this.dom.getRootNode() : null;
let queryRoot = this.dom.ownerDocument || document;
if (rootNode) {
if (typeof ShadowRoot !== "undefined" && rootNode instanceof ShadowRoot) {
queryRoot = rootNode;
}
else if (rootNode instanceof Document) {
queryRoot = rootNode;
}
}
const focusableElements = Array.from(queryRoot.querySelectorAll([
'a[href]',
'area[href]',
'button:not([disabled])',
'details',
'input:not([disabled])',
'iframe:not([disabled])',
'select:not([disabled])',
'textarea:not([disabled])',
'[contentEditable=""]',
'[contentEditable="true"]',
'[contentEditable="TRUE"]',
'[tabindex]:not([tabindex^="-"])',
//':not([disabled])'
].join(',')));
let index = focusableElements.indexOf(el) + direction;
if (index < 0) {
index = focusableElements.length - 1;
}
else if (index >= focusableElements.length) {
index = 0;
}
const targetElement = focusableElements[index];
if (group && direction == -1) {
const target = this._getSpriteByFocusElement(targetElement);
if (target && target.get("focusableGroup") == group) {
this._focusNext(targetElement, direction);
return;
}
}
if (targetElement) {
targetElement.focus();
}
}
_handleBlur(ev) {
if (this._a11yD == true) {
return;
}
const focused = this._focusedSprite;
if (focused && !focused.isDisposed() && focused.events.isEnabled("blur")) {
focused.events.dispatch("blur", {
type: "blur",
originalEvent: ev,
target: focused
});
}
this._focusedSprite = undefined;
}
/**
* @ignore
*/
updateTooltip(target) {
if (this._a11yD == true) {
return;
}
const text = $utils.stripFormatTags($utils.stripTags(target._getText()));
let tooltipElement = target.getPrivate("tooltipElement");
if (target.get("role") == "tooltip" && text != "") {
if (!tooltipElement) {
tooltipElement = this._makeTooltipElement(target);
}
if (tooltipElement.innerHTML != text) {
tooltipElement.innerHTML = text;
}
tooltipElement.setAttribute("aria-hidden", target.isVisibleDeep() ? "false" : "true");
}
else if (tooltipElement) {
tooltipElement.remove();
target.removePrivate("tooltipElement");
}
}
_makeTooltipElement(target) {
const container = this._tooltipElementContainer;
const tooltipElement = document.createElement("div");
tooltipElement.style.position = "absolute";
tooltipElement.style.width = "1px";
tooltipElement.style.height = "1px";
tooltipElement.style.overflow = "hidden";
tooltipElement.style.clip = "rect(1px, 1px, 1px, 1px)";
$utils.setInteractive(tooltipElement, false);
this._decorateFocusElement(target, tooltipElement);
container.append(tooltipElement);
target.setPrivate("tooltipElement", tooltipElement);
return tooltipElement;
}
_removeTooltipElement(target) {
if (this._a11yD == true) {
return;
}
const tooltipElement = target.getPrivate("tooltipElement");
if (tooltipElement) {
const parent = tooltipElement.parentElement;
if (parent) {
parent.removeChild(tooltipElement);
}
}
}
_invalidateAccessibility(target) {
if (this._a11yD == true) {
return;
}
this._focusElementDirty = true;
const focusElement = target.getPrivate("focusElement");
if (target.get("focusable")) {
if (focusElement) {
this._decorateFocusElement(target);
if (this._focusedSprite === target) {
this._positionFocusElement(target);
}
}
// else {
// this._renderer._makeFocusElement(0, this);
// }
}
else if (focusElement) {
this._removeFocusElement(target);
}
//this.updateCurrentFocus();
}
/**
* Returns `true` if `target` is currently focused.
*
* @param target Target
* @return Focused?
*/
focused(target) {
return this._focusedSprite === target;
}
/**
* Converts document coordinates to coordinates withing root element.
*
* @param point Document point
* @return Root point
*/
documentPointToRoot(point) {
const rect = this._getRealSize();
const size = this._getCalculatedSize(rect);
const scaleWidth = size.width / rect.width;
const scaleHeight = size.height / rect.height;
return {
x: (point.x - rect.left) * scaleWidth,
y: (point.y - rect.top) * scaleHeight,
};
}
/**
* Converts root coordinates to document
*
* @param point Document point
* @return Root point
*/
rootPointToDocument(point) {
const rect = this._getRealSize();
const size = this._getCalculatedSize(rect);
const scaleWidth = size.width / rect.width;
const scaleHeight = size.height / rect.height;
return {
x: (point.x / scaleWidth) + rect.left,
y: (point.y / scaleHeight) + rect.top
};
}
/**
* @ignore
*/
addDisposer(disposer) {
this._disposers.push(disposer);
return disposer;
}
_updateComputedStyles() {
const styles = window.getComputedStyle(this.dom);
const oldFonts = this._fontHash;
const newFonts = $object.keys(styles).flatMap((k) => { var _a; return typeof k === "string" && k.startsWith("font") ? [(_a = styles[k]) === null || _a === void 0 ? void 0 : _a.toString()] : []; });
if (oldFonts.length !== newFonts.length) {
this._fontHash = newFonts;
return true;
}
for (let i = 0; i < oldFonts.length; i++) {
if (oldFonts[i] !== newFonts[i]) {
this._fontHash = newFonts;
return true;
}
}
return false;
}
_checkComputedStyles() {
if (this._updateComputedStyles()) {
this._invalidateLabelBounds(this.container);
}
}
_invalidateLabelBounds(target) {
if (target instanceof Container) {
target.children.each((child) => {
this._invalidateLabelBounds(child);
});
}
else if (target instanceof Text) {
target.markDirtyBounds();
}
}
/**
* To all the clever heads out there. Yes, we did not make any attempts to
* scramble this.
*
* This is a part of a tool meant for our users to manage their commercial
* licenses for removal of amCharts branding from charts.
*
* The only legit way to do so is to purchase a commercial license for amCharts:
* https://www.amcharts.com/online-store/
*
* Removing or altering this code, or disabling amCharts branding in any other
* way is against the license and thus illegal.
*/
_hasLicense() {
for (let i = 0; i < registry.licenses.length; i++) {
if (registry.licenses[i].match(/^AM5C.{5,}/i)) {
return true;
}
}
return false;
}
_licenseApplied() {
if (this._logo) {
this._logo.set("forceHidden", true);
}
}
/**
* @ignore
*/
get debugGhostView() {
return this._renderer.debugGhostView;
}
/**
* @ignore
*/
set debugGhostView(value) {
this._renderer.debugGhostView = value;
}
/**
* Set this to `true` if you need chart to require first a tap onto it before
* touch gesture related functionality like zoom/pan is turned on.
*
* @see {@link https://www.amcharts.com/docs/v5/getting-started/root-element/#Touch_related_options} for more info
* @default false
* @since 5.2.9
* @param value Needs a tap to activate touch functions
*/
set tapToActivate(value) {
this._renderer.tapToActivate = value;
}
/**
* @return Needs a tap to activate touch functions
*/
get tapToActivate() {
return this._renderer.tapToActivate;
}
/**
* If `tapToActivate` is set to `true`, this setting will determine number
* of milliseconds the chart will stay "active", before releasing the
* controls back to the page.
*
* @see {@link https://www.amcharts.com/docs/v5/getting-started/root-element/#Touch_related_options} for more info
* @default 3000
* @since 5.2.9
* @param value Timeout
*/
set tapToActivateTimeout(value) {
this._renderer.tapToActivateTimeout = value;
}
/**
* @return Timeout
*/
get tapToActivateTimeout() {
return this._renderer.tapToActivateTimeout;
}
_makeHTMLElement(target) {
// Get container
const container = this._htmlElementContainer;
// Init
const htmlElement = document.createElement("div");
target.setPrivate("htmlElement", htmlElement);
// Should we put this into a wrapper?
let needWrapper = false;
let wrapperTarget;
target._walkParents((parent) => {
if (parent.get("verticalScrollbar")) {
needWrapper = true;
wrapperTarget = parent;
return false;
}
});
let htmlElementWrapper;
if (needWrapper) {
htmlElementWrapper = document.createElement("div");
target.setPrivate("htmlElementWrapper", htmlElementWrapper);
target.setPrivate("wrapperContainer", wrapperTarget);
htmlElementWrapper.style.position = "absolute";
htmlElementWrapper.style.overflow = "hidden";
htmlElementWrapper.style.boxSizing = "border-box";
htmlElementWrapper.style.top = "0px";
htmlElementWrapper.style.left = "0px";
htmlElementWrapper.style.width = "100%";
htmlElementWrapper.style.height = "100%";
wrapperTarget.events.on("boundschanged", () => {
this._positionHTMLElement(target);
});
}
//htmlElement.tabIndex = this.tabindex;
htmlElement.style.position = "absolute";
htmlElement.style.overflow = "auto";
htmlElement.style.boxSizing = "border-box";
$utils.setInteractive(htmlElement, target.get("interactive", false));
// Translate events
if (target.events.isEnabled("click")) {
$utils.setInteractive(htmlElement, true);
this._disposers.push($utils.addEventListener(htmlElement, "click", (ev) => {
const downEvent = this._renderer.getEvent(ev);
target.events.dispatch("click", {
type: "click",
originalEvent: downEvent.event,
point: downEvent.point,
simulated: false,
target: target
});
}));
}
this._positionHTMLElement(target);
if (needWrapper) {
htmlElementWrapper.append(htmlElement);
container.append(htmlElementWrapper);
}
else {
container.append(htmlElement);
}
$array.pushOne(this._htmlEnabledContainers, target);
return htmlElement;
}
_positionHTMLElements() {
$array.each(this._htmlEnabledContainers, (target) => {
this._positionHTMLElement(target);
});
}
_positionHTMLElement(target) {
const htmlElementWrapper = target.getPrivate("htmlElementWrapper");
if (htmlElementWrapper) {
const wrapperTarget = target.getPrivate("wrapperContainer");
if (wrapperTarget) {
const bounds = wrapperTarget.globalBounds();
htmlElementWrapper.style.clipPath = "rect(" + bounds.top + "px " + bounds.right + "px " + bounds.bottom + "px " + bounds.left + "px)";
}
}
const htmlElement = target.getPrivate("htmlElement");
if (htmlElement) {
// Translate settings
const visualSettings = [
"paddingTop",
"paddingRight",
"paddingBottom",
"paddingLeft",
"minWidth",
"minHeight",
"maxWidth",
"maxHeight"
];
$array.each(visualSettings, (setting) => {
const value = target.get(setting);
if (value) {
htmlElement.style[setting] = value + "px";
}
else {
htmlElement.style[setting] = "";
}
});
const strtingSettings = [
"fontFamily",
"fontSize",
"fontStyle",
"fontWeight",
"fontStyle",
"fontVariant",
"textDecoration"
];
$array.each(strtingSettings, (setting) => {
const value = target.get(setting);
if (value) {
if (setting == "fontSize" && !$type.isString(value)) {
htmlElement.style[setting] = value + "px";
}
else {
htmlElement.style[setting] = value + "";
}
}
else {
htmlElement.style[setting] = "";
}
});
// Init and reset scale / rotation
const scale = target.compositeScale() || 1;
const rotation = target.compositeRotation() || 0;
htmlElement.style.transform = "";
htmlElement.style.transformOrigin = "";
// Deal with opacity
const opacity = target.compositeOpacity();
setTimeout(() => {
htmlElement.style.opacity = opacity + "";
}, 10);
const visible = target.isVisibleDeep();
if (visible) {
htmlElement.style.display = "block";
}
// Deal with position
// const bounds = target.globalBounds();
// htmlElement.style.top = (bounds.top) + "px";
// htmlElement.style.left = (bounds.left) + "px";
let pos = {
x: target.x() + target.get("dx", 0),
y: target.y() + target.get("dy", 0)
};
if (target.parent) {
pos = target.parent.toGlobal(pos);
}
htmlElement.style.top = pos.y + "px";
htmlElement.style.left = pos.x + "px";
// Use width/height if those are set
const width = target.get("width");
const height = target.get("height");
let w = 0;
let h = 0;
if (width) {
w = target.width();
}
if (height) {
h = target.height();
}
if (!width || !height) {
htmlElement.style.position = "fixed";
htmlElement.style.width = "";
htmlElement.style.height = "";
const bbox = htmlElement.getBoundingClientRect();
htmlElement.style.position = "absolute";
if (!width)
w = bbox.width;
if (!height)
h = bbox.height;
let lw = w / scale;
let lh = h / scale;
let cx = target.get("centerX", 0);
let cy = target.get("centerY", 0);
let ll = 0;
let lr = 0;
let lt = 0;
let lb = 0;
if (cx instanceof Percent) {
ll = -cx.value * lw;
lr = (1 - cx.value) * lw;
}
else {
ll = -cx;
lr = lw - cx;
}
if (cy instanceof Percent) {
lt = -cy.value * lh;
lb = (1 - cy.value) * lh;
}
else {
lt = -cy;
lb = lh - cy;
}
target._localBounds = { left: ll, right: lr, top: lt, bottom: lb };
let previousBounds = target._adjustedLocalBounds;
let newBounds = target._display.getAdjustedBounds(target._localBounds);
target._adjustedLocalBounds = newBounds;
// compare each value of the bounds
if (previousBounds.left !== newBounds.left || previousBounds.right !== newBounds.right || previousBounds.top !== newBounds.top || previousBounds.bottom !== newBounds.bottom) {
target.markDirtyBounds();
}
}
if (w > 0) {
htmlElement.style.minWidth = (w) + "px";
}
if (h > 0) {
htmlElement.style.minHeight = (h) + "px";
}
// Hide or show
if (!visible || opacity == 0) {
htmlElement.style.display = "none";
}
// Center position
const x = target.get("centerX", 0);
const originX = isPercent(x) ? x.percent + "%" : x + "px";
const y = target.get("centerY", 0);
const originY = isPercent(y) ? y.percent + "%" : y + "px";
if (x || y) {
htmlElement.style.transform = "translate(-" + originX + ", -" + originY + ")" + htmlElement.style.transform;
}
// Deal with scale
if (scale != 1) {
htmlElement.style.transform += "scale(" + scale + ")";
}
if (rotation != 0) {
htmlElement.style.transform += " rotate(" + rotation + "deg)";
}
if (htmlElement.style.transform != "") {
htmlElement.style.transformOrigin = originX + " " + originY;
//htmlElement.style.transformOrigin = "0% 0%";
}
}
}
/**
* Sanitizes HTML content before it gets injected via `innerHTML`, unless
* sanitization is disabled via the `sanitizeHTML` setting.
*
* @ignore
*/
_sanitizeHTML(html) {
return this._settings.sanitizeHTML ? $utils.sanitizeHTML(html) : html;
}
_setHTMLContent(target, html) {
let htmlElement = target.getPrivate("htmlElement");
if (!htmlElement) {
htmlElement = this._makeHTMLElement(target);
if (!this.autoResize) {
this.resize();
}
}
const sanitized = this._sanitizeHTML(html);
if (htmlElement.innerHTML != sanitized) {
htmlElement.innerHTML = sanitized;
}
}
_removeHTMLContent(target) {
const htmlElementWrapper = target.getPrivate("htmlElementWrapper");
const htmlElement = target.getPrivate("htmlElement");
if (htmlElementWrapper) {
this._htmlElementContainer.removeChild(htmlElementWrapper);
target.removePrivate("htmlElement");
target.removePrivate("htmlElementWrapper");
target.removePrivate("wrapperContainer");
}
else if (htmlElement) {
this._htmlElementContainer.removeChild(htmlElement);
target.removePrivate("htmlElement");
target.removePrivate("wrapperContainer");
}
$array.remove(this._htmlEnabledContainers, target);
}
}
//# sourceMappingURL=Root.js.map