@amcharts/amcharts5
Version:
amCharts 5
1,632 lines • 53.3 kB
JavaScript
import { Disposer } from "./Disposer";
import { EventDispatcher } from "./EventDispatcher";
import { AnimationState, getInterpolate } from "./Animation";
import { States } from "./States";
import { registry } from "../Registry";
import * as $object from "./Object";
import * as $ease from "./Ease";
import * as $array from "./Array";
import * as $order from "./Order";
/**
* Allows to dynamically modify setting value of its target element.
*
* @see {@link https://www.amcharts.com/docs/v5/concepts/settings/adapters/} for more info
*/
export class Adapters {
constructor(entity) {
this._callbacks = {};
this._disabled = {};
this._entity = entity;
}
/**
* Add a function (`callback`) that will modify value for setting `key`.
*
* @see {@link https://www.amcharts.com/docs/v5/concepts/settings/adapters/} for more info
*/
add(key, callback) {
let callbacks = this._callbacks[key];
if (callbacks === undefined) {
callbacks = this._callbacks[key] = [];
}
callbacks.push(callback);
this._entity._markDirtyKey(key);
return new Disposer(() => {
if ($array.removeFirst(callbacks, callback)) {
this._entity._markDirtyKey(key);
}
});
}
/**
* Removes all adapters for the specific key.
*
* @since 5.1.0
*/
remove(key) {
const callbacks = this._callbacks[key];
if (callbacks !== undefined) {
delete this._callbacks[key];
if (callbacks.length !== 0) {
this._entity._markDirtyKey(key);
}
}
}
/**
* Enables (previously disabled) adapters for specific key.
*
* @since 5.1.0
*/
enable(key) {
if (this._disabled[key]) {
delete this._disabled[key];
this._entity._markDirtyKey(key);
}
}
/**
* Disables all adapters for specific key.
*
* @since 5.1.0
*/
disable(key) {
if (!this._disabled[key]) {
this._disabled[key] = true;
this._entity._markDirtyKey(key);
}
}
/**
* @ignore
*/
fold(key, value) {
if (!this._disabled[key]) {
const callbacks = this._callbacks[key];
if (callbacks !== undefined) {
for (let i = 0, len = callbacks.length; i < len; ++i) {
value = callbacks[i](value, this._entity, key);
}
}
}
return value;
}
}
/**
* Animation object.
*
* @see {@link https://www.amcharts.com/docs/v5/concepts/animations/} for more info
*/
export class Animation {
constructor(animation, from, to, duration, easing, loops, startingTime) {
this._time = 0;
this._stopped = false;
this._playing = true;
this.events = new EventDispatcher();
this._animation = animation;
this._from = from;
this._to = to;
this._duration = duration;
this._easing = easing;
this._loops = loops;
this._interpolate = getInterpolate(from, to);
this._oldTime = startingTime;
}
get to() {
return this._to;
}
get from() {
return this._from;
}
get playing() {
return this._playing;
}
get stopped() {
return this._stopped;
}
stop() {
if (!this._stopped) {
this._stopped = true;
this._playing = false;
if (this.events.isEnabled("stopped")) {
this.events.dispatch("stopped", {
type: "stopped",
target: this,
});
}
}
}
pause() {
this._playing = false;
this._oldTime = null;
}
play() {
if (!this._stopped && !this._playing) {
this._playing = true;
this._animation._startAnimation();
}
}
get percentage() {
return this._time / this._duration;
}
waitForStop() {
return new Promise((resolve, _reject) => {
if (this._stopped) {
resolve();
}
else {
const listener = () => {
stopped.dispose();
resolve();
};
const stopped = this.events.on("stopped", listener);
}
});
}
_checkEnded() {
if (this._loops > 1) {
--this._loops;
return false;
}
else {
return true;
}
}
_run(currentTime) {
if (this._oldTime !== null) {
this._time += currentTime - this._oldTime;
if (this._time > this._duration) {
this._time = this._duration;
}
}
this._oldTime = currentTime;
}
_reset(currentTime) {
this._oldTime = currentTime;
this._time = 0;
}
_value(diff) {
if (diff <= 0) {
return this._from;
}
const eased = this._easing(diff);
if (eased >= 0.999999) {
return this._to;
}
if (eased <= 0.000001) {
return this._from;
}
return this._interpolate(eased, this._from, this._to);
}
}
/**
* @ignore
*/
let counter = 0;
// How many elements are being built right now
let constructing = 0;
/**
* Base class for [[Entity]] objects that support Settings.
*
* @see {@link https://www.amcharts.com/docs/v5/concepts/settings/} for more info
*/
export class Settings {
constructor(settings) {
/**
* Unique ID.
*/
this.uid = ++counter;
this._privateSettings = {};
this._settingEvents = {};
this._privateSettingEvents = {};
this._debouncedSettingEvents = {};
this._debouncedPrivateSettingEvents = {};
this._prevSettings = {};
this._prevPrivateSettings = {};
this._animatingSettings = {};
this._animatingPrivateSettings = {};
this._disposed = false;
// TODO move this into Entity
this._userProperties = {};
// Settings the library worked out for itself. Anything written through
// `_setC` is flagged in `_userProperties` as well, so that everything which
// keys off it - themes, templates - behaves exactly as before, and only code
// that cares about provenance (serializing) tells them apart. Defaults
// (`_setDefault` and friends) are flagged here alone, since they never
// counted as user properties in the first place.
this._computedProperties = {};
/**
* If this is set to `false` then disposing does nothing, it's a no-op.
*/
this.enableDispose = true;
this._settings = settings;
}
_checkDirty() {
$object.keys(this._settings).forEach((key) => {
this._userProperties[key] = true;
this._markDirtyKey(key);
});
}
/**
* @ignore
*/
resetUserSettings() {
this._userProperties = {};
this._computedProperties = {};
}
_runAnimation(currentTime) {
let state = AnimationState.Stopped;
if (!this.isDisposed()) {
let playing = false;
let paused = false;
$object.each(this._animatingSettings, (key, animation) => {
if (animation.stopped) {
this._stopAnimation(key);
}
else if (animation.playing) {
animation._run(currentTime);
const diff = animation.percentage;
if (diff >= 1) {
if (animation._checkEnded()) {
this._setAnimated(key, animation._value(1));
}
else {
playing = true;
animation._reset(currentTime);
this._set(key, animation._value(1));
}
}
else {
playing = true;
this._set(key, animation._value(diff));
}
}
else {
paused = true;
}
});
$object.each(this._animatingPrivateSettings, (key, animation) => {
if (animation.stopped) {
this._stopAnimationPrivate(key);
}
else if (animation.playing) {
animation._run(currentTime);
const diff = animation.percentage;
if (diff >= 1) {
if (animation._checkEnded()) {
this.setPrivate(key, animation._value(1));
}
else {
playing = true;
animation._reset(currentTime);
this._setPrivate(key, animation._value(1));
}
}
else {
playing = true;
this._setPrivate(key, animation._value(diff));
}
}
else {
paused = true;
}
});
if (playing) {
state = AnimationState.Playing;
}
else if (paused) {
state = AnimationState.Paused;
}
}
return state;
}
_markDirtyKey(_key) {
this.markDirty();
}
_markDirtyPrivateKey(_key) {
this.markDirty();
}
/**
* Sets a callback function to invoke when specific key of settings changes
* or is set.
*
* @see {@link https://www.amcharts.com/docs/v5/concepts/events/#Settings_value_change} for more info
* @param key Settings key
* @param callback Callback
* @return Disposer for event
*/
on(key, callback) {
let events = this._settingEvents[key];
if (events === undefined) {
events = this._settingEvents[key] = [];
}
events.push(callback);
return new Disposer(() => {
$array.removeFirst(events, callback);
if (events.length === 0) {
delete this._settingEvents[key];
}
});
}
/**
* Removes a callback for when value of a setting changes.
*
* @see {@link https://www.amcharts.com/docs/v5/concepts/events/#Settings_value_change} for more info
* @param key Private settings key
* @param callback Callback
* @since 5.9.2
*/
off(key, callback) {
let events = this._settingEvents[key];
if (events !== undefined && callback !== undefined) {
$array.removeFirst(events, callback);
}
else {
delete this._settingEvents[key];
}
}
/**
* Sets a callback function to invoke the first time specific key of settings
* changes or is set. The callback is automatically removed after it fires once.
*
* @see {@link https://www.amcharts.com/docs/v5/concepts/events/#Settings_value_change} for more info
* @param key Settings key
* @param callback Callback
* @return Disposer for event
* @since 5.20.4
*/
once(key, callback) {
const disposer = this.on(key, (value, target, key) => {
disposer.dispose();
callback(value, target, key);
});
return disposer;
}
_onDebouncedHelper(map, key, callback, delay) {
let events = map[key];
if (events === undefined) {
events = map[key] = [];
}
const entry = { cb: callback, delay };
events.push(entry);
return new Disposer(() => {
if (entry.timeout) {
window.clearTimeout(entry.timeout);
}
$array.removeFirst(events, entry);
if (events.length === 0) {
delete map[key];
}
});
}
_offDebouncedHelper(map, key, callback) {
const events = map[key];
if (events !== undefined) {
if (callback !== undefined) {
const index = $array.findIndex(events, (e) => e.cb === callback);
if (index !== -1) {
const entry = events[index];
if (entry.timeout) {
window.clearTimeout(entry.timeout);
}
events.splice(index, 1);
if (events.length === 0) {
delete map[key];
}
}
}
else {
$array.each(events, (entry) => {
if (entry.timeout) {
window.clearTimeout(entry.timeout);
}
});
delete map[key];
}
}
}
/**
* Sets a debounced callback function to invoke when specific key of settings
* changes or is set. The callback fires only once even if the setting is changed
* multiple times within the debounce delay.
*
* @see {@link https://www.amcharts.com/docs/v5/concepts/events/#Settings_value_change} for more info
* @param key Settings key
* @param callback Callback
* @param debounceDelay Debounce delay in milliseconds
* @return Disposer for event
*/
onDebounced(key, callback, debounceDelay) {
return this._onDebouncedHelper(this._debouncedSettingEvents, key, callback, debounceDelay);
}
/**
* Removes a debounced callback for when value of a setting changes.
*
* @see {@link https://www.amcharts.com/docs/v5/concepts/events/#Settings_value_change} for more info
* @param key Settings key
* @param callback Callback
*/
offDebounced(key, callback) {
this._offDebouncedHelper(this._debouncedSettingEvents, key, callback);
}
/**
* Sets a debounced callback function to invoke the first time specific key of
* settings changes or is set. The callback fires only once, after which it is
* automatically removed.
*
* @see {@link https://www.amcharts.com/docs/v5/concepts/events/#Settings_value_change} for more info
* @param key Settings key
* @param callback Callback
* @param debounceDelay Debounce delay in milliseconds
* @return Disposer for event
* @since 5.20.4
*/
onceDebounced(key, callback, debounceDelay) {
const disposer = this.onDebounced(key, (value, target, key) => {
disposer.dispose();
callback(value, target, key);
}, debounceDelay);
return disposer;
}
/**
* Sets a callback function to invoke when specific key of private settings
* changes or is set.
*
* @see {@link https://www.amcharts.com/docs/v5/concepts/events/#Settings_value_change} for more info
* @param key Private settings key
* @param callback Callback
* @return Disposer for event
*/
onPrivate(key, callback) {
let events = this._privateSettingEvents[key];
if (events === undefined) {
events = this._privateSettingEvents[key] = [];
}
events.push(callback);
return new Disposer(() => {
$array.removeFirst(events, callback);
if (events.length === 0) {
delete this._privateSettingEvents[key];
}
});
}
/**
* Removes a callback for when value of a private setting changes.
*
* @see {@link https://www.amcharts.com/docs/v5/concepts/events/#Settings_value_change} for more info
* @param key Private settings key
* @param callback Callback
* @since 5.9.2
*/
offPrivate(key, callback) {
let events = this._privateSettingEvents[key];
if (events !== undefined && callback !== undefined) {
$array.removeFirst(events, callback);
}
else {
delete this._privateSettingEvents[key];
}
}
/**
* Sets a debounced callback function to invoke when specific key of private
* settings changes or is set. The callback fires only once even if the setting
* is changed multiple times within the debounce delay.
*
* @see {@link https://www.amcharts.com/docs/v5/concepts/events/#Settings_value_change} for more info
* @param key Private settings key
* @param callback Callback
* @param debounceDelay Debounce delay in milliseconds
* @return Disposer for event
*/
onPrivateDebounced(key, callback, debounceDelay) {
return this._onDebouncedHelper(this._debouncedPrivateSettingEvents, key, callback, debounceDelay);
}
/**
* Removes a debounced callback for when value of a private setting changes.
*
* @see {@link https://www.amcharts.com/docs/v5/concepts/events/#Settings_value_change} for more info
* @param key Private settings key
* @param callback Callback
*/
offDebouncedPrivate(key, callback) {
this._offDebouncedHelper(this._debouncedPrivateSettingEvents, key, callback);
}
/**
* @ignore
*/
getRaw(key, fallback) {
const value = this._settings[key];
if (value !== undefined) {
return value;
}
else {
return fallback;
}
}
/**
* Returns `true` if the setting exists.
*
* @see {@link https://www.amcharts.com/docs/v5/concepts/settings/} for more info
* @param key Settings key
* @return {boolean} Key exists
*/
has(key) {
return key in this._settings;
}
get(key, fallback) {
return this.getRaw(key, fallback);
}
_sendKeyEvent(key, value) {
const events = this._settingEvents[key];
if (events !== undefined) {
// Iterate over a copy so a callback that removes itself (e.g. `once`)
// does not corrupt the loop.
$array.each(events.slice(), (callback) => {
callback(value, this, key);
});
}
const debouncedEvents = this._debouncedSettingEvents[key];
if (debouncedEvents !== undefined) {
$array.each(debouncedEvents, (entry) => {
if (entry.timeout) {
window.clearTimeout(entry.timeout);
}
entry.timeout = window.setTimeout(() => {
entry.timeout = undefined;
entry.cb(value, this, key);
}, entry.delay);
});
}
}
_sendPrivateKeyEvent(key, value) {
const events = this._privateSettingEvents[key];
if (events !== undefined) {
// Iterate over a copy so a callback that removes itself (e.g. `once`)
// does not corrupt the loop.
$array.each(events.slice(), (callback) => {
callback(value, this, key);
});
}
const debouncedEvents = this._debouncedPrivateSettingEvents[key];
if (debouncedEvents !== undefined) {
$array.each(debouncedEvents, (entry) => {
if (entry.timeout) {
window.clearTimeout(entry.timeout);
}
entry.timeout = window.setTimeout(() => {
entry.timeout = undefined;
entry.cb(value, this, key);
}, entry.delay);
});
}
}
/**
* @ignore
*/
_setRaw(key, old, value) {
this._prevSettings[key] = old;
this._sendKeyEvent(key, value);
}
/**
* @ignore
*/
setRaw(key, value) {
const old = this._settings[key];
this._settings[key] = value;
if (old !== value) {
this._setRaw(key, old, value);
}
}
/**
* @ignore
*/
_set(key, value) {
const old = this._settings[key];
this._settings[key] = value;
if (old !== value) {
this._setRaw(key, old, value);
this._markDirtyKey(key);
}
}
_stopAnimation(key) {
const animation = this._animatingSettings[key];
if (animation) {
delete this._animatingSettings[key];
animation.stop();
}
}
/**
* Sets a setting `value` for the specified `key`, and returns the same `value`.
*
* @see {@link https://www.amcharts.com/docs/v5/concepts/settings/} for more info
* @param key Setting key
* @param value Setting value
* @return Setting value
*/
set(key, value) {
this._set(key, value);
this._stopAnimation(key);
return value;
}
/**
* Removes a setting value for the specified `key`;
*
* @see {@link https://www.amcharts.com/docs/v5/concepts/settings/} for more info
* @param key Setting key
*/
remove(key) {
if (key in this._settings) {
this._prevSettings[key] = this._settings[key];
delete this._settings[key];
this._sendKeyEvent(key, undefined);
this._markDirtyKey(key);
}
this._stopAnimation(key);
}
/**
* Removes all keys;
*
* @see {@link https://www.amcharts.com/docs/v5/concepts/settings/} for more info
*/
removeAll() {
$array.each($object.keys(this._settings), (key) => {
this.remove(key);
});
}
/**
* Returns a value of a private setting.
*
* @see {@link https://www.amcharts.com/docs/v5/concepts/settings/#Private_settings} for more info
*/
getPrivate(key, fallback) {
const value = this._privateSettings[key];
if (value !== undefined) {
return value;
}
else {
return fallback;
}
}
/**
* @ignore
*/
_setPrivateRaw(key, old, value) {
this._prevPrivateSettings[key] = old;
this._sendPrivateKeyEvent(key, value);
}
/**
* @ignore
*/
setPrivateRaw(key, value) {
const old = this._privateSettings[key];
this._privateSettings[key] = value;
if (old !== value) {
this._setPrivateRaw(key, old, value);
}
}
/**
* @ignore
*/
_setPrivate(key, value) {
const old = this._privateSettings[key];
this._privateSettings[key] = value;
if (old !== value) {
this._setPrivateRaw(key, old, value);
this._markDirtyPrivateKey(key);
}
}
_stopAnimationPrivate(key) {
const animation = this._animatingPrivateSettings[key];
if (animation) {
animation.stop();
delete this._animatingPrivateSettings[key];
}
}
/**
* @ignore
*/
setPrivate(key, value) {
this._setPrivate(key, value);
this._stopAnimationPrivate(key);
return value;
}
/**
* @ignore
*/
removePrivate(key) {
if (key in this._privateSettings) {
this._prevPrivateSettings[key] = this._privateSettings[key];
delete this._privateSettings[key];
this._markDirtyPrivateKey(key);
}
this._stopAnimationPrivate(key);
}
/**
* Sets multiple settings at once.
*
* `settings` must be an object with key: value pairs.
*
* @see {@link https://www.amcharts.com/docs/v5/concepts/settings/} for more info
* @param settings Settings
*/
setAll(settings) {
$object.each(settings, (key, value) => {
this.set(key, value);
});
}
/**
* Animates setting values from current/start values to new ones.
*
* @see {@link https://www.amcharts.com/docs/v5/concepts/animations/#Animating_settings} for more info
* @param options Animation options
* @return Animation object
*/
/**
* Writes a value an animation arrived at.
*
* An animation carries provenance along rather than creating it: a setting
* that was asked for stays that way, and one that was computed - or never set
* at all - does not become a choice just because something animated to it.
*/
_setAnimated(key, value) {
const authored = this._userProperties[key] && !this._computedProperties[key];
this.set(key, value);
if (!authored) {
this._computedProperties[key] = true;
}
}
animate(options) {
const key = options.key;
const to = options.to;
const duration = options.duration || 0;
const loops = options.loops || 1;
const from = (options.from === undefined ? this.get(key) : options.from);
const easing = (options.easing === undefined ? $ease.linear : options.easing);
if (duration === 0) {
this._setAnimated(key, to);
}
else {
if (from === undefined || from === to) {
this._setAnimated(key, to);
}
else {
this._setAnimated(key, from);
const animation = this._animatingSettings[key] = new Animation(this, from, to, duration, easing, loops, this._animationTime());
this._startAnimation();
return animation;
}
}
const animation = new Animation(this, from, to, duration, easing, loops, null);
animation.stop();
return animation;
}
/**
* @ignore
*/
animatePrivate(options) {
const key = options.key;
const to = options.to;
const duration = options.duration || 0;
const loops = options.loops || 1;
const from = (options.from === undefined ? this.getPrivate(key) : options.from);
const easing = (options.easing === undefined ? $ease.linear : options.easing);
if (duration === 0) {
this.setPrivate(key, to);
}
else {
if (from === undefined || from === to) {
this.setPrivate(key, to);
}
else {
this.setPrivate(key, from);
const animation = this._animatingPrivateSettings[key] = new Animation(this, from, to, duration, easing, loops, this._animationTime());
this._startAnimation();
return animation;
}
}
const animation = new Animation(this, from, to, duration, easing, loops, null);
animation.stop();
return animation;
}
_dispose() { }
/**
* Returns `true` if this element is disposed.
*
* @return Disposed
*/
isDisposed() {
return this._disposed;
}
/**
* Disposes this object.
*/
dispose() {
if (this.enableDispose && !this._disposed) {
this._disposed = true;
this._dispose();
}
}
}
/**
* Base class.
*
* @important
*/
export class Entity extends Settings {
/**
* IMPORTANT! Do not instantiate this class via `new Class()` syntax.
*
* Use static method `Class.new()` instead.
*
* @see {@link https://www.amcharts.com/docs/v5/getting-started/#New_element_syntax} for more info
* @ignore
*/
constructor(root, settings, isReal, templates = []) {
super(settings);
this.states = new States(this);
this.adapters = new Adapters(this);
this.events = this._createEvents();
this._userPrivateProperties = {};
this._dirty = {};
this._dirtyPrivate = {};
// Templates for the themes
this._templates = [];
// Default themes which can be overridden by the user's themes
this._defaultThemes = [];
// Disposers for all of the templates
this._templateDisposers = [];
this._disposers = [];
// Whether the template setup function should be run
this._runSetup = true;
this._disposerProperties = {};
if (!isReal) {
throw new Error("You cannot use `new Class()`, instead use `Class.new()`");
}
this._root = root;
this._internalTemplates = templates;
if (settings.id) {
this._registerId(settings.id);
}
}
/**
* Use this method to create an instance of this class.
*
* @see {@link https://www.amcharts.com/docs/v5/getting-started/#New_element_syntax} for more info
* @param root Root element
* @param settings Settings
* @param template Template
* @return Instantiated object
*/
static new(root, settings, template) {
// Tags on an element made while another one is being built are the library's
const userTags = constructing === 0 && settings.themeTags ? settings.themeTags.slice() : undefined;
constructing++;
try {
const x = (new this(root, settings, true));
x._userThemeTags = userTags;
x._template = template;
x._afterNew();
return x;
}
finally {
constructing--;
}
}
static _new(root, settings, templates = []) {
constructing++;
try {
const x = (new this(root, settings, true, templates));
x._afterNew();
return x;
}
finally {
constructing--;
}
}
_afterNew() {
this._checkDirty();
let shouldApply = false;
const template = this._template;
if (template) {
shouldApply = true;
template._setObjectTemplate(this);
}
$array.each(this._internalTemplates, (template) => {
shouldApply = true;
template._setObjectTemplate(this);
});
if (shouldApply) {
this._applyTemplates(false);
}
this.states.create("default", {});
this._setDefaults();
}
// This is the same as _afterNew, except it also applies the themes.
// This should only be used for classes which don't have a parent (because they extend from Entity and not Sprite).
_afterNewApplyThemes() {
this._checkDirty();
const template = this._template;
if (template) {
template._setObjectTemplate(this);
}
$array.each(this._internalTemplates, (template) => {
template._setObjectTemplate(this);
});
this.states.create("default", {});
this._setDefaults();
this._applyThemes();
}
_createEvents() {
return new EventDispatcher();
}
/**
* @ignore
*/
get classNames() {
return this.constructor.classNames;
}
/**
* @ignore
*/
get className() {
return this.constructor.className;
}
_setDefaults() {
}
_setDefaultFn(key, f) {
const value = this.get(key);
if (value) {
return value;
}
else {
const value = f();
this._setC(key, value);
return value;
}
}
_setDefault(key, value) {
if (!this.has(key)) {
super.set(key, value);
// a default only lands when nothing was asked for, so it is the
// library's own value - see `_setC`
this._computedProperties[key] = true;
}
}
_setRawDefault(key, value) {
if (!this.has(key)) {
super.setRaw(key, value);
this._computedProperties[key] = true;
}
}
_clearDirty() {
$object.keys(this._dirty).forEach((key) => {
this._dirty[key] = false;
});
$object.keys(this._dirtyPrivate).forEach((key) => {
this._dirtyPrivate[key] = false;
});
}
/**
* @ignore
*/
isDirty(key) {
return !!this._dirty[key];
}
/**
* @ignore
*/
isPrivateDirty(key) {
return !!this._dirtyPrivate[key];
}
_markDirtyKey(key) {
this._dirty[key] = true;
super._markDirtyKey(key);
}
_markDirtyPrivateKey(key) {
this._dirtyPrivate[key] = true;
super._markDirtyKey(key);
}
/**
* Checks if element is of certain class (or inherits one).
*
* @param type Class name to check
* @return {boolean} Is of class?
*/
isType(type) {
return this.classNames.indexOf(type) !== -1;
}
_pushPropertyDisposer(key, disposer) {
let disposers = this._disposerProperties[key];
if (disposers === undefined) {
disposers = this._disposerProperties[key] = [];
}
disposers.push(disposer);
return disposer;
}
_disposeProperty(key) {
const disposers = this._disposerProperties[key];
if (disposers !== undefined) {
$array.each(disposers, (disposer) => {
disposer.dispose();
});
delete this._disposerProperties[key];
}
}
/**
* @todo needs description
* @param value Template
*/
set template(value) {
const template = this._template;
if (template !== value) {
this._template = value;
if (template) {
template._removeObjectTemplate(this);
}
if (value) {
value._setObjectTemplate(this);
}
this._applyTemplates();
}
}
get template() {
return this._template;
}
/**
* @ignore
*/
markDirty() {
this._root._addDirtyEntity(this);
}
_startAnimation() {
this._root._addAnimation(this);
}
_animationTime() {
return this._root.animationTime;
}
_applyState(_name) { }
_applyStateAnimated(_name, _duration) { }
get(key, fallback) {
const value = this.adapters.fold(key, this._settings[key]);
if (value !== undefined) {
return value;
}
else {
return fallback;
}
}
/**
* @ignore
*/
isUserSetting(key) {
return this._userProperties[key] || false;
}
/**
* Sets a setting `value` for the specified `key`, and returns the same `value`.
*
* @see {@link https://www.amcharts.com/docs/v5/concepts/settings/} for more info
* @param key Setting key
* @param value Setting value
* @return Setting value
*/
set(key, value) {
this._userProperties[key] = true;
delete this._computedProperties[key];
return super.set(key, value);
}
/**
* Same as `set()`, but for a value the library works out itself rather than
* one that was asked for - a size measured during layout, a color derived
* from data, and so on.
*
* Behaves identically in every way that affects the chart; the value is only
* additionally flagged as computed, so that it can be left out when the chart
* is serialized.
*
* @ignore
* @param key Setting key
* @param value Setting value
* @return Setting value
*/
_setC(key, value) {
const authored = this._userProperties[key] && !this._computedProperties[key] && this._settings[key] === value;
const result = this.set(key, value);
if (!authored) {
this._computedProperties[key] = true;
}
return result;
}
/**
* Same as `setRaw()`, but for a value the library works out itself.
*
* Kept separate from `_setC()` because `setRaw()` deliberately writes without
* the side effects of `set()` - no animation is stopped and no change is
* announced - and callers that reach for it are relying on that.
*
* @ignore
* @param key Setting key
* @param value Setting value
*/
_setCRaw(key, value) {
const authored = this._userProperties[key] && !this._computedProperties[key] && this._settings[key] === value;
this.setRaw(key, value);
if (!authored) {
this._computedProperties[key] = true;
}
}
/**
* Flags already-set settings as computed, without writing anything.
*
* For settings passed to `new()` by the library itself, where re-setting them
* just to flag them would announce a change that never happened.
*
* @ignore
* @param keys Setting keys
* @return Itself
*/
_markC(...keys) {
$array.each(keys, (key) => {
this._computedProperties[key] = true;
});
return this;
}
/**
* Sets multiple computed settings at once.
*
* @ignore
* @param settings Settings
*/
_setCAll(settings) {
$object.each(settings, (key, value) => {
this._setC(key, value);
});
}
/**
* Returns `true` if the setting holds a value the library worked out itself.
*
* @ignore
* @param key Setting key
* @return Computed?
*/
isComputedSetting(key) {
return this._computedProperties[key] || false;
}
/**
* @ignore
*/
setRaw(key, value) {
this._userProperties[key] = true;
delete this._computedProperties[key];
super.setRaw(key, value);
}
/**
* Sets a setting `value` for the specified `key` only if the value for this key was not set previously using set method, and returns the same `value`.
*
* @see {@link https://www.amcharts.com/docs/v5/concepts/settings/} for more info
* @param key Setting key
* @param value Setting value
* @return Setting value
*/
_setSoft(key, value) {
if (!this._userProperties[key]) {
return super.set(key, value);
}
return value;
}
/**
* Removes a setting value for the specified `key`.
*
* @see {@link https://www.amcharts.com/docs/v5/concepts/settings/} for more info
* @param key Setting key
*/
remove(key) {
delete this._userProperties[key];
delete this._computedProperties[key];
this._removeTemplateProperty(key);
}
/**
* @ignore
*/
setPrivate(key, value) {
this._userPrivateProperties[key] = true;
return super.setPrivate(key, value);
}
/**
* @ignore
*/
setPrivateRaw(key, value) {
this._userPrivateProperties[key] = true;
super.setPrivateRaw(key, value);
}
/**
* @ignore
*/
removePrivate(key) {
delete this._userPrivateProperties[key];
this._removeTemplatePrivateProperty(key);
}
_setTemplateProperty(template, key, value) {
if (!this._userProperties[key]) {
const match = this._findTemplateByKey(key);
if (template === match) {
super.set(key, value);
}
}
}
_setTemplatePrivateProperty(template, key, value) {
if (!this._userPrivateProperties[key]) {
const match = this._findTemplateByPrivateKey(key);
if (template === match) {
super.setPrivate(key, value);
}
}
}
_removeTemplateProperty(key) {
if (!this._userProperties[key]) {
const match = this._findTemplateByKey(key);
if (match) {
// TODO don't stop the animation if the property didn't change
super.set(key, match._settings[key]);
}
else {
super.remove(key);
}
}
}
_removeTemplatePrivateProperty(key) {
if (!this._userPrivateProperties[key]) {
const match = this._findTemplateByPrivateKey(key);
if (match) {
// TODO don't stop the animation if the property didn't change
super.setPrivate(key, match._privateSettings[key]);
}
else {
super.removePrivate(key);
}
}
}
_walkParents(f) {
f(this._root._rootContainer);
f(this);
}
// TODO faster version of this method which is specialized to just 1 key
_applyStateByKey(name) {
const other = this.states.create(name, {});
const seen = {};
this._eachTemplate((template) => {
const state = template.states.lookup(name);
if (state) {
state._apply(other, seen);
}
});
$object.each(other._settings, (key) => {
if (!seen[key] && !other._userSettings[key]) {
other.remove(key);
}
});
}
_applyTemplate(template, state) {
this._templateDisposers.push(template._apply(this, state));
$object.each(template._settings, (key, value) => {
if (!state.settings[key] && !this._userProperties[key]) {
state.settings[key] = true;
super.set(key, value);
}
});
$object.each(template._privateSettings, (key, value) => {
if (!state.privateSettings[key] && !this._userPrivateProperties[key]) {
state.privateSettings[key] = true;
super.setPrivate(key, value);
}
});
if (this._runSetup && template.setup) {
this._runSetup = false;
template.setup(this);
}
}
/**
* Calls the closure with each template and returns the first template which is true
*/
_findStaticTemplate(f) {
if (this._template) {
if (f(this._template)) {
return this._template;
}
}
}
_eachTemplate(f) {
this._findStaticTemplate((template) => {
f(template);
return false;
});
// _internalTemplates is sorted with most specific to the right
$array.eachReverse(this._internalTemplates, f);
// _templates is sorted with most specific to the left
$array.each(this._templates, f);
}
_applyTemplates(remove = true) {
if (remove) {
this._disposeTemplates();
}
const state = {
settings: {},
privateSettings: {},
states: {},
};
this._eachTemplate((template) => {
this._applyTemplate(template, state);
});
if (remove) {
$object.each(this._settings, (key) => {
if (!this._userProperties[key] && !state.settings[key]) {
super.remove(key);
}
});
$object.each(this._privateSettings, (key) => {
if (!this._userPrivateProperties[key] && !state.privateSettings[key]) {
super.removePrivate(key);
}
});
}
}
_findTemplate(f) {
const value = this._findStaticTemplate(f);
if (value === undefined) {
// _internalTemplates is sorted with most specific to the right
const value = $array.findReverse(this._internalTemplates, f);
if (value === undefined) {
// _templates is sorted with most specific to the left
return $array.find(this._templates, f);
}
else {
return value;
}
}
else {
return value;
}
}
_findTemplateByKey(key) {
return this._findTemplate((template) => {
return key in template._settings;
});
}
_findTemplateByPrivateKey(key) {
return this._findTemplate((template) => {
return key in template._privateSettings;
});
}
_disposeTemplates() {
$array.each(this._templateDisposers, (disposer) => {
disposer.dispose();
});
this._templateDisposers.length = 0;
}
_removeTemplates() {
$array.each(this._templates, (template) => {
template._removeObjectTemplate(this);
});
this._templates.length = 0;
}
_applyThemes(force = false) {
if (this.get("ignoreThemes")) {
return false;
}
let isConnected = false;
const defaults = [];
let themes = [];
const themeTags = new Set();
const tags = this.get("themeTagsSelf");
if (tags) {
$array.each(tags, (tag) => {
themeTags.add(tag);
});
}
this._walkParents((entity) => {
if (entity === this._root._rootContainer) {
isConnected = true;
}
if (entity._defaultThemes.length > 0) {
defaults.push(entity._defaultThemes);
}
const theme = entity.get("themes");
if (theme) {
themes.push(theme);
}
const tags = entity.get("themeTags");
if (tags) {
$array.each(tags, (tag) => {
themeTags.add(tag);
});
}
});
themes = defaults.concat(themes);
this._removeTemplates();
if (isConnected || force) {
$array.eachReverse(this.classNames, (name) => {
const allRules = [];
$array.each(themes, (themes) => {
$array.each(themes, (theme) => {
const rules = theme._lookupRules(name);
if (rules) {
$array.eachReverse(rules, (rule) => {
const matches = rule.tags.every((tag) => {
return themeTags.has(tag);
});
if (matches) {
const result = $array.getFirstSortedIndex(allRules, (x) => {
const order = $order.compare(rule.tags.length, x.tags.length);
if (order === 0) {
return $order.compareArray(rule.tags, x.tags, $order.compare);
}
else {
return order;
}
});
allRules.splice(result.index, 0, rule);
}
});
}
});
});
$array.each(allRules, (rule) => {
this._templates.push(rule.template);
rule.template._setObjectTemplate(this);
});
});
}
this._applyTemplates();
if (isConnected || force) {
// This causes it to only run the setup function the first time that the themes are applied
this._runSetup = false;
}
return isConnected || force;
}
_changed() { }
_beforeChanged() {
if (this.isDirty("id")) {
const id = this.get("id");
if (id) {
this._registerId(id);
}
const prevId = this._prevSettings.id;
if (prevId) {
delete this._root.entitiesById[prevId];
delete registry.entitiesById[prevId];
}
}
}
_registerId(id) {
if (this._root.entitiesById[id] && this._root.entitiesById[id] !== this) {
throw new Error("An entity with id \"" + id + "\" already exists.");
}
this._root.entitiesById[id] = this;
registry.entitiesById[id] = this;
}
_afterChanged() { }
/**
* @ignore
*/
addDisposer(disposer) {
this._disposers.push(disposer);
return disposer;
}
_dispose() {
super._dispose();
const template = this._template;
if (template) {
template._removeObjectTemplate(this);
}
$array.each(this._internalTemplates, (template) => {
template._removeObjectTemplate(this);
});
this._removeTemplates();
this._disposeTemplates();
this.events.dispose();
this._disposers.forEach((x) => {
x.dispose();
});
$object.each(this._disposerProperties, (_, disposers) => {
$array.each(disposers, (disposer) => {
disposer.dispose();
});
});
const id = this.get("id");
if (id) {
delete this._root.entitiesById[id];
delete registry.entitiesById[id];
}
}
/**
* Creates and returns a "disposable" timeout.
*
* @param fn Callback
* @param delay Delay in milliseconds
* @return Timeout disposer
*/
setTimeout(fn, delay) {
const id = setTimeout(() => {
this.removeDispose(disposer);
fn();
}, delay);
const disposer = new Disposer(() => {
clearTimeout(id);
});
this._disposers.push(disposer);
return disposer;
}
/**
* @ignore
*/
removeDispose(target) {
if (!this.isDisposed()) {
let index = $array.indexOf(this._disposers, target);
if (index > -1) {
this._disposers.splice(index, 1);
}
}
target.dispose();
}
/**
* @ignore
*/
hasTag(tag) {
return $array.indexOf(this.get("themeTags", []), tag) !== -1;
}
/**
* @ignore
*/
addTag(tag) {
if (!this.hasTag(tag)) {
const tags = this.get("themeTags", []);
tags.push(tag);
this.set("themeTags", tags);
}
}
/**
* Tags given to the element that are still set.
*
* @ignore
*/
_getUserThemeTags() {
const userTags = this._userThemeTags;
if (!userTags) {
return [];
}
const tags = this.get("themeTags", []);
return userTags.filter((tag) => tags.indexOf(tag) !== -1);
}
/**
* @ignore
*/
_addUserThemeTags(tags) {
const userTags = this._userThemeTags || [];
$array.each(tags, (tag) => {
if (userTags.indexOf(tag) === -1) {
userTags.push(tag);
}
});
this._userThemeTags = userTags;
}
/**
* @ignore
*/
removeTag(tag) {
if (this.hasTag(tag)) {
const tags = this.get("themeTags", []);
$array.remove(tags, tag);
this.set("themeTags", tags);
}
}
_t(text, locale, ...rest) {
return this._root.language.translate(text, locale, ...rest);
}
/**
* An instance of [[Root]] object.
*
* @readonly
* @since 5.0.6
* @return Root object
*/
get root() {
return this._root;
}
}
Entity.className = "Entity";
Entity.classNames = ["Entity"];
//# sourceMappingURL=Entity.js.map