UNPKG

@amcharts/amcharts5

Version:
1,416 lines 61.5 kB
import { __awaiter } from "tslib";
import { ColumnSeries } from "../xy/series/ColumnSeries";
import { Container } from "../../core/render/Container";
import { Circle, color, Line, Link, ListTemplate, percent, Percent, Rectangle, RoundedRectangle, Template, Triangle } from "../../..";
import * as $array from "../../core/util/Array";
import * as $utils from "../../core/util/Utils";
import * as $math from "../../core/util/Math";
import * as $time from "../../core/util/Time";
import * as $object from "../../core/util/Object";
import * as $type from "../../core/util/Type";
/**
 * A series used in [[Gantt]] chart to display tasks and their progress.
 *
 * @see {@link https://www.amcharts.com/docs/v5/charts/gantt/#Gantt_series} for more info
 * @important
 * @since 5.14.0
 */
export class GanttSeries extends ColumnSeries {
    constructor() {
        super(...arguments);
        this._xPan = false;
        this._yPan = false;
        /**
         * A container that holds all the links between tasks.
         */
        this.linksContainer = this.children.push(Container.new(this._root, {}));
        /**
         * A line which is shown while creating a connector between two tasks.
         */
        this.connectorLine = this.children.push(Line.new(this._root, {
            themeTags: ["connectorline"],
            visible: false,
            forceInactive: true
        })._markC("themeTags", "visible", "forceInactive"));
        /**
         * A triangle that is shown at the end of the connector line, while creating
         * a connector.
         */
        this.connectorArrow = this.children.push(Triangle.new(this._root, {
            themeTags: ["connectorarrow"],
            visible: false,
            forceInactive: true
        })._markC("themeTags", "visible", "forceInactive"));
        /**
         * [[ListTemplate]] of [[Link]] that connect tasks.
         */
        this.links = this.addDisposer(new ListTemplate(Template.new({}), () => Link._new(this._root, {
            themeTags: $utils.mergeTags(this.containers.template.get("themeTags", []), ["link"])
        }, [this.links.template])));
        /**
         * [[ListTemplate]] of [[Container]]s that hold all the elements of series
         * items, such as grips, bullets, etc.
         */
        this.containers = this.addDisposer(new ListTemplate(Template.new({}), () => Container._new(this._root, {
            position: "absolute",
            themeTagsSelf: ["columncontainer"]
        }, [this.containers.template])));
        /**
         * [[ListTemplate]] of [[Container]]s that are used to mask elements, such
         * as progress rectangles.
         */
        this.maskedContainers = this.addDisposer(new ListTemplate(Template.new({}), () => Container._new(this._root, {
            position: "absolute",
            themeTagsSelf: ["maskedcontainer"]
        }, [this.maskedContainers.template])));
        /**
         * [[ListTemplate]] of [[Circle]]s that are used as start bullets.
         */
        this.startBullets = this.addDisposer(new ListTemplate(Template.new({}), () => Circle._new(this._root, {
            x: -1000,
            themeTagsSelf: ["connectorbullet", "start"],
        }, [this.startBullets.template])));
        /**
         * [[ListTemplate]] of [[Circle]]s that are used as end bullets.
         */
        this.endBullets = this.addDisposer(new ListTemplate(Template.new({}), () => Circle._new(this._root, {
            x: -1000,
            themeTagsSelf: ["connectorbullet", "end"]
        }, [this.endBullets.template])));
        /**
         * [[ListTemplate]] of [[Rectangle]]s that are used to resize task bars.
         */
        this.startGrips = this.addDisposer(new ListTemplate(Template.new({}), () => Rectangle._new(this._root, {
            themeTagsSelf: ["resizegrip", "start"]
        }, [this.startGrips.template])));
        /**
         * [[ListTemplate]] of [[Rectangle]]s that are used to resize task bars.
         */
        this.endGrips = this.addDisposer(new ListTemplate(Template.new({}), () => Rectangle._new(this._root, {
            themeTagsSelf: ["resizegrip", "end"]
        }, [this.endGrips.template])));
        /**
         * [[ListTemplate]] of [[RoundedRectangle]]s that are used to show
         * zero-duration tasks.
         */
        this.zeroRectangles = this.addDisposer(new ListTemplate(Template.new({}), () => RoundedRectangle._new(this._root, {
            themeTagsSelf: ["zerorectangle"],
        }, [this.zeroRectangles.template])));
        /**
         * [[ListTemplate]] of [[Rectangle]]s that are used to show progress of the
         * task. It is actually a remaining part of the task and is filled with
         * diagonal line pattern.
         */
        this.progressRectangles = this.addDisposer(new ListTemplate(Template.new({}), () => Rectangle._new(this._root, {
            themeTagsSelf: ["progressrectangle"]
        }, [this.progressRectangles.template])));
        /**
         * [[ListTemplate]] of [[Triangle]]s that are used to resize progress
         * rectangles.
         */
        this.progressGrips = this.addDisposer(new ListTemplate(Template.new({}), () => Triangle._new(this._root, {
            themeTagsSelf: ["progressgrip"]
        }, [this.progressGrips.template])));
    }
    _afterNew() {
        this.fields.push("linkTo", "categoryId");
        this.valueFields.push("progress", "duration");
        this._setC("exactLocationX", true);
        super._afterNew();
        this._disposers.push(this.events.on("globalpointermove", (event) => {
            if (this.connectorLine.isVisible()) {
                const point = this.toLocal(event.point);
                this._updateConnector(point);
            }
        }));
        this.events.on("globalpointerup", (_event) => {
            if (this.connectorLine.isVisible()) {
                this._endConnector();
            }
        });
        this.columns.template.events.on("dragstart", (e) => {
            this._columnDragStartX = e.target.x();
            this._columnDragStartY = e.target.y();
        });
        this.columns.template.events.on("dragged", (e) => {
            this._handleColumnDragged(e);
        });
        this.columns.template.events.on("dragstop", (e) => {
            const xAxis = this.get("xAxis");
            const dataItem = e.target.dataItem;
            const openValueX = dataItem.get("openValueX", 0);
            let newOpenValueX = xAxis.positionToValue(xAxis.coordinateToPosition(e.target.x()));
            const valueX = dataItem.get("valueX", 0);
            let diff = newOpenValueX - openValueX;
            dataItem.set("openValueX", newOpenValueX);
            dataItem.set("valueX", valueX + diff);
            dataItem.set("openValueXWorking", newOpenValueX);
            dataItem.set("valueXWorking", valueX + diff);
            this._fixValues(dataItem, "duration");
            this._moveChildValues(dataItem, diff);
        });
    }
    /**
     * @ignore
     */
    roundValue(value, invertThreshold) {
        let threshold = this.gantt.get("snapThreshold", 0.8);
        if (invertThreshold) {
            threshold = 1 - threshold;
        }
        const snapCount = this.get("snapCount", 1);
        const xAxis = this.get("xAxis");
        const baseInterval = xAxis.get("baseInterval");
        return $time.roun(value + this.getUnitDuration() * threshold, baseInterval.timeUnit, baseInterval.count * snapCount, this._root);
    }
    /**
     * @ignore
     */
    makeColumn(dataItem, listTemplate) {
        // Create the main column
        const column = super.makeColumn(dataItem, listTemplate);
        const yAxis = this.get("yAxis");
        column.events.on("click", () => {
            yAxis.selectDataItem(dataItem.get("categoryAxisDataItem"));
        });
        // --- Container setup ---
        const container = this.mainContainer.children.push(this.containers.make());
        container._setDataItem(dataItem);
        this.containers.push(container);
        const zeroRectangle = this.zeroRectangles.make();
        dataItem.set("zeroRectangle", zeroRectangle);
        this.zeroRectangles.push(zeroRectangle);
        container.children.unshift(zeroRectangle);
        if (dataItem.get("duration", 0) > 0) {
            zeroRectangle.hide(0);
        }
        let zy = 0;
        zeroRectangle.events.on("pointerdown", (e) => {
            zy = e.target.y();
            column.dragStart(e);
        });
        zeroRectangle.events.on("dragstart", (e) => {
            this._handleColumnDragged(e);
            e.target.setAll({
                x: 0,
                y: zy
            });
        });
        zeroRectangle.events.on("dragged", (e) => {
            this._handleColumnDragged(e);
            e.target.setAll({
                x: 0,
                y: zy
            });
        });
        zeroRectangle.events.on("dragstop", (e) => {
            column.dragStop(e);
        });
        zeroRectangle.events.on("pointerover", (e) => {
            this._handleColumnHover(e);
        });
        zeroRectangle.adapters.add("fillPattern", (fillPattern, target) => {
            const dataItem = target.dataItem;
            if (dataItem.get("progress", 0) < 1) {
                return progressRectangle.get("fillPattern");
            }
            return fillPattern;
        });
        const maskedContainer = container.children.push(this.maskedContainers.make());
        dataItem.setRaw("maskedContainer", maskedContainer);
        const mask = container.children.push(RoundedRectangle.new(this._root, {
            fill: color(0xffffff),
            forceInactive: true,
            fillOpacity: 0
        }));
        dataItem.setRaw("mask", mask);
        maskedContainer._setC("mask", mask);
        // --- Progress elements ---
        const progressRectangle = maskedContainer.children.push(this.progressRectangles.make());
        this.progressRectangles.push(progressRectangle);
        const progressGrip = this.progressGrips.make();
        this.progressGrips.push(progressGrip);
        maskedContainer.children.push(progressGrip);
        progressGrip.events.on("dragged", () => {
            let x = progressGrip.x();
            x = $math.fitToRange(x, 0, column.getPrivate("width", 0));
            progressGrip.setAll({ x: x, y: column.getPrivate("height", 0) });
            dataItem.set("progress", progressGrip.x() / column.getPrivate("width", 0));
        });
        // --- Grips (start/end) ---
        const startGrip = this.startGrips.make();
        this.startGrips.push(startGrip);
        container.children.push(startGrip);
        const endGrip = this.endGrips.make();
        this.endGrips.push(endGrip);
        container.children.push(endGrip);
        dataItem.setRaw("startGrip", startGrip);
        dataItem.setRaw("endGrip", endGrip);
        // --- Axis reference ---
        const xAxis = this.get("xAxis");
        // --- Grip events ---
        let startX = 0;
        startGrip.events.on("dragstart", () => {
            startX = column.x();
            column.show();
            zeroRectangle.hide();
            maskedContainer.show();
        });
        startGrip.events.on("pointerover", () => {
            column.states.applyAnimate("hover");
        });
        startGrip.events.on("pointerout", () => {
            if (column.isVisible()) {
                column.states.applyAnimate("default");
            }
        });
        startGrip.events.on("dragged", () => {
            startGrip.set("y", 0);
            const x = startGrip.x() + startX;
            let openValueX = xAxis.positionToValue(xAxis.coordinateToPosition(x));
            let minStart = this._findMinStart(dataItem);
            openValueX = Math.max(openValueX, minStart);
            openValueX = Math.min(openValueX, dataItem.get("valueX", openValueX));
            dataItem.set("openValueX", openValueX);
            startGrip.set("x", 0);
        });
        startGrip.events.on("dragstop", () => {
            this._fixValues(dataItem, "end");
            this.markDirtyValues();
        });
        let endX = 0;
        endGrip.events.on("dragstart", () => {
            endX = column.x();
            column.show();
            zeroRectangle.hide();
            maskedContainer.show();
        });
        endGrip.events.on("pointerover", () => {
            column.states.applyAnimate("hover");
        });
        endGrip.events.on("pointerout", () => {
            if (column.isVisible()) {
                column.states.applyAnimate("default");
            }
        });
        endGrip.events.on("dragged", () => {
            endGrip.set("y", 0);
            const x = endGrip.x() + endX;
            let valueX = xAxis.positionToValue(xAxis.coordinateToPosition(x));
            valueX = Math.max(valueX, dataItem.get("openValueX", 0));
            dataItem.set("valueX", valueX);
            dataItem.set("valueXWorking", valueX);
            const column = dataItem.get("graphics");
            if (column) {
                endGrip.set("x", column.width());
            }
        });
        endGrip.events.on("dragstop", () => {
            this._fixValues(dataItem, "start");
            this.markDirtyValues();
        });
        // --- Bullets (start/end) ---
        const startBullet = this.startBullets.make();
        startBullet.events.on("pointerout", () => {
            if (column.isVisible()) {
                column.states.applyAnimate("default");
            }
        });
        this.startBullets.push(startBullet);
        container.children.push(startBullet);
        const endBullet = this.endBullets.make();
        this.endBullets.push(endBullet);
        container.children.push(endBullet);
        // --- Bullet events (connector logic) ---
        const bulletPointerDown = (bullet) => {
            this._startConnector(bullet);
        };
        startBullet.events.on("pointerdown", () => bulletPointerDown(startBullet));
        endBullet.events.on("pointerdown", () => bulletPointerDown(endBullet));
        column.events.on("pointerover", (e) => {
            this._handleColumnHover(e);
        });
        column.events.on("pointerout", () => {
            this._hoveredDataItem = undefined;
            if (this.connectorLine.isVisible()) {
                startBullet.hide();
            }
        });
        // --- Data item references ---
        dataItem.setRaw("container", container);
        dataItem.setRaw("startBullet", startBullet);
        dataItem.setRaw("endBullet", endBullet);
        dataItem.setRaw("progressRectangle", progressRectangle);
        dataItem.setRaw("progressGrip", progressGrip);
        // --- Sync container with column position ---
        const syncProp = (prop, target) => {
            column.on(prop, (val) => {
                target.set(prop, val);
            });
        };
        syncProp("x", container);
        syncProp("y", container);
        syncProp("dx", container);
        syncProp("dy", container);
        syncProp("cornerRadiusBL", mask);
        syncProp("cornerRadiusBR", mask);
        syncProp("cornerRadiusTL", mask);
        syncProp("cornerRadiusTR", mask);
        if (this.isCollapsed(dataItem)) {
            dataItem.hide(0);
            column.hide(0);
            maskedContainer.hide(0);
        }
        else {
            if (dataItem.get("duration", 0) > 0) {
                column.appear();
                maskedContainer.appear();
            }
        }
        return column;
    }
    _handleColumnDragged(e) {
        const column = e.target;
        const dataItem = column.dataItem;
        // Maintain vertical position to prevent vertical dragging
        if (this._columnDragStartY !== undefined) {
            column.set("y", this._columnDragStartY);
        }
        // Calculate horizontal drag offset and apply to child elements
        if (dataItem && this._columnDragStartX !== undefined) {
            const dragOffset = column.x() - this._columnDragStartX;
            this._dragChildValues(dataItem, dragOffset);
            this._updateLinks();
        }
    }
    _handleColumnHover(e) {
        var _a;
        // Only process hover if we're currently creating a connector
        if (!this.connectorLine.isVisible())
            return;
        const column = e.target;
        const dataItem = column.dataItem;
        // Skip if no valid data item
        if (!dataItem)
            return;
        const startBullet = dataItem.get("startBullet");
        const sourceDataItem = (_a = this._startBullet) === null || _a === void 0 ? void 0 : _a.dataItem;
        // Show the start bullet only if:
        // 1. We have a valid start bullet
        // 2. We have a source data item for the connection
        // 3. Both items are at the same depth level
        // 4. We're not trying to connect to the same item
        if (startBullet &&
            sourceDataItem &&
            this.getDepth(sourceDataItem) === this.getDepth(dataItem) &&
            sourceDataItem !== dataItem) {
            startBullet.show();
            this._hoveredDataItem = dataItem;
        }
    }
    /**
     * Checks if the task is collapsed.
     *
     * @param  dataItem  Data item
     * @return           Collapsed?
     */
    isCollapsed(dataItem) {
        const categoryAxisDataItem = dataItem.get("categoryAxisDataItem");
        if (categoryAxisDataItem) {
            const parent = categoryAxisDataItem.get("parent");
            if (parent) {
                if (parent.get("collapsed")) {
                    return true;
                }
                return this.isCollapsed(parent);
            }
        }
        return false;
    }
    /**
     * Updates the series, recalculating values and updating children.
     *
     * @ignore Exclude from docs
     */
    _updateChildren() {
        if (this._valuesDirty) {
            this.setPrivateRaw("minX", undefined);
            this.setPrivateRaw("maxX", undefined);
            const yAxis = this.get("yAxis");
            const yRenderer = yAxis.get("renderer");
            $array.each(this.dataItems, (dataItem) => {
                const dataContext = dataItem.dataContext;
                const categoryAxisDataItem = dataItem.get("categoryAxisDataItem");
                if (categoryAxisDataItem) {
                    const children = dataItem.get("children");
                    if (children && children.length > 0) {
                        dataItem.set("openValueX", undefined);
                        dataItem.set("valueX", undefined);
                        dataItem.set("openValueXWorking", undefined);
                        dataItem.set("valueXWorking", undefined);
                        delete (dataContext[this.get("openValueXField")]);
                        delete (dataContext[this.get("valueXField")]);
                        delete (dataContext[this.get("durationField")]);
                    }
                    else {
                        dataContext[this.get("openValueXField")] = dataItem.get("openValueX");
                        dataContext[this.get("valueXField")] = dataItem.get("valueX");
                        dataContext[this.get("durationField")] = dataItem.get("duration");
                        dataContext[this.get("progressField")] = dataItem.get("progress");
                    }
                    dataContext[this.get("linkToField")] = dataItem.get("linkTo");
                    const category = dataItem.get("categoryY");
                    const categoryYField = this.get("categoryYField", "category");
                    dataContext[categoryYField] = category;
                    const categoryDataContext = categoryAxisDataItem.dataContext;
                    const categoryField = yAxis.get("categoryField", "category");
                    const parent = categoryAxisDataItem.get("parent");
                    categoryDataContext[categoryField] = category;
                    if (parent) {
                        categoryDataContext[yAxis.get("parentIdField", "parentId")] = parent.get("id");
                    }
                    // Collapsing is something a group does, at any level - a leaf has
                    // nothing to collapse, and a top-level group is the one most often
                    // collapsed
                    const rowChildren = categoryAxisDataItem.get("children");
                    if (rowChildren && rowChildren.length > 0) {
                        categoryDataContext[yAxis.get("collapsedField", "collapsed")] = categoryAxisDataItem.get("collapsed", false);
                    }
                }
            });
            $array.each(this.dataItems, (dataItem) => {
                this._adjustParentValues(dataItem);
                const children = dataItem.get("children");
                const startGrip = dataItem.get("startGrip");
                const endGrip = dataItem.get("endGrip");
                const progressGrip = dataItem.get("progressGrip");
                let visible = true;
                if (children && children.length > 0) {
                    visible = false;
                }
                if (startGrip) {
                    startGrip.setPrivate("visible", visible);
                }
                if (endGrip) {
                    endGrip.set("visible", visible);
                }
                if (progressGrip) {
                    progressGrip.set("visible", visible);
                }
                const categoryAxisDataItem = dataItem.get("categoryAxisDataItem");
                let index = 0;
                if (categoryAxisDataItem) {
                    const durationStepper = categoryAxisDataItem.get("durationStepper");
                    if (durationStepper) {
                        durationStepper.set("disabled", !visible);
                    }
                    index = categoryAxisDataItem.get("index", 0);
                }
                const duration = dataItem.get("duration", 0);
                let zeroRectangle = dataItem.get("zeroRectangle");
                const column = dataItem.get("graphics");
                const maskedContainer = dataItem.get("maskedContainer");
                const fill = categoryAxisDataItem.get("customColor", categoryAxisDataItem.get("color"));
                if (zeroRectangle) {
                    let height = 20;
                    let y0 = yAxis.indexToPosition(index, 0);
                    let y1 = yAxis.indexToPosition(index, 1);
                    y0 = yRenderer.positionToCoordinate(y0);
                    y1 = yRenderer.positionToCoordinate(y1);
                    const columnHeight = column.get("height", 0);
                    let r = 1;
                    if (columnHeight instanceof Percent) {
                        r *= columnHeight.value * $math.cos(45);
                    }
                    height = (y1 - y0) * r;
                    const maxHeight = column.get("maxHeight", Infinity) * $math.cos(45);
                    const minHeight = column.get("minHeight", 0) * $math.cos(45);
                    height = $math.fitToRange(height, minHeight, maxHeight);
                    zeroRectangle.setPrivate("height", height);
                    zeroRectangle.setPrivate("width", height);
                }
                if (column && maskedContainer && !this.isCollapsed(dataItem)) {
                    if (duration === 0 && zeroRectangle) {
                        if (!column.isHidden() && !column.isHiding()) {
                            zeroRectangle.setAll({
                                fill: fill,
                                stroke: fill
                            });
                            if (!(startGrip === null || startGrip === void 0 ? void 0 : startGrip.isDragging()) && !(endGrip === null || endGrip === void 0 ? void 0 : endGrip.isDragging())) {
                                column.hide();
                                maskedContainer.hide();
                                zeroRectangle.show();
                            }
                        }
                    }
                    else {
                        if (zeroRectangle) {
                            zeroRectangle.hide();
                        }
                        column.show();
                        maskedContainer.show();
                    }
                }
            });
            $array.each(this.dataItems, (dataItem) => {
                this._adjustLinkValues(dataItem);
            });
            $array.each(this.dataItems, (dataItem) => {
                this._adjustParentValues(dataItem);
                const categoryAxisDataItem = dataItem.get("categoryAxisDataItem");
                if (categoryAxisDataItem) {
                    categoryAxisDataItem.set("duration", dataItem.get("duration"));
                    categoryAxisDataItem.set("progress", dataItem.get("progress"));
                }
            });
            // dispatch event
            const eventType = "valueschanged";
            if (this.events.isEnabled(eventType)) {
                this.events.dispatch(eventType, {
                    type: eventType, target: this
                });
            }
        }
        super._updateChildren();
        if (this._columnsUpdated) {
            this._updateLinks();
        }
    }
    _getParentOpen(dataItem) {
        const parent = dataItem.get("parent");
        if (parent) {
            return this._getParentOpen(parent);
        }
        else {
            return dataItem.get("openValueX", -Infinity);
        }
    }
    _adjustLinkValues(dataItem) {
        const linkTo = dataItem.get("linkTo");
        if (linkTo) {
            const valueX = dataItem.get("valueX", 0);
            $array.each(linkTo, (id) => {
                const linkDataItem = this.getDataItemById(id);
                if (linkDataItem && linkDataItem !== dataItem) {
                    const linkOpenValueX = linkDataItem.get("openValueX", 0);
                    if (linkOpenValueX !== undefined && linkOpenValueX < valueX) {
                        linkDataItem.set("openValueX", valueX);
                        this._fixValues(linkDataItem, "duration");
                        this._adjustChildValues(linkDataItem);
                    }
                }
            });
        }
    }
    _adjustChildValues(dataItem) {
        const children = dataItem.get("children", []);
        if (children && children.length > 0) {
            let openValueX = dataItem.get("openValueX", 0);
            $array.each(children, (child) => {
                const childOpenValueX = child.get("openValueX", 0);
                if (childOpenValueX === undefined || childOpenValueX < openValueX) {
                    child.set("openValueX", openValueX);
                    this._fixValues(child, "duration");
                }
                this._adjustChildValues(child);
            });
        }
    }
    _dragChildValues(dataItem, dx) {
        const children = dataItem.get("children");
        if (children && children.length > 0) {
            $array.each(children, (child) => {
                const column = child.get("graphics");
                if (column) {
                    column.set("dx", dx);
                }
                this._dragChildValues(child, dx);
            });
        }
    }
    _moveChildValues(dataItem, diff) {
        const children = dataItem.get("children");
        if (children && children.length > 0) {
            $array.each(children, (child) => {
                child.set("openValueX", child.get("openValueX", 0) + diff);
                child.set("valueX", child.get("valueX", 0) + diff);
                child.set("openValueXWorking", child.get("openValueX"));
                child.set("valueXWorking", child.get("openValueX"));
                const column = child.get("graphics");
                if (column) {
                    column.set("dx", 0);
                }
                this._fixValues(child, "duration");
                this._moveChildValues(child, diff);
            });
        }
    }
    _findMinStart(dataItem) {
        // check if item has links from other items
        let maxValue = -Infinity;
        $array.each(this.dataItems, (item) => {
            const linkTo = item.get("linkTo");
            if (linkTo) {
                $array.each(linkTo, (id) => {
                    if (id === dataItem.get("id")) {
                        const itemValueX = item.get("valueX", 0);
                        if (itemValueX > maxValue) {
                            maxValue = itemValueX;
                        }
                    }
                });
            }
        });
        // if this is a child, check parent start
        const parent = dataItem.get("parent");
        if (parent) {
            maxValue = Math.max(maxValue, this._findMinStart(parent));
        }
        return maxValue;
    }
    _adjustParentValues(dataItem) {
        const categoryAxisDataItem = dataItem.get("categoryAxisDataItem");
        if (categoryAxisDataItem) {
            const column = dataItem.get("graphics");
            let color = categoryAxisDataItem.get("customColor", categoryAxisDataItem.get("color"));
            if (column) {
                column.setAll({
                    fill: color,
                    stroke: color
                });
            }
            const zeroRectangle = dataItem.get("zeroRectangle");
            if (zeroRectangle) {
                zeroRectangle.setAll({
                    fill: color,
                    stroke: color,
                });
            }
            const children = dataItem.get("children");
            if (children && children.length > 0) {
                let progress = 0;
                $array.each(children, (child) => {
                    const childProgress = child.get("progress", 0);
                    if (childProgress !== undefined) {
                        progress += childProgress;
                    }
                });
                progress /= children.length;
                dataItem.set("progress", progress);
            }
            if (categoryAxisDataItem) {
                let valueX = dataItem.get("valueX");
                let openValueX = dataItem.get("openValueX");
                const duration = dataItem.get("duration", 0);
                if (valueX === undefined && openValueX !== undefined) {
                    openValueX = this.getOpenValue(openValueX);
                    valueX = this.getEndValue(openValueX, duration);
                    dataItem.setRaw("openValueX", openValueX);
                    dataItem.setRaw("valueX", valueX);
                    //dataItem.setRaw("openValueXWorking", openValueX);
                    //dataItem.setRaw("valueXWorking", valueX);					
                }
                let parent = dataItem.get("parent");
                if (openValueX !== undefined && valueX !== undefined && parent) {
                    const parentOpenValueX = parent.get("openValueX");
                    const parentValueX = parent.get("valueX");
                    if (parentOpenValueX === undefined || parentOpenValueX > openValueX) {
                        parent.set("openValueX", openValueX);
                    }
                    if (parentValueX === undefined || parentValueX < valueX) {
                        parent.set("valueX", valueX);
                        parent.set("valueXWorking", valueX);
                    }
                    parent.set("duration", this.getDataItemDuration(parent));
                    this._adjustParentValues(parent);
                }
            }
        }
    }
    _startConnector(bullet) {
        var _a, _b, _c, _d;
        this._startBullet = bullet;
        this.connectorLine.show();
        this.connectorArrow.show();
        this._xPan = (_a = this.chart) === null || _a === void 0 ? void 0 : _a.get("panX");
        this._yPan = (_b = this.chart) === null || _b === void 0 ? void 0 : _b.get("panY");
        (_c = this.chart) === null || _c === void 0 ? void 0 : _c.set("panX", false);
        (_d = this.chart) === null || _d === void 0 ? void 0 : _d.set("panY", false);
        const dataItem = bullet.dataItem;
        // disable all columns of not the same depth
        const depth = this.getDepth(dataItem);
        const itemsToDisable = [];
        $array.each(this.dataItems, (item) => {
            if (this.getDepth(item) != depth) {
                $array.move(itemsToDisable, item);
            }
            // also if already connected, disable
            const links = dataItem.get("links");
            if (links && links[item.get("id")]) {
                $array.move(itemsToDisable, item);
            }
        });
        // if this item has parent, disable all items ecept items in the same branch
        const parent = dataItem.get("parent");
        if (parent) {
            $array.each(this.dataItems, (item) => {
                if (item.get("parent") !== parent) {
                    $array.move(itemsToDisable, item);
                }
            });
        }
        // disable all columns of the same depth which are connected to start bullet data item
        const id = dataItem.get("id");
        const checkIfConnected = (id) => {
            $array.each(this.dataItems, (item) => {
                const links = item.get("links");
                if (links && links[id]) {
                    itemsToDisable.push(item);
                    checkIfConnected(item.get("id"));
                }
            });
        };
        checkIfConnected(id);
        $array.each(itemsToDisable, (item) => {
            const column = item.get("graphics");
            if (column) {
                column.states.applyAnimate("disabled");
            }
            const container = item.get("container");
            if (container) {
                container.states.applyAnimate("disabled");
            }
            const zeroRectangle = item.get("zeroRectangle");
            if (zeroRectangle) {
                zeroRectangle.states.applyAnimate("disabled");
            }
        });
    }
    _updateConnector(point) {
        var _a;
        let startPoint = (_a = this._startBullet) === null || _a === void 0 ? void 0 : _a.toGlobal({ x: 0, y: 0 });
        if (startPoint) {
            startPoint = this.toLocal(startPoint);
            this.connectorLine.setAll({
                segments: [[[{ x: startPoint.x, y: startPoint.y }, { x: point.x, y: point.y }]]]
            });
            this.connectorArrow.setAll({
                x: point.x,
                y: point.y,
                rotation: $math.getAngle(startPoint, point) + 90,
            });
        }
    }
    _endConnector() {
        var _a, _b, _c, _d, _e;
        let startPoint = (_a = this._startBullet) === null || _a === void 0 ? void 0 : _a.toGlobal({ x: 0, y: 0 });
        if (startPoint) {
            startPoint = this.toLocal(startPoint);
            this.connectorLine.hide();
            this.connectorArrow.hide();
            $array.each(this.dataItems, (item) => {
                const column = item.get("graphics");
                if (column && column.isVisible()) {
                    column.states.applyAnimate("default");
                }
                const container = item.get("container");
                if (container && container.isVisible()) {
                    container.states.applyAnimate("default");
                }
                const zeroRectangle = item.get("zeroRectangle");
                if (zeroRectangle && zeroRectangle.isVisible()) {
                    zeroRectangle.states.applyAnimate("default");
                }
            });
            let dataItem = this._hoveredDataItem;
            if (dataItem && dataItem !== ((_b = this._startBullet) === null || _b === void 0 ? void 0 : _b.dataItem)) {
                const id = dataItem.get("id");
                if (id) {
                    const startDataItem = (_c = this._startBullet) === null || _c === void 0 ? void 0 : _c.dataItem;
                    if (startDataItem) {
                        const linkTo = startDataItem.get("linkTo", []);
                        if ($array.indexOf(linkTo, id) === -1) {
                            linkTo.push(id);
                            startDataItem.set("linkTo", linkTo);
                            this.markDirtyValues();
                        }
                    }
                }
            }
            this._updateLinks();
        }
        this.endBullets.each((bullet) => {
            bullet.show();
        });
        this.startBullets.each((bullet) => {
            bullet.hide();
        });
        (_d = this.chart) === null || _d === void 0 ? void 0 : _d.set("panX", this._xPan);
        (_e = this.chart) === null || _e === void 0 ? void 0 : _e.set("panY", this._yPan);
    }
    /**
     * Fixes values of a data item, adjusting start, end, or duration as needed.
     */
    _fixValues(dataItem, keep) {
        let valueX = dataItem.get("valueX");
        let openValueX = dataItem.get("openValueX");
        let duration = dataItem.get("duration", 0);
        if ($type.isNumber(valueX) && $type.isNumber(openValueX)) {
            if (keep === "start") {
                valueX = this.roundValue(valueX);
                duration = this.getDataItemDuration(dataItem);
                dataItem.set("duration", duration);
            }
            else if (keep === "end") {
                openValueX = this.roundValue(openValueX, true);
                duration = this.getDataItemDuration(dataItem);
                dataItem.set("duration", duration);
            }
            else if (keep === "duration") {
                openValueX = this.roundValue(openValueX, true);
                valueX = openValueX + duration * this.getUnitDuration();
            }
            openValueX = this.getOpenValue(openValueX);
            valueX = this.getEndValue(openValueX, duration);
            const animationDuration = this.get("interpolationDuration", 0);
            const animationEasing = this.get("interpolationEasing");
            const index = this.dataItems.indexOf(dataItem);
            if (index >= this.startIndex() && index <= this.endIndex()) {
                dataItem.animate({ key: "openValueXWorking", to: openValueX, duration: animationDuration, easing: animationEasing });
                dataItem.animate({ key: "valueXWorking", to: valueX, duration: animationDuration, easing: animationEasing });
                // animate not working also
                dataItem.animate({ key: "openValueX", to: openValueX, duration: animationDuration, easing: animationEasing });
                dataItem.animate({ key: "valueX", to: valueX, duration: animationDuration, easing: animationEasing });
            }
            else {
                dataItem.set("openValueXWorking", openValueX);
                dataItem.set("valueXWorking", valueX);
                dataItem.set("openValueX", openValueX);
                dataItem.set("valueX", valueX);
            }
        }
    }
    _toggleColumn(dataItem, visible) {
        super._toggleColumn(dataItem, visible);
        const container = dataItem.get("container");
        if (container) {
            container.setPrivate("visible", visible);
        }
    }
    _afterDataChange() {
        const yAxis = this.get("yAxis");
        $array.each(this.dataItems, (dataItem) => {
            this._fixValues(dataItem, "duration");
            const category = dataItem.get("categoryY");
            if (category) {
                const categoryAxisDataItem = yAxis.dataItems.find((item) => item.get("category") === category);
                dataItem.set("categoryAxisDataItem", categoryAxisDataItem);
                if (categoryAxisDataItem) {
                    categoryAxisDataItem.on("duration", () => {
                        const duration = categoryAxisDataItem.get("duration");
                        const children = dataItem.get("children");
                        if (!children || children.length === 0) {
                            if (duration !== undefined) {
                                dataItem.set("duration", duration);
                                this._fixValues(dataItem, "duration");
                            }
                        }
                    });
                }
            }
        });
        this.get("yAxis").markDirtyTree();
        super._afterDataChange();
    }
    _updateLinks() {
        const xAxis = this.get("xAxis");
        const yAxis = this.get("yAxis");
        const yRenderer = yAxis.get("renderer");
        let endRadius = 0;
        const endBullet = this.endBullets.getIndex(0);
        if (endBullet && endBullet.isVisible()) {
            endRadius = endBullet.get("radius", 0);
        }
        let columnHeight = percent(60);
        let minHeight = 0;
        let maxHeight = Infinity;
        let column = this.columns.getIndex(0);
        let r = 1;
        if (column) {
            columnHeight = column.get("height", 0);
            const cos = $math.cos(45);
            if (columnHeight instanceof Percent) {
                r = columnHeight.value * cos * cos;
            }
            minHeight = column.get("minHeight", 0) * cos * cos;
            maxHeight = column.get("maxHeight", Infinity) * cos * cos;
        }
        $array.each(this.dataItems, (dataItem) => {
            const linkTo = dataItem.get("linkTo");
            const categoryDataItem = dataItem.get("categoryAxisDataItem");
            if (linkTo) {
                let links = dataItem.get("links");
                const linkHorizontalOffset = this.get("linkHorizontalOffset", 25);
                $array.each(linkTo, (dataItemId) => {
                    if (!links) {
                        links = dataItem.set("links", {});
                    }
                    let link = links[dataItemId];
                    if (!link) {
                        link = this.links.make();
                        links[dataItemId] = link;
                        this.links.push(link);
                        this.linksContainer.children.push(link);
                        link.appear();
                        link.events.on("click", () => {
                            if (link.get("active")) {
                                this.links.removeValue(link);
                                const links = dataItem.get("links");
                                if (links) {
                                    delete links[dataItemId];
                                }
                                $array.remove(linkTo, dataItemId);
                                this.markDirtyValues();
                                link.dispose();
                            }
                        });
                        link.events.on("globalpointerup", () => {
                            if (link.get("active") && !link.isHover()) {
                                link.set("active", false);
                            }
                        });
                    }
                    const points = [];
                    const positionX = xAxis.valueToPosition(dataItem.get("valueX", 0));
                    const positionY = yAxis.categoryToPosition(dataItem.get("categoryY"));
                    const point = this.getPoint(positionX, positionY);
                    let firstPoint = { x: point.x + endRadius, y: point.y };
                    if (dataItem.get("duration", 0) === 0) {
                        let y0 = yAxis.indexToPosition(categoryDataItem.get("index", 0), 0);
                        let y1 = yAxis.indexToPosition(categoryDataItem.get("index", 0), 1);
                        y0 = yRenderer.positionToCoordinate(y0);
                        y1 = yRenderer.positionToCoordinate(y1);
                        let h = (y1 - y0) * r;
                        h = $math.fitToRange(h, minHeight, maxHeight);
                        firstPoint.x += h - endRadius;
                    }
                    points.push(firstPoint);
                    points.push({ x: firstPoint.x + linkHorizontalOffset, y: firstPoint.y });
                    const targetDataItem = this.getDataItemById(dataItemId);
                    if (targetDataItem) {
                        const positionX = xAxis.valueToPosition(targetDataItem.get("openValueX", 0));
                        const positionY = yAxis.categoryToPosition(targetDataItem.get("categoryY"));
                        const point = this.getPoint(positionX, positionY);
                        let x = point.x;
                        let y = point.y;
                        let lastPoint = { x: x, y: y };
                        if (targetDataItem.get("duration", 0) === 0) {
                            let targetCategoryDataItem = targetDataItem.get("categoryAxisDataItem");
                            if (targetCategoryDataItem) {
                                let y0 = yAxis.indexToPosition(targetCategoryDataItem.get("index", 0), 0);
                                let y1 = yAxis.indexToPosition(targetCategoryDataItem.get("index", 0), 1);
                                y0 = yRenderer.positionToCoordinate(y0);
                                y1 = yRenderer.positionToCoordinate(y1);
                                let h = (y1 - y0) * r;
                                h = $math.fitToRange(h, minHeight, maxHeight);
                                lastPoint.x -= h;
                            }
                        }
                        if (lastPoint.x - linkHorizontalOffset < firstPoint.x + linkHorizontalOffset) {
                            const category = dataItem.get("categoryY");
                            const yAxis = this.get("yAxis");
                            if (category) {
                                let index = yAxis.categoryToIndex(category);
                                const position0 = yAxis.indexToPosition(index);
                                const position1 = yAxis.indexToPosition(index, 1);
                                if (yAxis && category) {
                                    let sign = -1;
                                    if (lastPoint.y > firstPoint.y) {
                                        sign = 1;
                                    }
                                    let y = yAxis.get("renderer").positionToCoordinate(yAxis.categoryToPosition(category) + (position1 - position0) * sign);
                                    points.push({ x: firstPoint.x + linkHorizontalOffset, y: y });
                                }
                            }
                        }
                        points.push({ x: lastPoint.x - linkHorizontalOffset, y: lastPoint.y });
                        points.push(lastPoint);
                    }
                    link.set("points", points);
                });
            }
        });
    }
    /**
     * Updates series graphics.
     */
    _updateSeriesGraphics(dataItem, graphics, l, r, t, b, _fitW, fitH) {
        super._updateSeriesGraphics(dataItem, graphics, l, r, t, b, false, fitH);
        let x = dataItem.get("left", 0);
        let y = dataItem.get("top", 0);
        let width = dataItem.get("right", 0) - x;
        let height = dataItem.get("bottom", 0) - y;
        // Update the container position and size
        const container = dataItem.get("container");
        const mask = dataItem.get("mask");
        const categoryAxisDataItem = dataItem.get("categoryAxisDataItem");
        const fill = categoryAxisDataItem ? categoryAxisDataItem.get("customColor", categoryAxisDataItem.get("color")) : undefined;
        dataItem.setRaw("name", categoryAxisDataItem.dataContext.name);
        const startBullet = dataItem.get("startBullet");
        startBullet === null || startBullet === void 0 ? void 0 : startBullet.setAll({
            x: 0,
            stroke: fill
        });
        const endBullet = dataItem.get("endBullet");
        endBullet === null || endBullet === void 0 ? void 0 : endBullet.setAll({
            stroke: fill,
            x: width
        });
        if (container) {
            container.setAll({
                x: x,
                y: y,
                width: width,
                height: height
            });
        }
        const maskedContainer = dataItem.get("maskedContainer");
        if (maskedContainer) {
            maskedContainer.setAll({
                x: 0,
                y: 0,
                width: width,
                height: height
            });
        }
        if (mask) {
            mask.setAll({
                x: 0,
                y: 0,
                width: width,
                height: height
            });
        }
        const startGrip = dataItem.get("startGrip");
        if (startGrip) {
            startGrip.setAll({
                height: height
            });
        }
        const endGrip = dataItem.get("endGrip");
        if (endGrip) {
            endGrip.setAll({
                x: width,
                height: height
            });
        }
        // update progress rectangle
        const progressRectangle = dataItem.get("progressRectangle");
        if (progressRectangle) {
            const progress = dataItem.get("progress", 0);
            const progressWidth = width - width * progress;
            progressRectangle.setAll({
                x: width - progressWidth,
                width: progressWidth,
                height: height
            });
            const progressGrip = dataItem.get("progressGrip");
            if (progressGrip) {
                progressGrip.setAll({
                    x: width - progressWidth
                });
            }
            const zeroRectangle = dataItem.get("zeroRectangle");
            if (zeroRectangle) {
                zeroRectangle.markDirtyKey("fillPattern");
            }
        }
    }
    /**
     * 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 container = dataItem.get("container");
            if (container) {
                promises.push(container.hide(duration));
            }
            // hide all links
            const links = dataItem.get("links", {});
            if (links) {
                $object.each(links, (_key, link) => {
                    link.hide(duration);
                });
            }
            // hide all links to this data item
            const id = dataItem.get("id");
            if (id) {
                $array.each(this.dataItems, (item) => {
                    const linkTo = item.get("linkTo");
                    if (linkTo && linkTo.includes(id)) {
                        const links = item.get("links");
                        if (links && links[id]) {
                            const link = links[id];
                            link.hide(duration);
                        }
                    }
                });
            }
            yield Promise.all(promises);
        });
    }
    /**
     * 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 container = dataItem.get("container");
            if (container) {
                promises.push(container.show(duration));
            }
            // show all links
            const links = dataItem.get("links", {});
            if (links) {
                $object.each(links, (_key, link) => {
                    link.show(duration);
                });
            }
            // show all links to this data item
            const id = dataItem.get("id");
            if (id) {
                $array.each(this.dataItems, (item) => {
                    const linkTo = item.get("linkTo");
                    if (linkTo && linkTo.includes(id)) {
                        const links = item.get("links");
                        if (links && links[id]) {
                            const link = links[id];
                            link.show(duration);
                        }
                    }
                });
            }
            yield Promise.all(promises);
        });
    }
    /**
     * Disposes a data item and removes it from the series.
     *
     * @param dataItem  Data item
     */
    disposeDataItem(dataItem) {
        super.disposeDataItem(dataItem);
        const container = dataItem.get("container");
        if (container) {
            this.containers.removeValue(container);
            container.dispose();
        }
        const startBullet = dataItem.get("startBullet");
        if (startBullet) {
            this.startBullets.removeValue(startBullet);
            startBullet.dispose();
        }
        const endBullet = dataItem.get("endBullet");
        if (endBullet) {
            this.endBullets.removeValue(endBullet);
            endBullet.dispose();
        }
        const startGrip = dataItem.get("startGrip");
        if (startGrip) {
            this.startGrips.removeValue(startGrip);
            startGrip.dispose();
        }
        const endGrip = dataItem.get("endGrip");
        if (endGrip) {
            this.endGrips.removeValue(endGrip);
            endGrip.dispose();
        }
        const progressRectangle = dataItem.get("progressRectangle");
        if (progressRectangle) {
            this.progressRectangles.removeValue(progressRectangle);
            progressRectangle.dispose();
        }
        const zeroRectangle = dataItem.get("zeroRectangle");
        if (zeroRectangle) {
            this.zeroRectangles.removeValue(zeroRectangle);
            zeroRectangle.dispose();
        }
        const progressGrip = dataItem.get("progressGrip");
        if (progressGrip) {
            this.progressGrips.removeValue(progressGrip);
            progressGrip.dispose();
        }
        this.disposeLinks(dataItem);
    }
    /**
     * Disposes all links of a data item and removes them from the series.
     *
     * @param dataItem  Data item
     */
    disposeLinks(dataItem) {
        const links = dataItem.get("links", {});
        if (links) {
            $object.each(links, (key, link) => {
                link.dispose();
                delete links[key];
            });
            dataItem.set("linkTo", []);
        }
        const id = dataItem.get("id", "");
        if (id) {
            $array.each(this.dataItems, (item) => {
                const linkTo = item.get("linkTo");
                if (linkTo && linkTo.includes(id)) {
                    $array.remove(linkTo, id);
                    const links = item.get("links");
                    if (links && links[id]) {
                        const link = links[id];
                        this.links.removeValue(link);
                        delete links[id];
                        link.dispose();
                    }
                }
            });
        }
    }
    /**
     * Returns the duration of a data item in units.
     *
     * @param dataItem  Data item
     * @return Duration in units
     */
    getDataItemDuration(dataItem) {
        const openValueX = dataItem.get("openValueX", 0);
        let valueX = dataItem.get("valueX", 0);
        // if valueX is within weekend or holiday, round it to the end of a previous work day
        return Math.round((valueX - openValueX) / this.getUnitDuration()) - this.getHolidayCount(openValueX, valueX);
    }
    /**
     * Returns the depth of a data item in the hierarchy.
     *
     * @param dataItem  Data item
     * @return Depth level
     */
    getDepth(dataItem) {
        let depth = 0;
        let parent = dataItem.get("parent");
        while (parent) {
            depth++;
            parent = parent.get("parent");
        }
        return depth;
    }
    /**
     * Returns the end value of a data item, adjusting for holidays and weekends.
     *
     * @param openValue  Open value in milliseconds
     * @param duration   Duration in units
     * @return           Adjusted end value in milliseconds
     */
    getEndValue(openValue, duration) {
        let adjustedDuration = duration;
        const unitDuration = this.getUnitDuration();
        if (this.gantt.get("excludeWeekends", true)) {
            for (let i = 0; i < adjustedDuration; i++) {
                if (this.isHoliday(openValue + i * unitDuration)) {
                    adjustedDuration++;
                }
            }
        }
        return openValue + adjustedDuration * unitDuration;
    }
    /**
     * Returns the open value of a data item, adjusting for holidays and weekends.
     *
     * @param openValue  Open value in milliseconds
     * @return           Adjusted open value
     */
    getOpenValue(openValue) {
        if (this.gantt.get("excludeWeekends", true)) {
            while (this.isHoliday(openValue)) {
                openValue += this.getUnitDuration();
            }
        }
        return openValue;
    }
    /**
     * Returns the duration of a unit in milliseconds.
     *
     * @return Duration in milliseconds
     */
    getUnitDuration() {
        return $time.getDuration(this.gantt.get("durationUnit", "day"));
    }
    /**
     * Returns the count of holidays between two dates.
     *
     * @param start  Start date in milliseconds
     * @param end    End date in milliseconds
     * @return       Count of holidays
     */
    getHolidayCount(start, end) {
        let count = 0;
        let holiday = true;
        for (let i = end; i >= start; i -= this.getUnitDuration()) {
            if (!this.isHoliday(i)) {
                holiday = false;
            }
            else {
                if (!holiday && this.isHoliday(i)) {
                    count++;
                }
            }
        }
        return count;
    }
    /**
         * Checks if a given date is a holiday or weekend.
         *
         * @param value  Date value in milliseconds
         * @return       `true` if the date is a holiday or weekend, otherwise `false`
         */
    isHoliday(value) {
        if (this.gantt.get("excludeWeekends", true)) {
            const holidays = this.gantt.get("holidays", []);
            const weekends = this.gantt.get("weekends", [0, 6]);
            const date = new Date(value);
            if (weekends.includes(date.getDay())) {
                return true;
            }
            if (holidays && holidays.length > 0) {
                let isHoliday = false;
                $array.eachContinue(holidays, (holiday) => {
                    if (holiday instanceof Date) {
                        const start = holiday.getTime();
                        const end = holiday.getTime() + $time.getDuration("day");
                        if (value >= start && value < end) {
                            isHoliday = true;
                            return false;
                        }
                    }
                    return true;
                });
                return isHoliday;
            }
        }
        return false;
    }
}
GanttSeries.className = "GanttSeries";
GanttSeries.classNames = ColumnSeries.classNames.concat([GanttSeries.className]);
//# sourceMappingURL=GanttSeries.js.map