UNPKG

@amcharts/amcharts4

Version:
613 lines 21.9 kB
import { __read, __spread } from "tslib"; /** * ============================================================================ * IMPORTS * ============================================================================ * @hidden */ import { system } from "../System"; import { registry } from "../Registry"; import { Container } from "../Container"; import { Component } from "../Component"; import { Paper } from "../rendering/Paper"; import { SVGContainer, svgContainers } from "../rendering/SVGContainer"; import { FocusFilter } from "../rendering/filters/FocusFilter"; import { Preloader } from "../elements/Preloader"; import { AmChartsLogo } from "../elements/AmChartsLogo"; import { Tooltip } from "../elements/Tooltip"; import { Disposer, MultiDisposer } from "../utils/Disposer"; import { percent } from "./Percent"; import { options } from "../Options"; import * as $array from "./Array"; import * as $type from "./Type"; import * as $dom from "./DOM"; import * as $utils from "./Utils"; import * as $log from "./Log"; /** * ============================================================================ * INSTANTIATION FUNCTIONS * ============================================================================ * @hidden */ /** * Creates all HTML and SVG containers needed for the chart instance, as well * as the new [[Sprite]] (as specified in `classType` parameter). * * @param htmlElement A container to creat elements in * @param classType A class definition of the new element to create * @return Newly-created Sprite object */ function createChild(htmlElement, classType) { var htmlContainer = $dom.getElement(htmlElement); // If there's no container available yet, we create a fake one var tmpContainer = false; if (!htmlContainer) { htmlContainer = document.createElement("div"); htmlContainer.style.width = "200px"; htmlContainer.style.height = "200px"; htmlContainer.style.top = "0"; htmlContainer.style.left = "0"; htmlContainer.style.visibility = "hidden"; htmlContainer.style.position = "absolute"; document.body.appendChild(htmlContainer); tmpContainer = true; } if (htmlContainer) { htmlContainer.innerHTML = ""; //htmlContainer.style.overflow = "hidden"; var svgDiv_1 = new SVGContainer(htmlContainer); var paper = new Paper(svgDiv_1.SVGContainer, "svg-" + (svgContainers.length - 1)); // the approach with masks is chosen because overflow:visible is set on SVG element in order tooltips could go outside // svg area - this is often needed when working with small charts. // main container which holds content container and tooltips container var container_1 = new Container(); container_1.htmlContainer = htmlContainer; container_1.svgContainer = svgDiv_1; container_1.width = percent(100); container_1.height = percent(100); container_1.background.fillOpacity = 0; container_1.paper = paper; paper.append(container_1.group); // Set up moving to proper element container if it's not yet ready at call time if (tmpContainer) { $dom.ready(function () { container_1.moveHtmlContainer(htmlElement); }); } // this is set from parent container, but this one doesn't have, so do it manually. container_1.relativeWidth = 1; container_1.relativeHeight = 1; svgDiv_1.container = container_1; // creating classType instance var sprite_1 = container_1.createChild(classType); sprite_1.topParent = container_1; var uid = sprite_1.uid; registry.invalidSprites[uid] = []; registry.invalidDatas[uid] = []; registry.invalidPositions[uid] = []; registry.invalidLayouts[uid] = []; container_1.baseId = uid; sprite_1.isBaseSprite = true; sprite_1.focusFilter = new FocusFilter(); registry.baseSprites.push(sprite_1); registry.baseSpritesByUid[uid] = sprite_1; sprite_1.maskRectangle = { x: 0, y: 0, width: Math.max(svgDiv_1.width || 0, 0), height: Math.max(svgDiv_1.height || 0, 0) }; // this solves issues with display:none, as all children are measured as 0x0 container_1.events.on("maxsizechanged", function (event) { if (event.previousWidth == 0 || event.previousHeight == 0) { container_1.deepInvalidate(); } if (sprite_1.maskRectangle) { sprite_1.maskRectangle = { x: 0, y: 0, width: Math.max(svgDiv_1.width || 0, 0), height: Math.max(svgDiv_1.height || 0, 0) }; } }); var loopTimer_1 = null; // Checks to see whether the chart was properly disposed or not var loop_1 = function () { if (!sprite_1.isDisposed()) { if ($dom.getRoot(sprite_1.dom) == null) { if (options.autoDispose) { container_1.htmlContainer = undefined; svgDiv_1.htmlElement = undefined; sprite_1.dispose(); } else { $log.warn("Chart was not disposed", sprite_1.uid); } loopTimer_1 = null; } else { loopTimer_1 = window.setTimeout(loop_1, 1000); } } else { loopTimer_1 = null; } }; loop_1(); sprite_1.addDisposer(new Disposer(function () { if (loopTimer_1 !== null) { clearTimeout(loopTimer_1); } $array.remove(registry.baseSprites, sprite_1); registry.baseSpritesByUid[sprite_1.uid] = undefined; })); // TODO figure out a better way of doing this sprite_1.addDisposer(container_1); // tooltip container var tooltipContainer_1 = container_1.createChild(Container); tooltipContainer_1.topParent = container_1; tooltipContainer_1.width = percent(100); tooltipContainer_1.height = percent(100); tooltipContainer_1.isMeasured = false; container_1.tooltipContainer = tooltipContainer_1; sprite_1.tooltip = new Tooltip(); sprite_1.tooltip.hide(0); sprite_1.tooltip.setBounds({ x: 0, y: 0, width: tooltipContainer_1.maxWidth, height: tooltipContainer_1.maxHeight }); tooltipContainer_1.events.on("maxsizechanged", function () { $type.getValue(sprite_1.tooltip).setBounds({ x: 0, y: 0, width: tooltipContainer_1.maxWidth, height: tooltipContainer_1.maxHeight }); }, undefined, false); //@todo: maybe we don't need to create one by default but only on request? var preloader_1 = new Preloader(); preloader_1.events.on("inited", function () { preloader_1.__disabled = true; }, undefined, false); container_1.preloader = preloader_1; //if (!options.commercialLicense) { if (sprite_1 instanceof Container && !sprite_1.hasLicense()) { var logo_1 = tooltipContainer_1.createChild(AmChartsLogo); tooltipContainer_1.events.on("maxsizechanged", function (ev) { if ((tooltipContainer_1.maxWidth <= 100) || (tooltipContainer_1.maxHeight <= 50)) { logo_1.hide(); } else if (logo_1.isHidden || logo_1.isHiding) { logo_1.show(); } }, undefined, false); sprite_1.logo = logo_1; logo_1.align = "left"; logo_1.valign = "bottom"; } $utils.used(sprite_1.numberFormatter); // need to create one. // Set this as an autonomouse instance // Controls like Preloader, Export will use this. container_1.isStandaloneInstance = true; if (options.onlyShowOnViewport) { if (!$dom.isElementInViewport(htmlContainer, options.viewportTarget)) { sprite_1.__disabled = true; sprite_1.tooltipContainer.__disabled = true; var disposers = [ $dom.addEventListener(window, "DOMContentLoaded", function () { viewPortHandler(sprite_1); }), $dom.addEventListener(window, "load", function () { viewPortHandler(sprite_1); }), $dom.addEventListener(window, "resize", function () { viewPortHandler(sprite_1); }), $dom.addEventListener(window, "scroll", function () { viewPortHandler(sprite_1); }) ]; if (options.viewportTarget) { var targets = $type.isArray(options.viewportTarget) ? options.viewportTarget : options.viewportTarget ? [options.viewportTarget] : []; for (var i = 0; i < targets.length; i++) { var target = targets[i]; disposers.push($dom.addEventListener(target, "resize", function () { viewPortHandler(sprite_1); })); disposers.push($dom.addEventListener(target, "scroll", function () { viewPortHandler(sprite_1); })); } } var disposer = new MultiDisposer(disposers); sprite_1.addDisposer(disposer); sprite_1.vpDisposer = disposer; } else if (options.queue) { addToQueue(sprite_1); } } else if (options.queue) { addToQueue(sprite_1); } return sprite_1; } else { system.log("html container not found"); throw new Error("html container not found"); } } /** * Disposes all of the currently active charts. */ export function disposeAllCharts() { while (registry.baseSprites.length !== 0) { registry.baseSprites.pop().dispose(); } } export function addToQueue(sprite) { if (registry.queue.indexOf(sprite) == -1) { sprite.__disabled = true; sprite.tooltipContainer.__disabled = true; sprite.events.disableType("appeared"); if (registry.queue.length == 0) { registry.events.once("exitframe", function () { queueHandler(sprite); }); system.requestFrame(); } sprite.addDisposer(new Disposer(function () { removeFromQueue(sprite); })); registry.queue.push(sprite); } } export function removeFromQueue(sprite) { var index = registry.queue.indexOf(sprite); if (index >= 0) { registry.queue.splice(registry.queue.indexOf(sprite), 1); var nextSprite = registry.queue[index]; if (nextSprite) { queueHandler(nextSprite); } } } /** * Checks whether the chart was not initialized fully due to setting * of `onlyShowOnViewport`. If it hasn't and is now in the viewport * the chart will be initialized. * * @since 4.9.12 * @param sprite Top-level chart object */ export function viewPortHandler(sprite) { if (sprite.__disabled && $dom.isElementInViewport(sprite.htmlContainer, options.viewportTarget)) { if (sprite.vpDisposer) { sprite.vpDisposer.dispose(); } addToQueue(sprite); } } export function queueHandler(sprite) { if (sprite && sprite.tooltipContainer) { sprite.__disabled = false; sprite.tooltipContainer.__disabled = false; sprite.events.enableType("appeared"); sprite.dispatch("removedfromqueue"); if (sprite.showOnInit) { sprite.events.on("appeared", function () { removeFromQueue(sprite); }); } if (sprite.vpDisposer) { sprite.vpDisposer.dispose(); } if (sprite instanceof Container) { sprite.invalidateLabels(); } if (sprite.tooltipContainer) { sprite.tooltipContainer.invalidateLayout(); } if (sprite instanceof Component) { sprite.invalidateData(); sprite.reinit(); sprite.events.once("datavalidated", function () { if (sprite.showOnInit) { sprite.appear(); } else { removeFromQueue(sprite); } }); } else { sprite.reinit(); sprite.events.once("inited", function () { removeFromQueue(sprite); }); if (sprite.showOnInit) { sprite.appear(); } } } } /** * A shortcut to creating a chart instance. * * The first argument is either a reference to or an id of a DOM element to be * used as a container for the chart. * * The second argument is the type reference of the chart type. (for plain * JavaScript users this can also be a string indicating chart type) * * ```TypeScript * let chart = am4core.create("chartdiv", am4charts.PieChart); * ``` * ```JavaScript * // Can pass in chart type reference like this: * var chart = am4core.create("chartdiv", am4charts.PieChart); * * // ... or chart class type as a string: * var chart = am4core.create("chartdiv", "PieChart"); * ``` * * @param htmlElement Reference or id of the target container element * @param classType Class type of the target chart type * @return Chart instance */ export function create(htmlElement, classType) { // This is a nasty hack for the benefit of vanilla JS users, who do not // enjoy benefits of type-check anyway. // We're allowing passing in a name of the class rather than type reference // itself. var classError; if ($type.isString(classType)) { if ($type.hasValue(registry.registeredClasses[classType])) { classType = registry.registeredClasses[classType]; } else { classType = registry.registeredClasses["Container"]; classError = new Error("Class [" + classType + "] is not loaded."); } } // Create the chart var chart = createChild(htmlElement, classType); // Error? if (classError) { chart.raiseCriticalError(classError); } return chart; } /** * A shortcut to creating a chart from a config object. * * Example: * * ```TypeScript * let chart am4core.createFromConfig({ ... }, "chartdiv", am4charts.XYChart ); * ``` * ```JavaScript * var chart am4core.createFromConfig({ ... }, "chartdiv", "XYChart" ); * ``` * * If `chartType` parameter is not supplied it must be set in a config object, * via reference to chart type, e.g.: * * ```TypeScript * { * "type": am4charts.XYChart, * // ... * } * ``` * ```JavaScript * { * "type": am4charts.XYChart, * // ... * } * ``` * * Or via string: (if you are using JavaScript) * * ```TypeScript * { * "type": "XYChart", * // ... * } * ``` * ```JavaScript * { * "type": "XYChart", * // ... * } * ``` * * A `container` can either be a reference to an HTML container to put chart * in, or it's unique id. * * If `container` is not specified, it must be included in the config object: * * ```TypeScript * { * "type": "XYChart", * "container": "chartdiv", * // ... * } * ``` * ```JavaScript * { * "type": "XYChart", * "container": "chartdiv", * // ... * } * ``` * * @param config Config object in property/value pairs * @param htmlElement Container reference or ID * @param objectType Chart type * @return A newly created chart instance * @todo Throw exception if type is not correct */ export function createFromConfig(config, htmlElement, classType) { // Extract chart type from config if necessary if (!$type.hasValue(classType)) { classType = config.type; delete config.type; } // Extract element from config if necessary if (!$type.hasValue(htmlElement)) { htmlElement = config.container; delete config.container; } // Check if we need to extract actual type reference var finalType; var classError; if ($type.isString(classType) && $type.hasValue(registry.registeredClasses[classType])) { finalType = registry.registeredClasses[classType]; } else if (typeof classType !== "function") { finalType = Container; classError = new Error("Class [" + classType + "] is not loaded."); } else { finalType = classType; } // Create the chart var chart = createChild(htmlElement, finalType); // Set config if (classError) { chart.raiseCriticalError(classError); } else { chart.config = config; } return chart; } /** * Useful in creating real queues form mult-chart creation. * * Accepts a reference to a function which crates and returns actual chart * object. * * It returns a `Promise` which you can use to catch chart instance once it's * created. * * ```TypeScript * am4core.createDeferred(function(div) { * // Create first chart * let chart = am4core.create(div, am4charts.XYChart); * // ... * return chart; * }, "chartdiv1").then(chart) { * // `chart` variable holds an instance of the chart * console.log("Chart ready", chart); * } * * am4core.createDeferred(function(div) { * // Create second chart * let chart = am4core.create(div, am4charts.PieChart); * // ... * return chart; * }, "chartdiv2").then(chart) { * // `chart` variable holds an instance of the chart * console.log("Chart ready", chart); * } * ``` * ```JavaScript * am4core.createDeferred(function(div) { * // Create first chart * var chart = am4core.create(div, am4charts.XYChart); * // ... * return chart; * }, "chartdiv1").then(chart) { * // `chart` variable holds an instance of the chart * console.log("Chart ready", chart); * } * * am4core.createDeferred(function(div) { * // Create second chart * var chart = am4core.create(div, am4charts.PieChart); * // ... * return chart; * }, "chartdiv2").then(chart) { * // `chart` variable holds an instance of the chart * console.log("Chart ready", chart); * } * ``` * * @see {@link https://www.amcharts.com/docs/v4/concepts/performance/#Deferred_daisy_chained_instantiation} for more information * @since 4.10.0 * @param callback Callback function that creates chart * @param scope Scope to call callback in * @param ...rest Parameters to pass into callback * @return Promise with chart instance */ export function createDeferred(callback, scope) { var rest = []; for (var _i = 2; _i < arguments.length; _i++) { rest[_i - 2] = arguments[_i]; } return new Promise(function (resolve, reject) { registry.deferred.push({ scope: scope, callback: callback, args: rest, resolve: resolve }); if (registry.deferred.length == 1) { processNextDeferred(); } }); } function processNextDeferred() { var _a; var next = registry.deferred[0]; if (next) { var sprite_2 = (_a = next.callback).call.apply(_a, __spread([next.scope], next.args)); sprite_2.events.on("ready", function () { next.resolve(sprite_2); registry.deferred.shift(); if (options.deferredDelay) { setTimeout(processNextDeferred, options.deferredDelay); } else { processNextDeferred(); } }); } } /** * Applies a theme to System, and subsequently all chart instances created * from that point forward. * * amCharts supports multiple themes. Calling `useTheme` multiple times will * make the System apply multiple themes, rather than overwrite previously * set one. * * This enables combining features from multiple themes on the same chart. * E.g.: * * ```TypeScript * am4core.useTheme(am4themes.material); * am4core.useTheme(am4themes.animated); * ``` * ```JavaScript * am4core.useTheme(am4themes.material); * am4core.useTheme(am4themes.animated); * ``` * * The above will apply both the Material color and animation options to all * charts created. * * @param value A reference to a theme */ export function useTheme(value) { if (registry.themes.indexOf(value) === -1) { registry.themes.push(value); } } /** * Removes a theme from "active themes" list, so it won't get applied to any * charts created subsequently. * * @param value A reference to a theme */ export function unuseTheme(value) { $array.remove(registry.themes, value); } /** * Removes all "active" themes. Any charts created subsequently will not have * any theme applied to them. */ export function unuseAllThemes() { registry.themes = []; } /** * Adds a license, e.g.: * * ```TypeScript * am4core.addLicense("xxxxxxxx"); * ``` * ```JavaScript * am4core.addLicense("xxxxxxxx"); * ``` * * Multiple licenses can be added to cover for multiple products. * * @since 4.5.16 * @param license License key */ export function addLicense(license) { options.licenses.push(license); } //# sourceMappingURL=Instance.js.map