UNPKG

@amcharts/amcharts4

Version:
1,219 lines (1,217 loc) 59.6 kB
/** * Functionality for any series-based elements, like Line Series (graphs), * Pie slice lists, etc. */ import { __extends } from "tslib"; /** * ============================================================================ * IMPORTS * ============================================================================ * @hidden */ import { Component } from "../../core/Component"; import { Sprite } from "../../core/Sprite"; import { List, ListTemplate, ListDisposer } from "../../core/utils/List"; import { Dictionary, DictionaryDisposer } from "../../core/utils/Dictionary"; import { DataItem } from "../../core/DataItem"; import { Container } from "../../core/Container"; import { Tooltip } from "../../core/elements/Tooltip"; import { Bullet } from "../elements/Bullet"; import { LegendSettings } from "../Legend"; import { options } from "../../core/Options"; import { Color } from "../../core/utils/Color"; import { registry } from "../../core/Registry"; import * as $iter from "../../core/utils/Iterator"; import * as $math from "../../core/utils/Math"; import * as $ease from "../../core/utils/Ease"; import * as $utils from "../../core/utils/Utils"; import * as $object from "../../core/utils/Object"; import * as $type from "../../core/utils/Type"; import * as $array from "../../core/utils/Array"; import * as $colors from "../../core/utils/Colors"; /** * ============================================================================ * DATA ITEM * ============================================================================ * @hidden */ /** * Defines a [[DataItem]] for [[Series]]. * * @see {@link DataItem} */ var SeriesDataItem = /** @class */ (function (_super) { __extends(SeriesDataItem, _super); /** * Constructor */ function SeriesDataItem() { var _this = _super.call(this) || this; _this.className = "SeriesDataItem"; //@todo Should we make `bullets` list disposable? //this._disposers.push(new DictionaryDisposer(this.bullets)); _this.values.value = {}; _this.values.value = {}; _this.applyTheme(); return _this; } Object.defineProperty(SeriesDataItem.prototype, "bullets", { /** * A dictionary of data items bullets, where key is uid of a bullet template. */ get: function () { if (!this._bullets) { this._bullets = new Dictionary(); this._disposers.push(new DictionaryDisposer(this._bullets)); } return this._bullets; }, enumerable: true, configurable: true }); /** * Destroys this object and all related data. */ SeriesDataItem.prototype.dispose = function () { this.bullets.clear(); _super.prototype.dispose.call(this); }; Object.defineProperty(SeriesDataItem.prototype, "value", { /** * @return Value */ get: function () { return this.values.value.value; }, /** * data items's numeric value. * * @param value Value */ set: function (value) { this.setValue("value", value); }, enumerable: true, configurable: true }); return SeriesDataItem; }(DataItem)); export { SeriesDataItem }; /** * ============================================================================ * MAIN CLASS * ============================================================================ * @hidden */ /** * Defines base class for any kind of serial data. * * @see {@link ISeriesEvents} for a list of available Events * @see {@link ISeriesAdapters} for a list of available Adapters * @todo Separate axis-related stuff to some other class so that MapSeries would not have unrelated stuff */ var Series = /** @class */ (function (_super) { __extends(Series, _super); /** * Constructor */ function Series() { var _this = _super.call(this) || this; /** * Should this series excluded from the axis scale calculations? * * @default false */ _this._ignoreMinMax = false; /** * Should series' bullets? * * @default true */ _this._showBullets = true; /** * Settings for the appearance of the related legend items. */ _this.legendSettings = new LegendSettings(); /** * Lowest overal values by type. */ _this._tmin = new Dictionary(); /** * Highest overal values by type. */ _this._tmax = new Dictionary(); /** * Lowest values in current selection by type. */ _this._smin = new Dictionary(); /** * Highest values in current selection by type. */ _this._smax = new Dictionary(); /** * [dataItemsByAxis description] * * Both by category and date. * * @ignore Exclude from docs * @todo Description */ _this.dataItemsByAxis = new Dictionary(); /** * Normally series items are focusable using keyboard, so that people can * select them with a TAB key. However, if there are a lot of data points on * screen it might be long and useless to tab through all o fthem. * * This is where `skipFocusThreshold` comes in. If there are more items than * the value set here, we will not make those focusable and rather let screen * reader software rely on the series summary, or authors provide alternative * detailed information display, such as HTML table. * * Different series might have different threshold defaults. */ _this.skipFocusThreshold = 20; /** * Used to indicate if `itemReaderText` was changed "from the outside". */ _this._itemReaderTextChanged = false; /** * Most of the series use absolute values. However sometimes various * calculated percent values are need, e.g. item's percent representation * across all values in series, etc. * * It's a resource-intensive operation, so it is disabled by default. * * If you need percents to be calculated, e.g. for showing them in tooltips, * or creating 100% stacks, this setting needs to be set to `true`. * * NOTE: `PieChart`, which relies on slice percentages, has this * automatically set to `true`. * * @default false */ _this.calculatePercent = false; /** * When `calculatePercent` is enabled and data item's percent value is * calculated, last item's real value is used instead of its working value. * * This is done for the animations when last item in series (e.g. slice in * a `PieSeries`) is hidden or shown. (if we would use real value, the * calculated percent would always be 100%). * * Sometimes there is a need (e.g. for drill-down Sunburst) to disable this * hack by setting `usePercentHack` to `false`. * * @since 4.9.13 * @default true */ _this.usePercentHack = true; /** * Specifies if series should be automatically disposed when removing from * chart's `series` list. * * @default true */ _this.autoDispose = true; /** * When chart/series' data is processed, all kinds of derivative values are * calculated. E.g. sum, min, max, change, etc. This is a potentially * time-consuming operation, especially prominent in data-heavy charts. * * If your chart does not need those values, and you have a lot of data, * setting this to `true` might give a dramatic increase in initial chart * load speed. * * Please note, regular column and line series usage scenarios do not * require derivative values. Those come into play only when you do advanced * functionality like coloring segments of charts in different colors * depending on change between open and close values, have stacked series, or * display any of the derived values, like percent, in tooltips or bullets. * * @default false */ _this.simplifiedProcessing = false; if (_this.constructor === Series) { throw new Error("'Series' cannot be instantiated directly. Please use a specific series type."); } _this.className = "Series"; _this.isMeasured = false; _this.layout = "none"; _this.shouldClone = false; _this.setPropertyValue("hidden", false); _this.axisRanges = new List(); _this.axisRanges.events.on("inserted", _this.processAxisRange, _this, false); _this.minBulletDistance = 0; // otherwise we'll have a lot of cases when people won't see bullets and think it's a bug _this.mainContainer = _this.createChild(Container); _this.mainContainer.shouldClone = false; _this.mainContainer.mask = _this.createChild(Sprite); _this._disposers.push(_this.mainContainer); // all bullets should go on top of lines/fills. So we add a separate container for bullets and later set it's parent to chart.bulletsContainer var bulletsContainer = _this.mainContainer.createChild(Container); _this._shouldBeReady.push(bulletsContainer); bulletsContainer.shouldClone = false; bulletsContainer.layout = "none"; bulletsContainer.virtualParent = _this; _this._disposers.push(bulletsContainer); _this.bulletsContainer = bulletsContainer; _this.tooltip = new Tooltip(); _this.tooltip.virtualParent = _this; _this._disposers.push(_this.tooltip); _this.hiddenState.transitionEasing = $ease.cubicIn; // this data item holds sums, averages, etc _this.dataItem = _this.createDataItem(); _this._disposers.push(_this.dataItem); _this.dataItem.component = _this; // Apply accessibility _this.role = "group"; _this.applyTheme(); return _this; } /** * We need this here so that class names can be applied to bullets container. * * @ignore Exclude from docs */ Series.prototype.applyTheme = function () { _super.prototype.applyTheme.call(this); if (options.autoSetClassName && this.bulletsContainer) { this.bulletsContainer.className = this.className + "-bullets"; this.bulletsContainer.setClassName(); } }; /** * Sets defaults that instantiate some objects that rely on parent, so they * cannot be set in constructor. */ Series.prototype.applyInternalDefaults = function () { _super.prototype.applyInternalDefaults.call(this); if (!$type.hasValue(this.readerTitle)) { this.readerTitle = this.language.translate("Series"); } }; /** * Returns a new/empty DataItem of the type appropriate for this object. * * @see {@link DataItem} * @return Data Item */ Series.prototype.createDataItem = function () { return new SeriesDataItem(); }; Object.defineProperty(Series.prototype, "chart", { /** * @return Chart */ get: function () { return this._chart; }, /** * Chart series is used on. * * @param value Chart */ set: function (value) { this._chart = value; }, enumerable: true, configurable: true }); /** * Positions bullet. * * @param bullet Sprite */ Series.prototype.positionBullet = function (bullet) { // Placeholder method for extending classes to override. }; /** * Decorates newly created bullet after it has been instert into the list. * * @param event List event * @todo investigate why itemReaderText is undefined */ Series.prototype.processBullet = function (event) { var _this = this; var bullet = event.newValue; bullet.isTemplate = true; // Add accessibility options to bullet // If there are relatively few bullets, make them focusable this.events.once("datavalidated", function (ev) { if (_this.itemsFocusable()) { bullet.focusable = true; } }); this.invalidate(); }; /** * removes bullets * * @param event List event */ Series.prototype.removeBullet = function (event) { var bullet = event.oldValue; this.dataItems.each(function (dataItem) { var eachBullet = dataItem.bullets.getKey(bullet.uid); if (eachBullet) { eachBullet.dispose(); } }); this.invalidate(); }; /** * Validates data items. * * @ignore Exclude from docs */ Series.prototype.validateDataItems = function () { _super.prototype.validateDataItems.call(this); this.processValues(false); }; /** * Returns first value for the specific key in the series. * * @param key Key * @return Value * @todo Description * @todo Convert to propert object property iterator */ Series.prototype.getFirstValue = function (key, startIndex) { // find first /* return $iter.findMap(this.dataItems.iterator(), (dataItem) => { for (let key in dataItem.values) { if ($object.hasKey(dataItem.values, key)) { let value: number = dataItem.values[key].workingValue; if ($type.isNumber(value)) { return value; } } } return null; });*/ //if (startIndex > 0 && startIndex < this.dataItems.length - 1) { //startIndex++; //} for (var i = startIndex; i >= 0; i--) { var dataItem = this.dataItems.getIndex(i); var value = dataItem.getActualWorkingValue(key); if ($type.isNumber(value)) { return value; } } return null; }; /** * Returns first value for the specific key in the series. * * @param key Key * @return Value * @todo Description * @todo Convert to propert object property iterator */ Series.prototype.getAbsoluteFirstValue = function (key) { for (var i = 0; i < this.dataItems.length; i++) { var dataItem = this.dataItems.getIndex(i); var value = dataItem.values[key].value; if ($type.isNumber(value)) { return value; } } return null; }; /** * [rangeChangeUpdate description] * * @todo Description */ Series.prototype.rangeChangeUpdate = function () { _super.prototype.rangeChangeUpdate.call(this); this.processValues(true); }; /** * [processValues description] * * @todo Description * @todo Convert to propert object property iterator * @param dataItems [description] */ Series.prototype.processValues = function (working) { var _this = this; if (!this.simplifiedProcessing) { var dataItems = this.dataItems; var count_1 = {}; var sum_1 = {}; var absoluteSum_1 = {}; var low_1 = {}; var high_1 = {}; var open_1 = {}; var close_1 = {}; var previous_1 = {}; var first_1 = {}; var absoluteFirst_1 = {}; //let duration: number = 0; // todo: check if series uses selection.change or selection.change.percent and set duration to interpolationduration var startIndex_1 = $math.max(0, this.startIndex); startIndex_1 = $math.min(startIndex_1, this.dataItems.length); var endIndex = $math.min(this.endIndex, this.dataItems.length); if (!$type.isNumber(startIndex_1)) { startIndex_1 = 0; } if (!$type.isNumber(endIndex)) { endIndex = this.dataItems.length; } if (startIndex_1 > 0) { var dataItem_1 = dataItems.getIndex(startIndex_1 - 1); $object.each(dataItem_1.values, function (key, values) { var value = dataItem_1.getActualWorkingValue(key); if ($type.isNumber(value)) { // save previous previous_1[key] = value; } }); } var _loop_1 = function (i) { var dataItem_2 = dataItems.getIndex(i); $object.each(dataItem_2.values, function (key, values) { var value = dataItem_2.getActualWorkingValue(key); //if (i >= startIndex && i <= endIndex) { // do not add to count, sum etc if it is not within start/end index if ($type.isNumber(value)) { // count values if (!$type.isNumber(count_1[key])) { count_1[key] = 0; } count_1[key]++; // sum values if (!$type.isNumber(sum_1[key])) { sum_1[key] = 0; } sum_1[key] += value; // absolute sum values if (!$type.isNumber(absoluteSum_1[key])) { absoluteSum_1[key] = 0; } absoluteSum_1[key] += Math.abs(value); // open if (!$type.isNumber(open_1[key])) { open_1[key] = value; } // close close_1[key] = value; // low if (!$type.isNumber(low_1[key])) { low_1[key] = value; } else { if (low_1[key] > value) { low_1[key] = value; } } // high if (!$type.isNumber(high_1[key])) { high_1[key] = value; } else { if (high_1[key] < value) { high_1[key] = value; } } if (!$type.isNumber(first_1[key])) { first_1[key] = _this.getFirstValue(key, startIndex_1); } if (!$type.isNumber(absoluteFirst_1[key])) { absoluteFirst_1[key] = _this.getAbsoluteFirstValue(key); } // change dataItem_2.setCalculatedValue(key, value - first_1[key], "change"); // change from start percent // will fail if first value is 0 dataItem_2.setCalculatedValue(key, (value - first_1[key]) / first_1[key] * 100, "changePercent"); dataItem_2.setCalculatedValue(key, (value - absoluteFirst_1[key]), "startChange"); dataItem_2.setCalculatedValue(key, (value - absoluteFirst_1[key]) / absoluteFirst_1[key] * 100, "startChangePercent"); // previous change var prevValue = previous_1[key]; if (!$type.isNumber(prevValue)) { prevValue = value; } dataItem_2.setCalculatedValue(key, value - prevValue, "previousChange"); // previous change percent dataItem_2.setCalculatedValue(key, (value - prevValue) / prevValue * 100, "previousChangePercent"); // save previous previous_1[key] = value; } }); }; for (var i = startIndex_1; i < endIndex; i++) { _loop_1(i); } if (this.calculatePercent) { var _loop_2 = function (i) { var dataItem_3 = dataItems.getIndex(i); $object.each(dataItem_3.values, function (key) { var ksum = absoluteSum_1[key]; var value = dataItem_3.getActualWorkingValue(key); if ($type.isNumber(value)) { if (ksum > 0) { if (_this.usePercentHack) { // this hack is made in order to make it possible to animate single slice to 0 // if there is only one slice left, percent value is always 100%, so it won't animate // so we use real value of a slice instead of current value if (value == ksum) { ksum = dataItem_3.values[key].value; } } var percent = value / ksum * 100; dataItem_3.setCalculatedValue(key, percent, "percent"); } else { dataItem_3.setCalculatedValue(key, 0, "percent"); } } }); }; for (var i = startIndex_1; i < endIndex; i++) { _loop_2(i); } } // calculate one before first (cant do that in cycle, as we don't know open yet // when drawing line chart we should draw line to the invisible data point to the left, otherwise the line will always look like it starts from the selected point // so we do startIndex - 1 if (startIndex_1 > 0) { var zeroItem_1 = dataItems.getIndex(startIndex_1 - 1); $object.each(zeroItem_1.values, function (key) { var value = zeroItem_1.values[key].value; // change zeroItem_1.setCalculatedValue(key, value - open_1[key], "change"); // change percent zeroItem_1.setCalculatedValue(key, (value - open_1[key]) / open_1[key] * 100, "changePercent"); }); } // we save various data like sum, average to dataPoint of the series var dataItem_4 = this.dataItem; $object.each(dataItem_4.values, function (key) { dataItem_4.setCalculatedValue(key, sum_1[key], "sum"); dataItem_4.setCalculatedValue(key, absoluteSum_1[key], "absoluteSum"); dataItem_4.setCalculatedValue(key, sum_1[key] / count_1[key], "average"); dataItem_4.setCalculatedValue(key, open_1[key], "open"); dataItem_4.setCalculatedValue(key, close_1[key], "close"); dataItem_4.setCalculatedValue(key, low_1[key], "low"); dataItem_4.setCalculatedValue(key, high_1[key], "high"); dataItem_4.setCalculatedValue(key, count_1[key], "count"); }); } }; /** * (Re)validates the whole series, effectively causing it to redraw. * * @ignore Exclude from docs */ Series.prototype.validate = function () { if ($utils.isIE()) { this.filters.clear(); } $iter.each(this.axisRanges.iterator(), function (axisRange) { //axisRange.contents.disposeChildren(); // not good for columns, as they are reused // axisRange.appendChildren(); axisRange.validate(); }); _super.prototype.validate.call(this); var bulletsContainer = this.bulletsContainer; bulletsContainer.fill = this.fill; bulletsContainer.stroke = this.stroke; bulletsContainer.x = this.pixelX; bulletsContainer.y = this.pixelY; if (this.bulletsContainer.children.length > 0) { if (this._showBullets) { for (var i = 0; i < this.startIndex; i++) { var dataItem = this.dataItems.getIndex(i); if (dataItem) { dataItem.bullets.each(function (key, bullet) { bullet.__disabled = true; }); } } for (var i = this.dataItems.length - 1; i > this.endIndex; i--) { var dataItem = this.dataItems.getIndex(i); if (dataItem) { dataItem.bullets.each(function (key, bullet) { bullet.__disabled = true; }); } } } else { this.bulletsContainer.children.each(function (bullet) { bullet.__disabled = true; }); } } this.updateTooltipBounds(); }; /** * @ignore */ Series.prototype.updateTooltipBounds = function () { if (this.topParent) { var x = 0; var y = 0; var w = this.topParent.maxWidth; var h = this.topParent.maxHeight; var rect = { x: x, y: y, width: w, height: h }; this.tooltip.setBounds(rect); } }; Series.prototype.shouldCreateBullet = function (dataItem, bulletTemplate) { return true; }; /** * Validates data item's element, effectively redrawing it. * * @ignore Exclude from docs * @param dataItem Data item */ Series.prototype.validateDataElement = function (dataItem) { var _this = this; _super.prototype.validateDataElement.call(this, dataItem); if (this._showBullets) { if (!this.isHidden) { this.bulletsContainer.visible = true; } this.bullets.each(function (bulletTemplate) { // always better to use the same, this helps to avoid redrawing var bullet = dataItem.bullets.getKey(bulletTemplate.uid); if (_this.shouldCreateBullet(dataItem, bulletTemplate)) { if (!bullet) { var disabledField = bulletTemplate.propertyFields.disabled; var dataContext = dataItem.dataContext; if (disabledField && dataContext && dataContext[disabledField] === false) { bulletTemplate.applyOnClones = false; bulletTemplate.disabled = false; bullet = bulletTemplate.clone(); bulletTemplate.disabled = true; bulletTemplate.applyOnClones = true; } else { bullet = bulletTemplate.clone(); } bullet.shouldClone = false; dataItem.addSprite(bullet); if (!_this.visible || _this.isHiding) { bullet.hide(0); } } var currentDataItem = bullet.dataItem; if (currentDataItem != dataItem) { // set to undefined in order not to reuse if (currentDataItem) { currentDataItem.bullets.setKey(bulletTemplate.uid, undefined); } var readerText_1 = _this.itemReaderText; if (bullet instanceof Bullet) { if (!readerText_1) { readerText_1 = ("{" + bullet.xField + "}: {" + bullet.yField + "}"); } if (bullet.isDynamic) { dataItem.events.on("workingvaluechanged", bullet.deepInvalidate, bullet, false); //dataItem.events.on("calculatedvaluechanged", bullet.deepInvalidate, bullet, false); _this.dataItem.events.on("workingvaluechanged", bullet.deepInvalidate, bullet, false); } bullet.deepInvalidate(); } // Add accessibility to bullet if (bullet.focusable) { bullet.events.on("focus", function (ev) { bullet.readerTitle = _this.populateString(readerText_1, bullet.dataItem); }, undefined, false); bullet.events.on("blur", function (ev) { bullet.readerTitle = ""; }, undefined, false); } if (bullet.hoverable) { bullet.events.on("over", function (ev) { bullet.readerTitle = _this.populateString(readerText_1, bullet.dataItem); }, undefined, false); bullet.events.on("out", function (ev) { bullet.readerTitle = ""; }, undefined, false); } } bullet.parent = _this.bulletsContainer; dataItem.bullets.setKey(bulletTemplate.uid, bullet); // pass max w/h so we'd know if we should show/hide somethings bullet.maxWidth = dataItem.itemWidth; bullet.maxHeight = dataItem.itemHeight; bullet.__disabled = false; _this.positionBullet(bullet); } else { if (bullet) { bullet.__disabled = true; } } }); } else { this.bulletsContainer.visible = false; } }; /** * [handleDataItemWorkingValueChange description] * * @ignore Exclude from docs */ Series.prototype.handleDataItemWorkingValueChange = function (dataItem, name) { if (!this.dataRangeInvalid) { this.invalidateProcessedData(); } }; Object.defineProperty(Series.prototype, "ignoreMinMax", { /** * @return Exclude from calculations? */ get: function () { return this._ignoreMinMax; }, /** * Should this series excluded from the axis scale calculations? * * @default false * @param value Exclude from calculations? */ set: function (value) { this._ignoreMinMax = value; this.invalidateDataItems(); }, enumerable: true, configurable: true }); /** * Create a mask for the series. * * @ignore Exclude from docs */ Series.prototype.createMask = function () { // A placeholder method for extending classes to override. }; /** * Process axis range after it has been added to the list. * * @param event Event */ Series.prototype.processAxisRange = function (event) { // create container if not existing if (!this.rangesContainer) { this.rangesContainer = this.createChild(Container); this.rangesContainer.shouldClone = false; this.rangesContainer.isMeasured = false; } var axisRange = event.newValue; if (axisRange) { axisRange.contents.parent = this.rangesContainer; axisRange.isRange = true; axisRange.events.on("valuechanged", this.invalidateDataItems, this, false); } }; /** * [getAxisField description] * * @ignore Exclude from docs * @todo Description * @param axis [description] * @return [description] */ Series.prototype.getAxisField = function (axis) { return; }; /** * Shows the tooltip at specific position. * * @ignore Exclude from docs * @param xPosition X * @param yPosition Y */ Series.prototype.showTooltipAtPosition = function (xPosition, yPosition) { // Placeholder method for extending classes to override. }; Object.defineProperty(Series.prototype, "minBulletDistance", { /** * @return Distance (px) */ get: function () { return this.getPropertyValue("minBulletDistance"); }, /** * Minimal distance between data points in pixels. * * If distance gets smaller than this, bullets are turned off to avoid * overlapping. * * `0` (zero) disables this behavior. * * IMPORTANT: This setting will work only when Series' base axis * is [[CategoryAxis]] or [[DateAxis]]. If base axis is [[ValueAxis]] the * setting will be ignored, because it would be a huge overhead to measure * distance between each and every bullet. * * @default 0 * @param value Distance (px) */ set: function (value) { this.setPropertyValue("minBulletDistance", value, true); }, enumerable: true, configurable: true }); Object.defineProperty(Series.prototype, "bullets", { /** * A list of bullets that will be added to each and every items in the * series. * * You can push any object that is a descendant of a [[Sprite]] here. All * items added to this list will be copied and used as a bullet on all data * items, including their properties, events, etc. * * @see {@link https://www.amcharts.com/docs/v4/concepts/bullets/} for more info about the concept of Bullets * @return List of bullets. */ get: function () { if (!this._bullets) { this._bullets = new ListTemplate(new Bullet()); this._bullets.template.virtualParent = this; this._bullets.events.on("inserted", this.processBullet, this, false); this._bullets.events.on("removed", this.removeBullet, this, false); this._disposers.push(new ListDisposer(this._bullets)); this._disposers.push(this._bullets.template); } return this._bullets; }, enumerable: true, configurable: true }); /** * Binds related legend data item's visual settings to this series' visual * settings. * * @ignore Exclude from docs * @param marker Legend item container */ Series.prototype.createLegendMarker = function (marker) { // This is a placeholder method for extending classes to override. }; Object.defineProperty(Series.prototype, "hiddenInLegend", { /** * @return Hidden in legend? */ get: function () { return this.getPropertyValue("hiddenInLegend"); }, /** * Should the series be hidden in legend? * * @param value Hidden in legend? */ set: function (value) { if (this.setPropertyValue("hiddenInLegend", value)) { if (this.chart) { this.chart.feedLegend(); } } }, enumerable: true, configurable: true }); Object.defineProperty(Series.prototype, "name", { /** * @return Name */ get: function () { return this.getPropertyValue("name"); }, /** * Series' name. * * @param value Name */ set: function (value) { this.setPropertyValue("name", value); var legendDataItem = this.legendDataItem; if (legendDataItem) { legendDataItem.component.invalidate(); legendDataItem.component.invalidateRawData(); } this.readerTitle = value; }, enumerable: true, configurable: true }); Object.defineProperty(Series.prototype, "itemReaderText", { /** * @return Screen reader text template */ get: function () { // Get explicitly set reader text var readerText = this._itemReaderText; // Not set? Let's try something else if (!readerText) { // Tooltip text? if (this.tooltipText) { readerText = $utils.plainText(this.tooltipText); } else if (this.tooltipHTML) { readerText = $utils.plainText(this.tooltipHTML); } } if (!this._adapterO) { return readerText; } else { return this._adapterO.apply("itemReaderText", readerText); } }, /** * Screen reader text to be applied to each individual data item, such * as bullets, columns or slices. * * The template can contain field reference meta codes, i.e. `{dateX}`, * `{valueY}`, etc. * * Any text formatting options, e.g. `[bold]` will be ignored. * * @param value Screen reader text template */ set: function (value) { this._itemReaderText = value; this._itemReaderTextChanged = true; }, enumerable: true, configurable: true }); /** * Returns if number of data items in the series are beyond non-focusable * count and should not be available for TAB-through. * * @ignore Exclude from docs * @return Items focusable? */ Series.prototype.itemsFocusable = function () { return this.dataItems.length >= this.skipFocusThreshold ? false : true; }; Object.defineProperty(Series.prototype, "legendDataItem", { /** * @return Data item */ get: function () { return this._legendDataItem; }, /** * Legend data item that corresponds to this series. * * @param value Data item */ set: function (value) { this._legendDataItem = value; this._legendDataItem.itemContainer.deepInvalidate(); }, enumerable: true, configurable: true }); /** * Updates corresponding legend data item with current values. * * @ignore Exclude from docs * @param dataItem Data item */ Series.prototype.updateLegendValue = function (dataItem, notRange) { // if this series has legend item if (this.legendDataItem) { var legendSettings = this.legendSettings; var legendDataItem = this.legendDataItem; var label = legendDataItem.label; var valueLabel = legendDataItem.valueLabel; // update legend if ((dataItem && !dataItem.isDisposed()) || notRange) { if (valueLabel) { if (legendSettings.itemValueText) { valueLabel.text = legendSettings.itemValueText; } valueLabel.dataItem = dataItem; } if (label) { if (legendSettings.itemLabelText) { label.text = legendSettings.itemLabelText; } label.dataItem = dataItem; } } else { if (label) { // if itemLabelText is set, means we have to reset label even if labelText is not set if (legendSettings.labelText || legendSettings.itemLabelText != undefined) { label.text = legendSettings.labelText; } label.dataItem = this.dataItem; } if (valueLabel) { if (legendSettings.valueText || legendSettings.itemValueText != undefined) { valueLabel.text = legendSettings.valueText; } valueLabel.dataItem = this.dataItem; } } } }; /** * Copies all properties from another instance of [[Series]]. * * @param source Source series */ Series.prototype.copyFrom = function (source) { this.bullets.copyFrom(source.bullets); this.bulletsContainer.copyFrom(source.bulletsContainer); this.calculatePercent = source.calculatePercent; this.usePercentHack = source.usePercentHack; this.simplifiedProcessing = source.simplifiedProcessing; _super.prototype.copyFrom.call(this, source); }; /** * Displays a modal or console message with error, and halts any further * processing of this element. * * @param e Error */ Series.prototype.raiseCriticalError = function (e) { if (this._chart && this._chart.modal) { this._chart.modal.content = this._chart.adapter.apply("criticalError", e).message; this._chart.modal.closable = false; if (!options.suppressErrors) { this._chart.modal.open(); } this._chart.disabled = true; } if (options.verbose) { console.log(e); } }; /** * Applies filters to the element. * * @ignore Exclude from docs */ Series.prototype.applyFilters = function () { var _this = this; _super.prototype.applyFilters.call(this); this.bulletsContainer.filters.clear(); // copyFrom of a list copies, does not clone $iter.each(this.filters.iterator(), function (filter) { _this.bulletsContainer.filters.push(filter.clone()); }); }; Object.defineProperty(Series.prototype, "heatRules", { /** * A list of heat rules to apply to series' elements based on the value * of the data item. * * Heat rules can be any "numeric" (including `Color`) property, and can also * be applied to child objects of series, like columns, bullets, etc. * * E.g.: * * ```TypeScript * series.heatRules.push({ * "target": series.columns.template, * "property": "fill", * "min": am4core.color("#F5DBCB"), * "max": am4core.color("#ED7B84"), * "dataField": "valueY" *}); *``` * ```Javacript * series.heatRules.push({ * "target": series.columns.template, * "property": "fill", * "min": am4core.color("#F5DBCB"), * "max": am4core.color("#ED7B84"), * "dataField": "valueY" *}); *``` *```JSON *{ * // ... * "series": [{ * "type": "ColumnSeries", * "heatRules": [{ * "target": "columns.template", * "property": "fill", * "min": "#F5DBCB", * "max": "#ED7B84", * "dataField": "valueY" * }] * }] *} *``` * * @see {@link https://www.amcharts.com/docs/v4/concepts/series/#Heat_maps} for more about heat rules * @return Heat rules */ get: function () { var _this = this; if (!this._heatRules) { this._heatRules = new List(); this._heatRules.events.on("inserted", function (event) { var heatRule = event.newValue; var target = heatRule.target; if (target) { var dataField_1 = heatRule.dataField; if (!$type.hasValue(dataField_1)) { dataField_1 = "value"; } var seriesDataItem_1 = _this.dataItem; var property_1 = heatRule.property; var minValue = $type.toNumber(heatRule.minValue); var maxValue = $type.toNumber(heatRule.maxValue); if (!$type.isNumber(minValue) && !$type.isNumber(maxValue)) { _this.dataItem.events.on("calculatedvaluechanged", function (event) { if (event.property == dataField_1) { $iter.each(_this.dataItems.iterator(), function (dataItem) { var foundSprite = false; $array.each(dataItem.sprites, function (sprite) { if (sprite.clonedFrom == target) { var anySprite = sprite; anySprite[property_1] = anySprite[property_1]; foundSprite = true; } }); if (!foundSprite) { $array.each(dataItem.sprites, function (sprite) { if (sprite instanceof Container) { $iter.each(sprite.children.iterator(), function (child) { if (child.className == target.className) { var anyChild = child; anyChild[property_1] = anyChild[property_1]; } // giveup here else if (child instanceof Container) { child.deepInvalidate(); } }); } }); } }); } }); } _this.dataItems.template.events.on("workingvaluechanged", function (event) { if (event.property == dataField_1) { var dataItem = event.target; var foundSprite_1 = false; $array.each(dataItem.sprites, function (sprite) { if (sprite.clonedFrom == target) { var anySprite = sprite; anySprite[property_1] = anySprite[property_1]; foundSprite_1 = true; } }); if (!foundSprite_1) { $array.each(dataItem.sprites, function (sprite) { if (sprite instanceof Container) { $iter.each(sprite.children.iterator(), function (child) { if (child.className == target.className) {