UNPKG

@amcharts/amcharts5

Version:
716 lines 28.9 kB
import { __awaiter } from "tslib"; import { Component } from "../../core/render/Component"; import { List } from "../../core/util/List"; import { Color } from "../../core/util/Color"; import { percentInterpolate } from "../../core/util/Animation"; import { Percent } from "../../core/util/Percent"; import { p100 } from "../../core/util/Percent"; import { Container } from "../../core/render/Container"; import { Label } from "../../core/render/Label"; //import { Animations } from "../../core/util/Animation"; import * as $array from "../../core/util/Array"; import * as $type from "../../core/util/Type"; import * as $time from "../../core/util/Time"; /** * A base class for all series. */ export class Series extends Component { constructor() { super(...arguments); this._aggregatesCalculated = false; this._selectionAggregatesCalculated = false; this._dataProcessed = false; this._baseSeriesDirty = false; /** * List of bullets to use for the series. * * @see {@link https://www.amcharts.com/docs/v5/concepts/common-elements/bullets/} for more info */ this.bullets = new List(); /** * A [[Container]] series' bullets are stored in. * * @default Container.new() */ this.bulletsContainer = Container.new(this._root, { width: p100, height: p100, position: "absolute" }); } _afterNew() { this.valueFields.push("value", "customValue"); super._afterNew(); this.setPrivate("customData", {}); this._disposers.push(this.bullets.events.onAll(() => { this._handleBullets(this.dataItems); })); } _dispose() { this.bulletsContainer.dispose(); // can be in a different parent super._dispose(); } startIndex() { let len = this.dataItems.length; return Math.min(this.getPrivate("startIndex", 0), len); } endIndex() { let len = this.dataItems.length; return Math.max(0, Math.min(this.getPrivate("endIndex", len), len)); } _handleBullets(dataItems) { $array.each(dataItems, (dataItem) => { const bullets = dataItem.bullets; if (bullets) { $array.each(bullets, (bullet) => { bullet.dispose(); }); dataItem.bullets = undefined; } }); this.markDirtyValues(); } _makeBullets(dataItem) { if (this._shouldMakeBullet(dataItem)) { dataItem.bullets = []; this.bullets.each((bulletFunction) => { this._makeBullet(dataItem, bulletFunction); }); } } _shouldMakeBullet(_dataItem) { return true; } _makeBullet(dataItem, bulletFunction, index) { const bullet = bulletFunction(this._root, this, dataItem); if (bullet) { bullet._index = index; this._makeBulletReal(dataItem, bullet); } return bullet; } /** * Container a data item's bullets are added to. Series that lay their data * items out as separate containers can put bullets into the item itself, so * that they move with it and can be stacked among its other children. */ _getBulletContainer(_dataItem) { return this.bulletsContainer; } _makeBulletReal(dataItem, bullet) { let sprite = bullet.get("sprite"); if (sprite) { sprite._setDataItem(dataItem); sprite.setRaw("position", "absolute"); this._applyBulletDefaultPaint(sprite, dataItem); this._getBulletContainer(dataItem).children.push(sprite); } bullet.series = this; dataItem.bullets.push(bullet); } /** * A bullet whose shape defines none of its own paint inherits the full set * from the series, so bullets match their series without restating the color * (like columns do). It's all-or-nothing: if the bullet sets even one of * `fill`/`fillGradient`/`fillPattern`/`stroke`/`strokeGradient`, it is left * entirely alone. Only `Graphics`-based shapes are touched; container/label/ * picture bullets are left to style their own children. * * @ignore */ _applyBulletDefaultPaint(sprite, dataItem) { if (sprite.isType("Graphics")) { const unstyled = sprite.get("fill") == null && sprite.get("fillGradient") == null && sprite.get("fillPattern") == null && sprite.get("stroke") == null && sprite.get("strokeGradient") == null; if (unstyled) { // pie slices, flow nodes and hierarchy nodes hold their color on the data item const itemFill = dataItem ? dataItem.get("fill") : undefined; sprite.set("fill", itemFill != null ? itemFill : this.get("fill")); sprite.set("fillGradient", this.get("fillGradient")); sprite.set("fillPattern", this.get("fillPattern")); sprite.set("stroke", this.get("stroke")); sprite.set("strokeGradient", this.get("strokeGradient")); } } } /** * Adds bullet directly to a data item. * * Please note: method accepts [[Bullet]] instance as a paramter, not a * reference to a function. * * You should add Bullet instance, not a method like you do it on series. * * @see {@link https://www.amcharts.com/docs/v5/concepts/common-elements/bullets/#Adding_directly_to_data_item} for more info * @since 5.6.0 * * @param dataItem Target data item * @param bullet Bullet instance */ addBullet(dataItem, bullet) { if (!dataItem.bullets) { dataItem.bullets = []; } if (bullet) { this._makeBulletReal(dataItem, bullet); } } _clearDirty() { super._clearDirty(); this._aggregatesCalculated = false; this._baseSeriesDirty = false; this._selectionAggregatesCalculated = false; } _prepareChildren() { super._prepareChildren(); const dataItems = this.dataItems; const count = dataItems.length; let startIndex = this.startIndex(); let endIndex = this.endIndex(); if (this.isDirty("name")) { this.updateLegendValue(); } if (this.isDirty("heatRules")) { this._valuesDirty = true; } if (this.isPrivateDirty("baseValueSeries")) { const baseValueSeries = this.getPrivate("baseValueSeries"); if (baseValueSeries) { this._disposers.push(baseValueSeries.onPrivate("startIndex", () => { this._baseSeriesDirty = true; this.markDirtyValues(); })); } } const calculateAggregates = this.get("calculateAggregates"); if (calculateAggregates) { if (this._valuesDirty && !this._dataProcessed) { if (!this._aggregatesCalculated) { this._calculateAggregates(0, count); this._aggregatesCalculated = true; if (startIndex != 0) { this._psi = undefined; } } } if ((this._psi != startIndex || this._pei != endIndex || this.isPrivateDirty("adjustedStartIndex")) && !this._selectionAggregatesCalculated) { if (startIndex === 0 && endIndex === count && this._aggregatesCalculated) { // void } else { this._calculateAggregates(startIndex, endIndex); } this._selectionAggregatesCalculated = true; } } if (this.isDirty("tooltip")) { let tooltip = this.get("tooltip"); if (tooltip) { tooltip.hide(0); tooltip.set("tooltipTarget", this); } } if (this.isDirty("fill") || this.isDirty("stroke")) { let markerRectangle; const legendDataItem = this.get("legendDataItem"); if (legendDataItem) { markerRectangle = legendDataItem.get("markerRectangle"); if (markerRectangle) { if (this.isVisible()) { if (this.isDirty("stroke")) { let stroke = this.get("stroke"); markerRectangle.set("stroke", stroke); } if (this.isDirty("fill")) { let fill = this.get("fill"); markerRectangle.set("fill", fill); } } } } this.updateLegendMarker(undefined); } if (this.bullets.length > 0) { let startIndex = this.startIndex(); let endIndex = this.endIndex(); if (endIndex < count) { endIndex++; } for (let i = startIndex; i < endIndex; i++) { let dataItem = dataItems[i]; if (!dataItem.bullets) { this._makeBullets(dataItem); } } } } _handleRemoved() { } /** * @ignore */ _adjustStartIndex(index) { return index; } _calculateAggregates(startIndex, endIndex) { let fields = this._valueFields; if (!fields) { throw new Error("No value fields are set for the series."); } const excludeFromAggregate = this.get("excludeFromAggregate"); if (excludeFromAggregate) { fields = fields.filter((field) => { return excludeFromAggregate.indexOf(field) == -1; }); } const sum = {}; const absSum = {}; const count = {}; const low = {}; const high = {}; const open = {}; const close = {}; const average = {}; const previous = {}; for (let f = 0, flen = fields.length; f < flen; f++) { let key = fields[f]; sum[key] = 0; absSum[key] = 0; count[key] = 0; } const dataItems = this.dataItems; const len = dataItems.length; for (let f = 0, flen = fields.length; f < flen; f++) { let key = fields[f]; let change = key + "Change"; let changePercent = key + "ChangePercent"; let changePrevious = key + "ChangePrevious"; let changePreviousPercent = key + "ChangePreviousPercent"; let changeSelection = key + "ChangeSelection"; let changeSelectionPercent = key + "ChangeSelectionPercent"; let openKey = "valueY"; if (key === "valueX" || key.endsWith("ValueX")) { openKey = "valueX"; } const baseValueSeries = this.getPrivate("baseValueSeries"); const adjustedStartIndex = this.getPrivate("adjustedStartIndex", startIndex); const calculateChangesForItem = (dataItem, value, key) => { if (startIndex === 0) { const openValue = open[openKey]; const changeValue = value - openValue; dataItem.setRaw((change), changeValue); dataItem.setRaw((changePercent), changeValue / openValue * 100); } const changePreviousValue = value - previous[openKey]; const changeSelectionValue = value - open[openKey]; dataItem.setRaw((changePrevious), changePreviousValue); dataItem.setRaw((changePreviousPercent), changePreviousValue / previous[openKey] * 100); dataItem.setRaw((changeSelection), changeSelectionValue); dataItem.setRaw((changeSelectionPercent), changeSelectionValue / open[openKey] * 100); previous[key] = value; }; // find first non-null value to initialize open, low, high instead of checking in loop for (let i = adjustedStartIndex; i < endIndex; i++) { const dataItem = dataItems[i]; if (dataItem) { let value = dataItem.get(key); if (value != null) { if (low[key] == null) { low[key] = value; } if (high[key] == null) { high[key] = value; } if (open[key] == null) { open[key] = value; previous[key] = value; if (baseValueSeries) { open[openKey] = baseValueSeries._getBase(openKey); } } // stop cycle break; } } } for (let i = adjustedStartIndex; i < endIndex; i++) { const dataItem = dataItems[i]; if (dataItem) { let value = dataItem._settings[key]; if (value != null) { count[key]++; sum[key] += value; absSum[key] += Math.abs(value); if (low[key] > value) { low[key] = value; } if (high[key] < value) { high[key] = value; } close[key] = value; calculateChangesForItem(dataItem, value, key); } } } average[key] = sum[key] / count[key]; if (endIndex < len) { const dataItem = dataItems[endIndex]; if (dataItem) { let value = dataItem.get(key); if (value != null) { calculateChangesForItem(dataItem, value, key); } } } if (endIndex + 1 < len) { const dataItem = dataItems[endIndex + 1]; if (dataItem) { let value = dataItem.get(key); if (value != null) { calculateChangesForItem(dataItem, value, key); } } } if (startIndex > 0) { startIndex--; } delete previous[key]; for (let i = startIndex; i < adjustedStartIndex; i++) { const dataItem = dataItems[i]; if (dataItem) { let value = dataItem.get(key); if (previous[key] == null) { previous[key] = value; } if (value != null) { calculateChangesForItem(dataItem, value, key); } } } } for (let f = 0, flen = fields.length; f < flen; f++) { let key = fields[f]; this.setPrivate((key + "AverageSelection"), average[key]); this.setPrivate((key + "CountSelection"), count[key]); this.setPrivate((key + "SumSelection"), sum[key]); this.setPrivate((key + "AbsoluteSumSelection"), absSum[key]); this.setPrivate((key + "LowSelection"), low[key]); this.setPrivate((key + "HighSelection"), high[key]); this.setPrivate((key + "OpenSelection"), open[key]); this.setPrivate((key + "CloseSelection"), close[key]); } if (startIndex === 0 && endIndex === len) { for (let f = 0, flen = fields.length; f < flen; f++) { let key = fields[f]; this.setPrivate((key + "Average"), average[key]); this.setPrivate((key + "Count"), count[key]); this.setPrivate((key + "Sum"), sum[key]); this.setPrivate((key + "AbsoluteSum"), absSum[key]); this.setPrivate((key + "Low"), low[key]); this.setPrivate((key + "High"), high[key]); this.setPrivate((key + "Open"), open[key]); this.setPrivate((key + "Close"), close[key]); } } } _updateChildren() { super._updateChildren(); this._psi = this.startIndex(); this._pei = this.endIndex(); if (this.isDirty("visible")) { this.bulletsContainer.set("visible", this.get("visible")); } // Apply heat rules const rules = this.get("heatRules"); if (this._valuesDirty && rules && rules.length > 0) { $array.each(rules, (rule) => { const minValue = rule.minValue || this.getPrivate((rule.dataField + "Low")) || 0; const maxValue = rule.maxValue || this.getPrivate((rule.dataField + "High")) || 0; $array.each(rule.target._entities, (target) => { const value = target.dataItem.get(rule.dataField); if (!$type.isNumber(value)) { if (rule.neutral) { target._setC(rule.key, rule.neutral); } const states = target.states; if (states) { const defaultState = states.lookup("default"); if (defaultState && rule.neutral) { defaultState._setC(rule.key, rule.neutral); } } if (!rule.customFunction) { return; } } if (rule.customFunction) { rule.customFunction.call(this, target, minValue, maxValue, value); } else { let percent; if (rule.logarithmic) { percent = (Math.log(value) * Math.LOG10E - Math.log(minValue) * Math.LOG10E) / ((Math.log(maxValue) * Math.LOG10E - Math.log(minValue) * Math.LOG10E)); } else { percent = (value - minValue) / (maxValue - minValue); } if ($type.isNumber(value) && (!$type.isNumber(percent) || Math.abs(percent) == Infinity)) { percent = 0.5; } // fixes problems if all values are the same let propertyValue; if ($type.isNumber(rule.min)) { propertyValue = rule.min + (rule.max - rule.min) * percent; } else if (rule.min instanceof Color) { propertyValue = Color.interpolate(percent, rule.min, rule.max); } else if (rule.min instanceof Percent) { propertyValue = percentInterpolate(percent, rule.min, rule.max); } target._setC(rule.key, propertyValue); const states = target.states; if (states) { const defaultState = states.lookup("default"); if (defaultState) { defaultState._setC(rule.key, propertyValue); } } } }); }); } if (this.get("visible")) { const dataItems = this.dataItems; let count = dataItems.length; let startIndex = this.startIndex(); let endIndex = this.endIndex(); if (endIndex < count) { endIndex++; } if (startIndex > 0) { startIndex--; } for (let i = 0; i < startIndex; i++) { this._hideBullets(dataItems[i]); } for (let i = startIndex; i < endIndex; i++) { this._positionBullets(dataItems[i]); } for (let i = endIndex; i < count; i++) { this._hideBullets(dataItems[i]); } } } _positionBullets(dataItem) { if (dataItem.bullets) { $array.each(dataItem.bullets, (bullet) => { this._positionBullet(bullet); const sprite = bullet.get("sprite"); if (bullet.get("dynamic")) { if (sprite) { sprite._markDirtyKey("fill"); sprite.markDirtySize(); } if (sprite instanceof Container) { sprite.walkChildren((child) => { child._markDirtyKey("fill"); child.markDirtySize(); if (child instanceof Label) { child.text.markDirtyText(); } }); } } if (sprite instanceof Label && sprite.get("populateText")) { sprite.text.markDirtyText(); } }); } } _hideBullets(dataItem) { if (dataItem.bullets) { $array.each(dataItem.bullets, (bullet) => { let sprite = bullet.get("sprite"); if (sprite) { sprite.setPrivate("visible", false); } }); } } _positionBullet(_bullet) { } _placeBulletsContainer(chart) { chart.bulletsContainer.children.moveValue(this.bulletsContainer); } _removeBulletsContainer() { const bulletsContainer = this.bulletsContainer; if (bulletsContainer.parent) { bulletsContainer.parent.children.removeValue(bulletsContainer); } } /** * @ignore */ disposeDataItem(dataItem) { //super.disposeDataItem(dataItem); // does nothing const bullets = dataItem.bullets; if (bullets) { $array.each(bullets, (bullet) => { bullet.dispose(); }); dataItem.bullets = undefined; } } _getItemReaderLabel() { return ""; } /** * Shows series's data item. * * @param dataItem Data item * @param duration Animation duration in milliseconds * @return Promise */ showDataItem(dataItem, duration) { const _super = Object.create(null, { showDataItem: { get: () => super.showDataItem } }); return __awaiter(this, void 0, void 0, function* () { const promises = [_super.showDataItem.call(this, dataItem, duration)]; const bullets = dataItem.bullets; if (bullets) { $array.each(bullets, (bullet) => { const sprite = bullet.get("sprite"); if (sprite) { promises.push(sprite.show(duration)); } }); } yield Promise.all(promises); }); } /** * Hides series's data item. * * @param dataItem Data item * @param duration Animation duration in milliseconds * @return Promise */ hideDataItem(dataItem, duration) { const _super = Object.create(null, { hideDataItem: { get: () => super.hideDataItem } }); return __awaiter(this, void 0, void 0, function* () { const promises = [_super.hideDataItem.call(this, dataItem, duration)]; const bullets = dataItem.bullets; if (bullets) { $array.each(bullets, (bullet) => { const sprite = bullet.get("sprite"); if (sprite) { promises.push(sprite.hide(duration)); } }); } yield Promise.all(promises); }); } _sequencedShowHide(show, duration) { return __awaiter(this, void 0, void 0, function* () { if (this.get("sequencedInterpolation")) { if (!$type.isNumber(duration)) { duration = this.get("interpolationDuration", 0); } if (duration > 0) { const startIndex = this.startIndex(); const endIndex = this.endIndex(); yield Promise.all($array.map(this.dataItems, (dataItem, i) => __awaiter(this, void 0, void 0, function* () { let realDuration = duration || 0; if (i < startIndex - 10 || i > endIndex + 10) { realDuration = 0; } //let delay = this.get("sequencedDelay", 0) * i + realDuration * (i - startIndex) / (endIndex - startIndex); let delay = this.get("sequencedDelay", 0) + realDuration / (endIndex - startIndex); yield $time.sleep(delay * (i - startIndex)); if (show) { yield this.showDataItem(dataItem, realDuration); } else { yield this.hideDataItem(dataItem, realDuration); } }))); } else { yield Promise.all($array.map(this.dataItems, (dataItem) => { if (show) { return this.showDataItem(dataItem, 0); } else { return this.hideDataItem(dataItem, 0); } })); } } }); } /** * @ignore */ updateLegendValue(dataItem) { if (dataItem) { const legendDataItem = dataItem.get("legendDataItem"); if (legendDataItem) { const valueLabel = legendDataItem.get("valueLabel"); if (valueLabel) { const text = valueLabel.text; let txt = ""; valueLabel._setDataItem(dataItem); txt = this.get("legendValueText", text.get("text", "")); valueLabel.set("text", txt); text.markDirtyText(); } const label = legendDataItem.get("label"); if (label) { const text = label.text; let txt = ""; label._setDataItem(dataItem); txt = this.get("legendLabelText", text.get("text", "")); label.set("text", txt); text.markDirtyText(); } } } } /** * @ignore */ updateLegendMarker(_dataItem) { } _onHide() { super._onHide(); const tooltip = this.getTooltip(); if (tooltip) { tooltip.hide(); } } /** * @ignore */ hoverDataItem(_dataItem) { } /** * @ignore */ unhoverDataItem(_dataItem) { } /** * @ignore */ _getBase(key) { const dataItem = this.dataItems[this.startIndex()]; if (dataItem) { return dataItem.get(key); } return 0; } } Series.className = "Series"; Series.classNames = Component.classNames.concat([Series.className]); //# sourceMappingURL=Series.js.map