import { __awaiter } from "tslib"; import { PercentSeries } from "../percent/PercentSeries"; import { Template } from "../../core/util/Template"; import { ListTemplate } from "../../core/util/List"; import { FunnelSlice } from "./FunnelSlice"; import { Tick } from "../../core/render/Tick"; import { Label } from "../../core/render/Label"; import { percent, p50, p100 } from "../../core/util/Percent"; import * as $array from "../../core/util/Array"; import * as $type from "../../core/util/Type"; import * as $utils from "../../core/util/Utils"; /** * Creates a funnel series for use in a [[SlicedChart]]. * * @see {@link https://www.amcharts.com/docs/v5/charts/percent-charts/sliced-chart/funnel-series/} for more info * @important */ export class FunnelSeries extends PercentSeries { constructor() { super(...arguments); Object.defineProperty(this, "_tag", { enumerable: true, configurable: true, writable: true, value: "funnel" }); /** * A [[ListTemplate]] of all slice links in series. * * `links.template` can also be used to configure slice links. * * @see {@link https://www.amcharts.com/docs/v5/charts/percent-charts/sliced-chart/funnel-series/#Slice_links} for more info */ Object.defineProperty(this, "links", { enumerable: true, configurable: true, writable: true, value: this._makeLinks() }); Object.defineProperty(this, "_total", { enumerable: true, configurable: true, writable: true, value: 0 }); Object.defineProperty(this, "_count", { enumerable: true, configurable: true, writable: true, value: 0 }); Object.defineProperty(this, "_nextCoord", { enumerable: true, configurable: true, writable: true, value: 0 }); Object.defineProperty(this, "_opposite", { enumerable: true, configurable: true, writable: true, value: false }); } _makeSlices() { return new ListTemplate(Template.new({}), () => FunnelSlice._new(this._root, { themeTags: $utils.mergeTags(this.slices.template.get("themeTags", []), [this._tag, "series", "slice", this.get("orientation")]) }, [this.slices.template])); } _makeLabels() { return new ListTemplate(Template.new({}), () => Label._new(this._root, { themeTags: $utils.mergeTags(this.labels.template.get("themeTags", []), [this._tag, "series", "label", this.get("orientation")]) }, [this.labels.template])); } _makeTicks() { return new ListTemplate(Template.new({}), () => Tick._new(this._root, { themeTags: $utils.mergeTags(this.ticks.template.get("themeTags", []), [this._tag, "series", "tick", this.get("orientation")]) }, [this.ticks.template])); } _makeLinks() { return new ListTemplate(Template.new({}), () => FunnelSlice._new(this._root, { themeTags: $utils.mergeTags(this.links.template.get("themeTags", []), [this._tag, "series", "link", this.get("orientation")]) }, [this.links.template])); } /** * @ignore */ makeLink(dataItem) { const link = this.slicesContainer.children.push(this.links.make()); link._setDataItem(dataItem); dataItem.set("link", link); this.links.push(link); return link; } _afterNew() { super._afterNew(); const slicesContainer = this.slicesContainer; slicesContainer.setAll({ isMeasured: true, position: "relative", width: percent(100), height: percent(100) }); slicesContainer.onPrivate("width", () => { this.markDirtySize(); }); slicesContainer.onPrivate("height", () => { this.markDirtySize(); }); if (this.get("orientation") == "vertical") { this.set("layout", this._root.horizontalLayout); } else { this.set("layout", this._root.verticalLayout); } } processDataItem(dataItem) { super.processDataItem(dataItem); const slice = this.makeSlice(dataItem); slice._setDataItem(dataItem); dataItem.set("slice", slice); this.makeLink(dataItem); const label = this.makeLabel(dataItem); label.on("x", () => { this._updateTick(dataItem); }); label.on("y", () => { this._updateTick(dataItem); }); this.makeTick(dataItem); slice.events.on("positionchanged", () => { label.markDirty(); }); slice.events.on("boundschanged", () => { const dataItem = slice.dataItem; if (dataItem) { this._updateTick(dataItem); } }); } _updateChildren() { this._opposite = false; if (this.children.indexOf(this.labelsContainer) == 0) { this._opposite = true; } let total = 0; let count = 0; $array.each(this.dataItems, (dataItem) => { const value = dataItem.get("value"); if ($type.isNumber(value)) { count++; if (value > 0) { total += Math.abs(dataItem.get("valueWorking", value) / value); } else { if (this.get("ignoreZeroValues", false)) { count--; } else { if (dataItem.isHidden()) { count--; } else { total += 1; } } } } }); this._total = 1 / count * total; this._count = count; if (this.isDirty("alignLabels")) { this._fixLayout(); } if (this._total > 0 && (this._valuesDirty || this._sizeDirty)) { const slicesContainer = this.slicesContainer; let h; if (this.get("orientation") == "vertical") { h = slicesContainer.innerHeight(); } else { h = slicesContainer.innerWidth(); } this._nextCoord = this.get("startLocation", 0) * h; this.markDirtyBounds(); let i = 0; $array.each(this._dataItems, (dataItem) => { this.updateLegendValue(dataItem); dataItem.set("index", i); i++; const slice = dataItem.get("slice"); const tick = dataItem.get("tick"); const label = dataItem.get("label"); const link = dataItem.get("link"); const color = dataItem.get("fill"); slice._setDefault("fill", color); slice._setDefault("stroke", color); link._setDefault("fill", color); link._setDefault("stroke", color); const value = dataItem.get("value"); if ($type.isNumber(value)) { if (value == 0 && this.get("ignoreZeroValues")) { slice.setPrivate("visible", false); tick.setPrivate("visible", false); label.setPrivate("visible", false); } else { slice.setPrivate("visible", true); tick.setPrivate("visible", true); label.setPrivate("visible", true); this.decorateSlice(dataItem); if (this.isLast(dataItem)) { link.setPrivate("visible", false); } else if (!dataItem.isHidden()) { link.setPrivate("visible", true); } } } }); } super._updateChildren(); } _fixLayout() { const orientation = this.get("orientation"); const labelsContainer = this.labelsContainer; const labelsTemplate = this.labels.template; if (this.get("alignLabels")) { labelsContainer.set("position", "relative"); labelsContainer.setAll({ isMeasured: true }); if (orientation == "vertical") { this.set("layout", this._root.horizontalLayout); labelsTemplate.setAll({ centerX: p100, x: p100 }); } else { this.set("layout", this._root.verticalLayout); labelsTemplate.setAll({ centerX: 0, x: 0 }); } } else { labelsContainer.setAll({ isMeasured: false, position: "absolute" }); if (orientation == "vertical") { labelsContainer.setAll({ x: p50 }); labelsTemplate.setAll({ centerX: p50, x: 0 }); } else { labelsContainer.setAll({ y: p50 }); labelsTemplate.setAll({ centerX: p50, y: 0 }); } } this.markDirtySize(); } getNextValue(dataItem) { let index = dataItem.get("index"); let nextValue = dataItem.get("valueWorking", 0); if (index < this.dataItems.length - 1) { let nextItem = this.dataItems[index + 1]; nextValue = nextItem.get("valueWorking", 0); if (nextItem.isHidden() || (nextItem.get("value") == 0 && this.get("ignoreZeroValues"))) { return this.getNextValue(nextItem); } } return nextValue; } isLast(dataItem) { let index = dataItem.get("index"); if (index == this.dataItems.length - 1) { return true; } else { for (let i = index + 1; i < this.dataItems.length; i++) { if (!this.dataItems[i].isHidden()) { return false; } } } return true; } decorateSlice(dataItem) { const orientation = this.get("orientation"); const slice = dataItem.get("slice"); const label = dataItem.get("label"); const link = dataItem.get("link"); const slicesContainer = this.slicesContainer; let maxWidth = slicesContainer.innerWidth(); let maxHeight = slicesContainer.innerHeight(); let maxSize = maxWidth; if (orientation == "horizontal") { maxSize = maxHeight; } const nextValue = this.getNextValue(dataItem); const value = dataItem.get("value", 0); const workingValue = Math.abs(dataItem.get("valueWorking", value)); const bottomRatio = this.get("bottomRatio", 0); const valueHigh = this.getPrivate("valueHigh", 0); let d = 1; if (value != 0) { d = workingValue / Math.abs(value); } else { if (dataItem.isHidden()) { d = 0.000001; } } if (this._nextCoord == Infinity) { this._nextCoord = 0; } let topWidth = workingValue / valueHigh * maxSize; let bottomWidth = (workingValue - (workingValue - nextValue) * bottomRatio) / valueHigh * maxSize; slice.setAll({ topWidth, bottomWidth, orientation }); link.setAll({ topWidth: bottomWidth, bottomWidth: (workingValue - (workingValue - nextValue)) / valueHigh * maxSize, orientation }); const startLocation = this.get("startLocation", 0); const endLocation = this.get("endLocation", 1); if (orientation == "vertical") { let linkHeight = link.height() * d; maxHeight = maxHeight * (endLocation - startLocation) + linkHeight; slice.set("y", this._nextCoord); let height = Math.min(100000, Math.max(0, maxHeight / this._count * d / this._total - linkHeight)); slice.setAll({ height, x: maxWidth / 2 }); let labelY = this._nextCoord + height / 2; label.set("y", labelY); this._nextCoord += height + linkHeight; link.setAll({ y: this._nextCoord - linkHeight, x: maxWidth / 2 }); } else { let linkHeight = link.width() * d; maxWidth = maxWidth * (endLocation - startLocation) + linkHeight; slice.set("x", this._nextCoord); let width = Math.min(100000, Math.max(0, maxWidth / this._count * d / this._total - linkHeight)); slice.setAll({ width, y: maxHeight / 2 }); const labelX = this._nextCoord + width / 2; label.set("x", labelX); this._nextCoord += width + linkHeight; link.setAll({ x: this._nextCoord - linkHeight, y: maxHeight / 2 }); } } /** * 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* () { dataItem.get("link").hide(duration); return _super.hideDataItem.call(this, dataItem, duration); }); } /** * 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* () { dataItem.get("link").show(duration); return _super.showDataItem.call(this, dataItem, duration); }); } _updateTick(dataItem) { if (this.get("alignLabels")) { const tick = dataItem.get("tick"); const label = dataItem.get("label"); const slice = dataItem.get("slice"); if (tick && slice && label) { const labelsContainer = this.labelsContainer; const slicesContainer = this.slicesContainer; let tickLocation = tick.get("location", 0.5); const lcw = labelsContainer.width(); const lch = labelsContainer.height(); const pl = labelsContainer.get("paddingLeft", 0); const pr = labelsContainer.get("paddingRight", 0); const pt = labelsContainer.get("paddingTop", 0); const pb = labelsContainer.get("paddingBottom", 0); let p0 = { x: 0, y: 0 }; let p1 = { x: 0, y: 0 }; let p2 = { x: 0, y: 0 }; if (this._opposite) { tickLocation = 1 - tickLocation; } if (this.get("orientation") == "vertical") { p0 = slice.getPoint(tickLocation, 0.5); p0.x += slice.x() + slicesContainer.x(); p0.y += slice.y() + slicesContainer.y(); if (this._opposite) { p1.x = lcw; p1.y = label.y(); p2.x = lcw - pl; p2.y = p1.y; } else { p1.x = slicesContainer.x() + slicesContainer.width(); p1.y = label.y(); p2.x = p1.x + lcw - label.width() - pr; p2.y = p1.y; } } else { p0 = slice.getPoint(0.5, tickLocation); p0.x += slice.x() + slicesContainer.x(); p0.y += slice.y() + slicesContainer.y(); if (this._opposite) { p1.y = lch; p1.x = label.x(); p2.y = lch - pt; p2.x = p1.x; } else { p1.y = slicesContainer.y() + slicesContainer.height(); p1.x = label.x(); p2.y = p1.y + lch - label.height() - pb; p2.x = p1.x; } } tick.set("points", [p0, p1, p2]); } } } /** * @ignore */ disposeDataItem(dataItem) { super.disposeDataItem(dataItem); let link = dataItem.get("link"); if (link) { this.links.removeValue(link); link.dispose(); } } _positionBullet(bullet) { const sprite = bullet.get("sprite"); if (sprite) { const dataItem = sprite.dataItem; const slice = dataItem.get("slice"); if (slice) { const width = slice.width(); const height = slice.height(); const locationX = bullet.get("locationX", 0.5); const locationY = bullet.get("locationY", 0.5); let dx = 0; let dy = 0; if (this.get("orientation") == "horizontal") { dy = height / 2; } else { dx = width / 2; } sprite.setAll({ x: slice.x() + width * locationX - dx, y: slice.y() - dy + height * locationY }); } } } } Object.defineProperty(FunnelSeries, "className", { enumerable: true, configurable: true, writable: true, value: "FunnelSeries" }); Object.defineProperty(FunnelSeries, "classNames", { enumerable: true, configurable: true, writable: true, value: PercentSeries.classNames.concat([FunnelSeries.className]) }); //# sourceMappingURL=FunnelSeries.js.map