UNPKG

@amcharts/amcharts5

Version:
2,002 lines 78.1 kB
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