"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.SankeyChartBase = exports.preRenderLayout = exports.adjustPadding = exports.groupNodesByColumn = void 0;
var tslib_1 = require("tslib");
var react_focus_1 = require("@fluentui/react-focus");
var Callout_1 = require("@fluentui/react/lib/Callout");
var Utilities_1 = require("@fluentui/react/lib/Utilities");
var d3_array_1 = require("d3-array");
var d3_sankey_1 = require("d3-sankey");
var d3_selection_1 = require("d3-selection");
var d3_shape_1 = require("d3-shape");
var React = require("react");
var ChartHoverCard_1 = require("../../utilities/ChartHoverCard/ChartHoverCard");
var image_export_utils_1 = require("../../utilities/image-export-utils");
var getClassNames = (0, Utilities_1.classNamesFunction)();
var PADDING_PERCENTAGE = 0.3;
var NON_SELECTED_NODE_AND_STREAM_COLOR = '#757575';
var DEFAULT_NODE_COLORS = [
    { fillColor: '#00758F', borderColor: '#002E39' },
    { fillColor: '#77004D', borderColor: '#43002C' },
    { fillColor: '#4F6BED', borderColor: '#3B52B4' },
    { fillColor: '#937600', borderColor: '#6D5700' },
    { fillColor: '#286EA8', borderColor: '#00457E' },
    { fillColor: '#A43FB1', borderColor: '#7C158A' },
    { fillColor: '#CC3595', borderColor: '#7F215D' },
    { fillColor: '#0E7878', borderColor: '#004E4E' },
    { fillColor: '#8764B8', borderColor: '#4B3867' },
    { fillColor: '#9C663F', borderColor: '#6D4123' },
];
var MIN_HEIGHT_FOR_DOUBLINE_TYPE = 36;
var MIN_HEIGHT_FOR_TYPE = 24;
var REST_STREAM_OPACITY = 1;
var NON_SELECTED_OPACITY = 1;
var SELECTED_STREAM_OPACITY = 0.3;
var NON_SELECTED_STREAM_BORDER_OPACITY = 0.5;
var DEFAULT_TEXT_COLOR = '#323130';
var NON_SELECTED_TEXT_COLOR = '#FFFFFF';
var NODE_WIDTH = 124;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function getSelectedNodes(selectedLinks) {
    var nodes = [];
    selectedLinks.forEach(function (link) {
        nodes.push(link.target);
        if (nodes.indexOf(link.source) === -1) {
            nodes.push(link.source);
        }
    });
    return nodes;
}
function getSelectedLinks(singleNode) {
    // eslint-disable-next-line @typescript-eslint/no-explicit-any, no-array-constructor
    var q = new Array();
    var finalLinks = new Set();
    singleNode.sourceLinks.forEach(function (link) {
        q.push(link);
        finalLinks.add(link);
    });
    while (q.length > 0) {
        var poppedLink = q.shift();
        var node = poppedLink.target;
        if (node && node.sourceLinks) {
            node.sourceLinks.forEach(function (link) {
                finalLinks.add(link);
                q.push(link);
            });
        }
    }
    if (singleNode.targetLinks) {
        singleNode.targetLinks.forEach(function (link) {
            q.push(link);
            finalLinks.add(link);
        });
    }
    while (q.length > 0) {
        var poppedLink = q.shift();
        var node = poppedLink.source;
        if (node && node.targetLinks) {
            node.targetLinks.forEach(function (link) {
                finalLinks.add(link);
                q.push(link);
            });
        }
    }
    return finalLinks;
}
function getSelectedLinksforStreamHover(singleLink) {
    // eslint-disable-next-line @typescript-eslint/no-explicit-any, no-array-constructor
    var q = new Array();
    var finalLinks = new Set();
    var finalNodes = new Set();
    q.push(singleLink.source);
    finalLinks.add(singleLink);
    while (q.length > 0) {
        var poppedNode = q.shift();
        finalNodes.add(poppedNode);
        if (poppedNode.targetLinks && poppedNode.targetLinks.length > 0) {
            poppedNode.targetLinks.forEach(function (link) {
                q.push(link.source);
                finalLinks.add(link);
            });
        }
    }
    q.push(singleLink.target);
    while (q.length > 0) {
        var poppedNode = q.shift();
        finalNodes.add(poppedNode);
        if (poppedNode.sourceLinks && poppedNode.sourceLinks.length > 0) {
            poppedNode.sourceLinks.forEach(function (link) {
                q.push(link.target);
                finalLinks.add(link);
            });
        }
    }
    return {
        selectedLinks: finalLinks,
        selectedNodes: finalNodes,
    };
}
/**
 * This is used to group nodes by column index.
 */
// This is exported for unit tests.
function groupNodesByColumn(graph) {
    var nodesInColumn = {};
    graph.nodes.forEach(function (node) {
        var columnId = node.layer;
        if (nodesInColumn[columnId]) {
            nodesInColumn[columnId].push(node);
        }
        else {
            nodesInColumn[columnId] = [node];
        }
    });
    return nodesInColumn;
}
exports.groupNodesByColumn = groupNodesByColumn;
/**
 * This is used to normalize the nodes value whose value is less than 1% of the total column value.
 */
function adjustOnePercentHeightNodes(nodesInColumn, computedNodes, originalLinks) {
    var totalColumnValue = Object.values(nodesInColumn).map(function (column) {
        return (0, d3_array_1.sum)(column, function (node) { return node.value; });
    });
    totalColumnValue.forEach(function (columnValue, index) {
        var totalPercentage = 0;
        var onePercent = 0.01 * columnValue;
        var columnNodes = nodesInColumn[index];
        columnNodes.forEach(function (node) {
            var value = computedNodes[node.nodeId];
            var nodePercentage = (value / columnValue) * 100;
            node.actualValue = value;
            //if the value is less than 1% then we are making it as 1% of total .
            if (nodePercentage < 1) {
                node.value = onePercent;
                totalPercentage = totalPercentage + 1;
            }
            else {
                totalPercentage = totalPercentage + nodePercentage;
            }
        });
        //since we have adjusted the value to be 1% but we need to keep the sum of the percentage value under 100.
        var scalingRatio = totalPercentage !== 0 ? totalPercentage / 100 : 1;
        if (scalingRatio > 1) {
            // Loop through each node in that column and scale that node--and its incoming and outgoing links--by the
            // scaling ratio. We need the sankey diagram to re-layout the nodes and links after we do this.
            columnNodes.forEach(function (node) {
                var normalized = (node.value = node.value / scalingRatio);
                // Which Original Value? and Which Normalized Value is needed, here? The Node? The Link? Both?
                changeColumnValue(node, computedNodes[node.nodeId], normalized, originalLinks);
            });
        }
    });
}
/**
 * This is used for normalizing each link's value to reflect the normalized node value.
 */
function changeColumnValue(node, originalNodeValue, normalizedNodeValue, linkValues) {
    // For each link in the source and target, compute the proportion that this link contributes to the total
    // then adjust the link's value to reflect its proportion of the normalized node value.
    var updateLinkValue = function (link) {
        var value = linkValue(linkValues, link);
        link.unnormalizedValue = value;
        var linkRatio = value / originalNodeValue;
        link.value = Math.max(normalizedNodeValue * linkRatio, link.value);
    };
    node.sourceLinks.forEach(updateLinkValue);
    node.targetLinks.forEach(updateLinkValue);
}
/**
 * This is used for calculating the node non normalized value based on link non normalized value.
 * The links have the original weights. Computed nodes have the total weight of all incoming and outgoing links.
 */
function populateNodeActualValue(data, computedNodes, originalLinks) {
    data.links.forEach(function (link) {
        if (!link.unnormalizedValue) {
            link.unnormalizedValue = linkValue(originalLinks, link);
        }
    });
    data.nodes.forEach(function (node) {
        node.actualValue = computedNodes[node.nodeId];
    });
}
/**
 * This is used to introduce dynamic padding for cases where the number of nodes in a column is huge
 * so that we maintain a node to space ratio for such columns as if we fail to do so the
 * chart is devoid of nodes and only shows links.
 */
// This is exported for unit tests
function adjustPadding(sankey, height, nodesInColumn) {
    var padding = sankey.nodePadding();
    var minPadding = PADDING_PERCENTAGE * height;
    Object.values(nodesInColumn).forEach(function (column) {
        var totalPaddingInColumn = height - (0, d3_array_1.sum)(column, function (node) { return node.y1 - node.y0; });
        if (minPadding < totalPaddingInColumn) {
            // Here we are calculating the min of default and calculated padding, we will not increase the padding
            // in any scenario.
            padding = Math.min(padding, minPadding / (column.length - 1));
        }
    });
    sankey.nodePadding(padding);
}
exports.adjustPadding = adjustPadding;
function idFromNumberOrSNode(node) {
    if (typeof node === 'number') {
        return node;
    }
    return node.nodeId;
}
/**
 * Duplicates the supplied chart data so that we do not alter the original.
 * @param data The data to duplicate.
 * @returns The duplicated data.
 */
function duplicateData(data) {
    return {
        nodes: data.nodes.map(function (node) { return (tslib_1.__assign({}, node)); }),
        links: data.links.map(function (link) { return (tslib_1.__assign({}, link)); }),
    };
}
function valuesOfNodes(nodes) {
    var result = {};
    nodes.forEach(function (node) {
        result[node.nodeId] = node.value;
    });
    return result;
}
function valuesOfLinks(links) {
    var result = {};
    links.forEach(function (link) {
        var sourceId = idFromNumberOrSNode(link.source);
        var sourceToTarget = result[sourceId];
        if (!sourceToTarget) {
            sourceToTarget = {};
            result[sourceId] = sourceToTarget;
        }
        sourceToTarget[idFromNumberOrSNode(link.target)] = link.value;
    });
    return result;
}
function linkValue(originalLinks, link) {
    return originalLinks[idFromNumberOrSNode(link.source)][idFromNumberOrSNode(link.target)];
}
// This is exported for unit tests.
function preRenderLayout(margins, containerWidth, containerHeight, isRtl) {
    var left = margins.left, right = margins.right, top = margins.top, bottom = margins.bottom;
    var width = containerWidth - right;
    var height = containerHeight - bottom > 0 ? containerHeight - bottom : 0;
    var sankey = (0, d3_sankey_1.sankey)()
        .nodeWidth(NODE_WIDTH)
        .extent([
        [left, top],
        [width - 1, height - 6],
    ])
        .nodeAlign(isRtl ? d3_sankey_1.sankeyRight : d3_sankey_1.sankeyJustify);
    return { sankey: sankey, height: height, width: width };
}
exports.preRenderLayout = preRenderLayout;
var elipsis = '...';
/**
 * This is used to assign node fillcolors and borderColor cyclically when the user doesnt
 * provide color to  individual node.
 */
function assignNodeColors(nodes, colorsForNodes, borderColorsForNodes) {
    var colors;
    var borders;
    if (colorsForNodes && borderColorsForNodes) {
        colors = colorsForNodes;
        borders = borderColorsForNodes;
    }
    else {
        colors = DEFAULT_NODE_COLORS.map(function (color) { return color.fillColor; });
        borders = DEFAULT_NODE_COLORS.map(function (color) { return color.borderColor; });
    }
    var currentIndex = 0;
    nodes.forEach(function (node) {
        if (!node.color && !node.borderColor) {
            node.color = colors[currentIndex];
            node.borderColor = borders[currentIndex];
        }
        else if (node.color && !node.borderColor) {
            node.borderColor = '#757575';
        }
        else if (node.borderColor && !node.color) {
            node.color = '#F5F5F5';
        }
        currentIndex = (currentIndex + 1) % colors.length;
    });
}
/**
 * Takes in the display name for the node and potentially returns a trimmed version of the name.
 * @param tspan the `tspan` element to use for text visual length measurement
 * @param text is the text which we will potentially truncate
 * @param rectangleWidth is the width of the rectangle which will contain the text
 * @param padding is the space we need to leave between the rect lines and other text
 * @returns the name to show on the node which might be the truncated `text` if the `text` is too long
 */
function truncateText(tspan, text, rectangleWidth, padding) {
    // NOTE: This method is the most-expensive in terms of rerendering components.
    var textLengthForNodeName = rectangleWidth - padding; // This can likely be computed once and passed in.
    // The following `select` statement injects a `tempText` element into the DOM. This injection
    // (and subsequent removal) is causing a layout recalculation. This is a performance issue.
    // Note that this code will always inject a `tempText` element, but doesn't always remove it. This is a bug.
    if (fitsWithinNode(tspan, text, textLengthForNodeName)) {
        return text;
    }
    // Computing the size of elipsis is performed with each node. This should be computed once and used everywhere.
    // TODO: Compute the size of the elipsis once and use it everywhere.
    var elipsisLength = computeElipsisLength(tspan);
    var line = '';
    // Calculate how much of the original text to show.
    // TODO: The folllowing is O(n). We could use a binary search to make this faster: O(log(n)).
    for (var i = 0; i < text.length; i++) {
        line += text[i];
        tspan.text(line);
        var currentNode = tspan.node();
        if (currentNode !== null) {
            var w = currentNode.getComputedTextLength();
            if (w >= textLengthForNodeName - elipsisLength) {
                line = line.slice(0, -1);
                line += elipsis;
                break;
            }
        }
    }
    tspan.text(null);
    return line;
}
function fitsWithinNode(tspan, text, textLengthForNodeName) {
    var measurement = measureText(tspan, text);
    if (measurement === undefined) {
        return false;
    }
    return measurement <= textLengthForNodeName;
}
function measureText(tspan, text) {
    var _a;
    try {
        tspan.text(text);
        return (_a = tspan.node()) === null || _a === void 0 ? void 0 : _a.getComputedTextLength();
    }
    finally {
        tspan.text(null);
    }
}
function computeElipsisLength(tspan) {
    var measurement = measureText(tspan, elipsis);
    return measurement === undefined ? 0 : measurement;
}
function computeLinkAttributes(links, linkFrom, linkAriaLabel) {
    var result = {};
    links.forEach(function (link) {
        var sourceId = idFromNumberOrSNode(link.source);
        var sourceToTarget = result[sourceId];
        if (!sourceToTarget) {
            sourceToTarget = {};
            result[sourceId] = sourceToTarget;
        }
        sourceToTarget[idFromNumberOrSNode(link.target)] = {
            reactId: (0, Utilities_1.getId)('link'),
            from: linkFrom(link.source),
            aria: linkAriaLabel(link),
        };
    });
    return result;
}
var linkToDataPoints = function (d) {
    var halfWidth = d.width * 0.5;
    var y0 = d.y0;
    var y1 = d.y1;
    return [
        { x: d.source.x1, y0: y0 + halfWidth, y1: y0 - halfWidth },
        { x: d.target.x0, y0: y1 + halfWidth, y1: y1 - halfWidth },
    ];
};
var linkArea = (0, d3_shape_1.area)()
    .x(function (p) { return p.x; })
    .y0(function (p) { return p.y0; })
    .y1(function (p) { return p.y1; })
    .curve(d3_shape_1.curveBumpX);
function nodeTextColor(state, singleNode) {
    return !(!state.selectedState ||
        (state.selectedNodes.has(singleNode.index) && state.selectedNode) ||
        !state.selectedNode)
        ? DEFAULT_TEXT_COLOR
        : NON_SELECTED_TEXT_COLOR;
}
// NOTE: To start employing React.useMemo properly, we need to convert this code from a React.Component
// to a function component. This will require a significant refactor of the code in this file.
// https://stackoverflow.com/questions/60223362/fast-way-to-convert-react-class-component-to-functional-component
// I am concerned that doing so would break this contract, making it difficult for consuming code.
var SankeyChartBase = /** @class */ (function (_super) {
    tslib_1.__extends(SankeyChartBase, _super);
    function SankeyChartBase(props) {
        var _this = _super.call(this, props) || this;
        _this._isRtl = (0, Utilities_1.getRTL)();
        _this._numColumns = 0;
        _this.toImage = function (opts) {
            return (0, image_export_utils_1.toImage)(_this.chartContainer, undefined, _this._isRtl, opts);
        };
        _this._onCloseCallout = function () {
            _this.setState({
                isCalloutVisible: false,
                refSelected: undefined,
                descriptionMessage: '',
            });
        };
        _this._onBlur = function () {
            /**/
        };
        _this._fillNodeColors = function (singleNode) {
            var state = _this.state;
            if (!state.selectedState) {
                return singleNode.color;
            }
            else {
                var selectedNode = state.selectedNode;
                if (selectedNode && state.selectedNodes.has(singleNode.index)) {
                    return selectedNode.color;
                }
                else if (!selectedNode) {
                    return singleNode.color;
                }
            }
        };
        _this._fillNodeBorder = function (singleNode) {
            var state = _this.state;
            if (!state.selectedState) {
                return singleNode.borderColor;
            }
            else {
                if (state.selectedNodes.has(singleNode.index)) {
                    var selectedNode = state.selectedNode;
                    return selectedNode ? selectedNode.borderColor : singleNode.borderColor;
                }
                return singleNode.borderColor;
            }
        };
        _this._calculateChartMinWidth = function () {
            return (_this._margins.left +
                _this._margins.right +
                // total width of all node columns
                _this._numColumns * NODE_WIDTH +
                // minimum total width of all column gaps
                (_this._numColumns - 1) * (NODE_WIDTH / 2));
        };
        (0, Utilities_1.initializeComponentRef)(_this);
        _this.state = {
            containerHeight: 468,
            containerWidth: 912,
            selectedState: false,
            selectedLinks: new Set(),
            selectedNodes: new Set(),
            shouldOverflow: false,
            isCalloutVisible: false,
        };
        _this._calloutId = (0, Utilities_1.getId)('callout');
        _this._linkId = (0, Utilities_1.getId)('link');
        _this._chartId = (0, Utilities_1.getId)('sankeyChart');
        _this._emptyChartId = (0, Utilities_1.getId)('_SankeyChart_empty');
        _this._labelTooltipId = (0, Utilities_1.getId)('tooltip');
        _this._margins = { top: 36, right: 48, bottom: 32, left: 48 };
        // We memo-ize creation so that we only create a new object when any of the fields change.
        _this._computeClassNamesProps = (0, Utilities_1.memoizeFunction)(function (theme, pathColor, className, containerWidth, containerHeight, enableReflow) { return ({
            theme: theme,
            width: containerWidth,
            height: containerHeight,
            pathColor: pathColor,
            className: className,
            enableReflow: enableReflow,
        }); });
        // `getClassNames` is memoized underneath, so it only recomputes when the `styles` or `classNamesProps` change.
        // We memoize `classNamesProps` so that we only create new class names when absolutely necessary,
        // and so that we can memoize the `_createNodes` method.
        _this._computeClassNames = (0, Utilities_1.memoizeFunction)(function (styles, classNamesProps) { return getClassNames(styles, classNamesProps); });
        _this._normalizeData = (0, Utilities_1.memoizeFunction)(function (data, containerWidth, containerHeight, colorsForNodes, borderColorsForNodes) { return _this._normalizeSankeyData(data, containerWidth, containerHeight, colorsForNodes, borderColorsForNodes); });
        _this._fetchTooltip = (0, Utilities_1.memoizeFunction)(function (classNames) {
            return _this._labelTooltipDiv(classNames);
        });
        // Prepare the localization utilities
        _this._strings = (0, Utilities_1.memoizeFunction)(function (strings) {
            var fromString = (strings === null || strings === void 0 ? void 0 : strings.linkFrom) || 'From {0}';
            // NOTE: The `node` parameter is the sankey-generated node on the link, and not the original `node` supplied
            // by the caller.
            return {
                linkFrom: function (node) { return (0, Utilities_1.format)(fromString, node.name); },
            };
        })(props.strings);
        _this._accessibility = (0, Utilities_1.memoizeFunction)(function (accessibility) {
            var linkString = (accessibility === null || accessibility === void 0 ? void 0 : accessibility.linkAriaLabel) || 'link from {0} to {1} with weight {2}';
            var nodeString = (accessibility === null || accessibility === void 0 ? void 0 : accessibility.nodeAriaLabel) || 'node {0} with weight {1}';
            return {
                emptyAriaLabel: (accessibility === null || accessibility === void 0 ? void 0 : accessibility.emptyAriaLabel) || 'Graph has no data to display',
                linkAriaLabel: function (link) {
                    return (0, Utilities_1.format)(linkString, link.source.name, link.target.name, link.unnormalizedValue ? _this._formatNumber(link.unnormalizedValue) : link.unnormalizedValue);
                },
                nodeAriaLabel: function (node, weight) { return (0, Utilities_1.format)(nodeString, node.name, _this._formatNumber(weight)); },
            };
        })(props.accessibility);
        // NOTE: Memoizing the `_createNodes` and `_createLinks` methods would break the hoverability of the chart
        // because the nodes are currently created differently based on the layout information. Hence why we do not
        // memoize these methods (but have stubs for memoizing as the `_fetchNodes` and `_fetchLinks` methods).
        _this._nodeAttributes = (0, Utilities_1.memoizeFunction)(function (nodes, nodeAriaLabel) {
            return _this._computeNodeAttributes(nodes, nodeAriaLabel);
        });
        _this._fetchNodes = function (classNames, nodes, nodeAttributes, tooltipDiv) { return _this._createNodes(classNames, nodes, nodeAttributes, tooltipDiv); };
        _this._linkAttributes = (0, Utilities_1.memoizeFunction)(function (links, linkFrom, linkAriaLabel) {
            return computeLinkAttributes(links, linkFrom, linkAriaLabel);
        });
        _this._fetchLinks = function (links, linkAttributes) {
            return _this._createLinks(links, linkAttributes);
        };
        return _this;
        // Our shorter path to performance is to pre-compute the truncated labels of each node because
        // that should not change based on the position of the mouse. This is a shorter path becase the code which
        // computes the truncated labels creates and destroys a `tempText` element in the DOM. This is causing a
        // reflow and repaint of the entire chart. This is a performance issue, and so computing the truncated labels
        // once will help to mitigate this issue.
    }
    SankeyChartBase.prototype.componentDidMount = function () {
        this._fitParentContainer();
    };
    SankeyChartBase.prototype.componentDidUpdate = function (prevProps) {
        if (prevProps.shouldResize !== this.props.shouldResize) {
            this._fitParentContainer();
        }
    };
    SankeyChartBase.prototype.componentWillUnmount = function () {
        cancelAnimationFrame(this._reqID);
    };
    SankeyChartBase.prototype.render = function () {
        var _this = this;
        if (!this._isChartEmpty()) {
            var _a = this.props, theme = _a.theme, className = _a.className, styles = _a.styles, pathColor = _a.pathColor, colorsForNodes = _a.colorsForNodes, borderColorsForNodes = _a.borderColorsForNodes;
            var state = this.state;
            var classNamesProps = this._computeClassNamesProps(theme, pathColor, className, state.containerWidth, state.containerHeight, this.props.enableReflow);
            var classNames_1 = this._computeClassNames(styles, classNamesProps);
            // Compute the position of each node and link
            var _b = this._normalizeData(this.props.data.SankeyChartData, state.containerWidth, state.containerHeight, colorsForNodes, borderColorsForNodes), nodes_1 = _b.nodes, links_1 = _b.links, width = _b.width, height = _b.height;
            // In FocusZone, the focus order is determined by the rendering order of the elements. We need to find
            // a rendering order such that the focus moves through the nodes and links in a logical sequence.
            // Rendering the nodes and links layer by layer in a vertical order seems to be the most effective solution
            // with FocusZone. Although this focus order may not be entirely logical, it ensures that the focus moves
            // sequentially and prevents links (especially skip layer links) from being rendered over the nodes.
            var nodeLinkDomOrderArray_1 = [];
            nodes_1.sort(function (a, b) {
                if (a.x0 !== b.x0) {
                    return a.x0 - b.x0;
                }
                return a.y0 - b.y0;
            });
            nodes_1.forEach(function (item, index) {
                nodeLinkDomOrderArray_1.push({ layer: item.layer, type: 'node', index: index });
            });
            links_1.sort(function (a, b) {
                var asx0 = a.source.x0;
                var bsx0 = b.source.x0;
                if (asx0 !== bsx0) {
                    return asx0 - bsx0;
                }
                return a.y0 - b.y0;
            });
            links_1.forEach(function (item, index) {
                nodeLinkDomOrderArray_1.push({ layer: item.source.layer, type: 'link', index: index });
            });
            nodeLinkDomOrderArray_1.sort(function (a, b) {
                if (a.layer !== b.layer) {
                    return a.layer - b.layer;
                }
                if (a.type > b.type) {
                    return -1;
                }
                if (a.type < b.type) {
                    return 1;
                }
                return 0;
            });
            // NOTE: I don't love this approach to caching the "select" result. Is it still valid from render-to-render?
            // although local testing seems to indicate so, I do not trust that React will always support that instance.
            // It might be better to perform this `fetch` within the `_showTooltip` and `_hideTooltip` methods.
            var tooltipDiv = this._fetchTooltip(classNames_1);
            // Pre-compute some important attributes about nodes, specifically text
            var nodeAttributes = this._nodeAttributes(nodes_1, this._accessibility.nodeAriaLabel);
            // Build the nodes and links as rendered in the UX.
            var nodeData_1 = this._fetchNodes(classNames_1, nodes_1, nodeAttributes, tooltipDiv);
            var linkAttributes = this._linkAttributes(links_1, this._strings.linkFrom, this._accessibility.linkAriaLabel);
            var linkData_1 = this._fetchLinks(links_1, linkAttributes);
            var calloutProps = tslib_1.__assign({ isCalloutVisible: state.isCalloutVisible, directionalHint: Callout_1.DirectionalHint.topAutoEdge, id: "toolTip".concat(this._calloutId), target: state.refSelected, color: state.color, XValue: state.xCalloutValue, YValue: state.yCalloutValue ? state.yCalloutValue : state.dataForHoverCard, descriptionMessage: state.descriptionMessage, isBeakVisible: false, gapSpace: 15, onDismiss: this._onCloseCallout, className: classNames_1.calloutContentRoot, preventDismissOnLostFocus: true }, this.props.calloutProps);
            return (React.createElement("div", { className: classNames_1.root, role: 'presentation', ref: function (rootElem) { return (_this.chartContainer = rootElem); }, onMouseLeave: this._onCloseCallout },
                React.createElement(react_focus_1.FocusZone, { direction: react_focus_1.FocusZoneDirection.vertical, className: classNames_1.chartWrapper },
                    React.createElement("svg", { width: width, height: height, id: this._chartId },
                        nodeLinkDomOrderArray_1.map(function (item) {
                            if (item.type === 'node') {
                                return (React.createElement("g", { key: nodes_1[item.index].nodeId, className: classNames_1.nodes }, nodeData_1[item.index]));
                            }
                            else {
                                return (React.createElement("g", { key: "".concat(links_1[item.index].source.nodeId, "-").concat(links_1[item.index].target.nodeId), className: classNames_1.links, strokeOpacity: 1 }, linkData_1[item.index]));
                            }
                        }),
                        calloutProps.isCalloutVisible && (React.createElement(Callout_1.Callout, tslib_1.__assign({}, calloutProps),
                            React.createElement(ChartHoverCard_1.ChartHoverCard, { XValue: calloutProps.XValue, YValue: calloutProps.YValue, color: calloutProps.color, descriptionMessage: calloutProps.descriptionMessage ? calloutProps.descriptionMessage : '' })))))));
        }
        return (React.createElement("div", { id: this._emptyChartId, role: 'alert', style: { opacity: '0' }, "aria-label": this._accessibility.emptyAriaLabel }));
    };
    SankeyChartBase.prototype._computeNodeAttributes = function (nodes, nodeAriaLabel) {
        var _this = this;
        var result = {};
        var weightSpan = (0, d3_selection_1.select)('.nodeName').append('text').attr('class', 'tempText').append('tspan').text(null);
        var nameSpan = (0, d3_selection_1.select)('.nodeName')
            .append('text')
            .attr('class', 'tempText')
            .attr('font-size', '10')
            .append('tspan')
            .text(null);
        nodes.forEach(function (singleNode) {
            var height = Math.max(singleNode.y1 - singleNode.y0, 0);
            var padding = 8;
            var textLengthForNodeWeight = 0;
            var nodeValue = singleNode.actualValue;
            // If the nodeWeight is in the same line as node description an extra padding
            // of 6 px is required between node description and node weight.
            if (height < MIN_HEIGHT_FOR_DOUBLINE_TYPE) {
                padding = padding + 6;
                // The following `select` statement injects a `tempText` element into the DOM. This injection
                // (and subsequent removal) is causing a layout recalculation. This is a performance issue.
                var measurement = measureText(weightSpan, _this._formatNumber(nodeValue));
                if (measurement !== undefined) {
                    textLengthForNodeWeight = measurement;
                    padding = padding + textLengthForNodeWeight;
                }
            }
            // Since the total width of the node is 124 and we are giving margin of 8px from the left .
            // So the actual value on which it will be truncated is 124-8=116.
            var truncatedname = truncateText(nameSpan, singleNode.name, NODE_WIDTH - 8, padding);
            var isTruncated = truncatedname.slice(-3) === elipsis;
            result[singleNode.nodeId] = {
                reactId: (0, Utilities_1.getId)('nodeBar'),
                gElementId: (0, Utilities_1.getId)('nodeGElement'),
                name: truncatedname,
                aria: nodeAriaLabel(singleNode, nodeValue),
                trimmed: isTruncated,
                height: height,
                weightOffset: textLengthForNodeWeight,
            };
        });
        (0, d3_selection_1.selectAll)('.tempText').remove();
        return result;
    };
    SankeyChartBase.prototype._linkCalloutAttributes = function (singleLink, from) {
        return {
            selectedLink: singleLink,
            isCalloutVisible: true,
            color: singleLink.source.color,
            xCalloutValue: singleLink.target.name,
            yCalloutValue: this._formatNumber(singleLink.unnormalizedValue),
            descriptionMessage: from,
        };
    };
    SankeyChartBase.prototype._normalizeSankeyData = function (data, containerWidth, containerHeight, colorsForNodes, borderColorsForNodes) {
        var _a = preRenderLayout(this._margins, containerWidth, containerHeight, this._isRtl), sankey = _a.sankey, height = _a.height, width = _a.width;
        // Clone the data before mutating it (via the SankeyLayoutGenerator) so that we don't mutate the original data.
        var transformed = duplicateData(data);
        sankey(transformed);
        // NOTE: After the prior line, `transformed` is now a more-complex object than the incoming `ISankeyChartData`.
        // `transformed` should be cast to a more-specific type. This is a breaking change because we would be eliminating
        // fields from `ISankeyChartData` and putting those fields on a now-local type. But doing so makes it clearer what
        // the caller needs to supply and why. For example, the `actualValue` and `layer` fields of `ISNodeExtra` should
        // both be moved. Similarly for `unnormalizedValue` in `ISLinkExtra`.
        // `SankeyNodeMinimal` and `SankeyLinkMinimal` are both the types after `sankey(transformed)`, but have almost no
        // bearing on the data before `sankey(transformed)` (which is basically nodes with ids and names along with links
        // with source index, target index, and value).
        var nodesInColumn = groupNodesByColumn(transformed);
        this._numColumns = Object.keys(nodesInColumn).length;
        // Keep track of the original values of the links and their acccumulated values in the nodes
        // Setting these in external objects so they cannot be mutated by other code.
        // The IDs of nodes can be numbers or strings. But, the IDs of links are always the index into the "nodes" array.
        // After the sankey layout is computed, the each link's `source` and `target` will have the ID of the node in the
        // type originally specified in the Nodes array. Consequently, we get the values of those links after the sankey
        // transformation.
        var nodeValues = valuesOfNodes(transformed.nodes);
        var linkValues = valuesOfLinks(transformed.links);
        adjustOnePercentHeightNodes(nodesInColumn, nodeValues, linkValues);
        adjustPadding(sankey, height - 6, nodesInColumn);
        // `sankey` is called a second time, probably to re-layout the nodes with the one-percent adjusted weights.
        // NOTE: The second call to `sankey` is required to allow links to be hoverable.
        // Without the second call, the links are not hoverable.
        sankey(transformed);
        populateNodeActualValue(transformed, nodeValues, linkValues);
        assignNodeColors(transformed.nodes, colorsForNodes, borderColorsForNodes);
        return {
            width: width,
            height: height,
            nodes: transformed.nodes,
            links: transformed.links,
        };
    };
    SankeyChartBase.prototype._labelTooltipDiv = function (classNames) {
        // find the tooltip div. If it doesn't exist, then create it.
        var tooltipDiv = (0, d3_selection_1.select)("#".concat(this._labelTooltipId));
        if (tooltipDiv.empty()) {
            tooltipDiv = (0, d3_selection_1.select)('body')
                .append('div')
                .attr('id', this._labelTooltipId)
                .attr('class', classNames.toolTip)
                .style('opacity', 0);
        }
        // If the div exists, then `classNames` has changed; update the `class` on the `div`.
        tooltipDiv.attr('class', classNames.toolTip);
        return tooltipDiv;
    };
    SankeyChartBase.prototype._createLinks = function (dataLinks, linkAttributes) {
        var _this = this;
        if (dataLinks) {
            var linkId_1 = this._linkId;
            return dataLinks.map(function (singleLink, index) {
                var onMouseOut = function () {
                    _this._onStreamLeave(singleLink);
                };
                var _a = linkValue(linkAttributes, singleLink), reactId = _a.reactId, from = _a.from, aria = _a.aria;
                // eslint-disable-next-line @typescript-eslint/no-explicit-any
                var dataPoints = linkToDataPoints(singleLink);
                var key = "".concat(linkId_1, "-").concat(index);
                var gradientId = "gradient-".concat(key);
                var gradientUrl = "url(#".concat(gradientId, ")");
                var source = singleLink.source;
                var target = singleLink.target;
                // TODO: localize the aria-label string
                return (React.createElement("g", { key: key },
                    React.createElement("defs", null,
                        React.createElement("linearGradient", { id: gradientId, x1: "0%", y1: "0%", x2: "100%", y2: "0%" },
                            React.createElement("stop", { offset: "0", stopColor: source.color }),
                            React.createElement("stop", { offset: "100%", stopColor: target.color }))),
                    React.createElement("path", { d: linkArea(dataPoints), id: reactId, fill: _this._fillStreamColors(singleLink, gradientUrl), stroke: _this._fillStreamBorder(singleLink, gradientUrl), strokeWidth: "2", strokeOpacity: _this._getOpacityStreamBorder(singleLink), onMouseOver: function (event) { return _this._onStreamHover(event, singleLink, from); }, onMouseOut: onMouseOut, onFocus: function (event) { return _this._onFocusLink(event, singleLink, from); }, onBlur: _this._onBlur, fillOpacity: _this._getOpacityStream(singleLink), "data-is-focusable": true, "aria-label": aria, role: "img" })));
            });
        }
        return [];
    };
    SankeyChartBase.prototype._createNodes = function (classNames, dataNodes, nodeAttributes, tooltipDiv) {
        var _this = this;
        if (dataNodes) {
            var state_1 = this.state;
            var textAnchor_1 = this._isRtl ? 'end' : 'start';
            return dataNodes.map(function (singleNode, index) {
                var onMouseOut = function () {
                    _this._onLeave(singleNode);
                };
                var _a = nodeAttributes[singleNode.nodeId], nodeId = _a.reactId, gElementId = _a.gElementId, height = _a.height, isTruncated = _a.trimmed, truncatedName = _a.name, textLengthForNodeWeight = _a.weightOffset, aria = _a.aria;
                var tooTall = height > MIN_HEIGHT_FOR_DOUBLINE_TYPE;
                var name = singleNode.name, actualValue = singleNode.actualValue, x0 = singleNode.x0, x1 = singleNode.x1, y0 = singleNode.y0;
                var textColor = nodeTextColor(state_1, singleNode);
                return (React.createElement("g", { key: index, id: gElementId },
                    React.createElement("rect", { x: x0, y: y0, height: height, width: x1 - x0, fill: _this._fillNodeColors(singleNode), id: nodeId, onMouseOver: _this._onHover.bind(_this, singleNode), onMouseOut: onMouseOut, onFocus: _this._onCloseCallout.bind(_this), stroke: _this._fillNodeBorder(singleNode), strokeWidth: "2", opacity: "1", "data-is-focusable": true, "aria-label": aria, role: "img" }),
                    height > MIN_HEIGHT_FOR_TYPE && (React.createElement("g", { className: classNames.nodeTextContainer },
                        React.createElement("g", { className: "nodeName" },
                            React.createElement("text", { id: "".concat(nodeId, "-name"), x: x0, y: y0, dy: '1.2em', dx: '0.4em', textAnchor: textAnchor_1, fontWeight: "regular", "aria-hidden": "true", fill: textColor, fontSize: 10, onMouseOver: _this._showTooltip.bind(_this, name, isTruncated, tooltipDiv), onMouseOut: _this._hideTooltip.bind(_this, tooltipDiv) }, truncatedName)),
                        React.createElement("text", { x: tooTall ? x0 : x1 - textLengthForNodeWeight - 8, y: y0, dy: tooTall ? '2em' : '1em', dx: tooTall ? '0.4em' : '0em', textAnchor: textAnchor_1, fontWeight: "bold", "aria-hidden": "true", fill: textColor, fontSize: 14 }, actualValue ? _this._formatNumber(actualValue) : actualValue)))));
            });
        }
    };
    SankeyChartBase.prototype._onLeave = function (singleNode) {
        if (this.state.selectedState) {
            this.setState({
                selectedState: false,
                selectedNodes: new Set(),
                selectedLinks: new Set(),
                selectedNode: undefined,
            });
        }
    };
    SankeyChartBase.prototype._onHover = function (singleNode, mouseEvent) {
        mouseEvent.persist();
        this._onCloseCallout();
        if (!this.state.selectedState) {
            var selectedLinks = getSelectedLinks(singleNode);
            var selectedNodes = getSelectedNodes(selectedLinks);
            selectedNodes.push(singleNode);
            this.setState({
                selectedState: true,
                selectedNodes: new Set(Array.from(selectedNodes).map(function (node) { return node.index; })),
                selectedLinks: new Set(Array.from(selectedLinks).map(function (link) { return link.index; })),
                selectedNode: singleNode,
                refSelected: mouseEvent, //this._refArray.get(index),
                isCalloutVisible: singleNode.y1 - singleNode.y0 < MIN_HEIGHT_FOR_TYPE,
                color: singleNode.color,
                xCalloutValue: singleNode.name,
                yCalloutValue: this._formatNumber(singleNode.actualValue),
            });
        }
    };
    SankeyChartBase.prototype._formatNumber = function (value) {
        return this.props.formatNumberOptions
            ? value.toLocaleString(undefined, this.props.formatNumberOptions)
            : value.toString();
    };
    SankeyChartBase.prototype._onStreamHover = function (mouseEvent, singleLink, from) {
        mouseEvent.persist();
        this._onCloseCallout();
        if (!this.state.selectedState) {
            var _a = getSelectedLinksforStreamHover(singleLink), selectedLinks = _a.selectedLinks, selectedNodes = _a.selectedNodes;
            this.setState(tslib_1.__assign({ selectedState: true, selectedNodes: new Set(Array.from(selectedNodes).map(function (node) { return node.index; })), selectedLinks: new Set(Array.from(selectedLinks).map(function (link) { return link.index; })), refSelected: mouseEvent }, this._linkCalloutAttributes(singleLink, from)));
        }
    };
    SankeyChartBase.prototype._onStreamLeave = function (singleLink) {
        if (this.state.selectedState) {
            this.setState({
                selectedState: false,
                selectedNodes: new Set(),
                selectedLinks: new Set(),
                selectedLink: undefined,
            });
        }
    };
    SankeyChartBase.prototype._onFocusLink = function (element, singleLink, from) {
        // There is a big difference in how "Tab" and the "Arrow keys" are handled in this diagram.
        // In particular, I would expect the "Down" key to be like "Tab", but it jumps a little wildly. I'm not sure
        // if this behavior is an accessiblity violation, but it we might want to investigate it.
        element.persist();
        this._onCloseCallout();
        this.setState(tslib_1.__assign({ refSelected: element.currentTarget }, this._linkCalloutAttributes(singleLink, from)));
    };
    SankeyChartBase.prototype._fillStreamColors = function (singleLink, gradientUrl) {
        var state = this.state;
        if (state.selectedState && state.selectedLinks.has(singleLink.index)) {
            var selectedNode = state.selectedNode;
            return selectedNode ? selectedNode.color : gradientUrl;
        }
    };
    SankeyChartBase.prototype._fillStreamBorder = function (singleLink, gradientUrl) {
        var state = this.state;
        if (!state.selectedState) {
            return NON_SELECTED_NODE_AND_STREAM_COLOR;
        }
        else {
            if (state.selectedLinks.has(singleLink.index)) {
                var selectedNode = state.selectedNode;
                return selectedNode ? selectedNode.borderColor : gradientUrl;
            }
            return NON_SELECTED_NODE_AND_STREAM_COLOR;
        }
    };
    SankeyChartBase.prototype._getOpacityStream = function (singleLink) {
        var state = this.state;
        if (state.selectedState) {
            if (!state.selectedLinks.has(singleLink.index)) {
                return NON_SELECTED_OPACITY;
            }
            else if (!state.selectedNode) {
                return SELECTED_STREAM_OPACITY;
            }
        }
        return REST_STREAM_OPACITY;
    };
    SankeyChartBase.prototype._getOpacityStreamBorder = function (singleLink) {
        var state = this.state;
        if (state.selectedState && !state.selectedLinks.has(singleLink.index) && !state.selectedNode) {
            return NON_SELECTED_STREAM_BORDER_OPACITY;
        }
        return NON_SELECTED_OPACITY;
    };
    SankeyChartBase.prototype._fitParentContainer = function () {
        var _this = this;
        var _a = this.state, containerWidth = _a.containerWidth, containerHeight = _a.containerHeight;
        this._reqID = requestAnimationFrame(function () {
            // NOTE: Calls to this method trigger a re-render.
            var container = _this.props.parentRef ? _this.props.parentRef : _this.chartContainer;
            var currentContainerWidth = container &&
                (_this.props.enableReflow
                    ? Math.max(container.getBoundingClientRect().width, _this._calculateChartMinWidth())
                    : container.getBoundingClientRect().width);
            var currentContainerHeight = container && container.getBoundingClientRect().height;
            var shouldResize = containerWidth !== currentContainerWidth || containerHeight !== currentContainerHeight;
            if (shouldResize) {
                _this.setState({
                    containerWidth: currentContainerWidth,
                    containerHeight: currentContainerHeight,
                });
            }
        });
    };
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    SankeyChartBase.prototype._showTooltip = function (text, checkTrcuncated, div, evt) {
        if (checkTrcuncated) {
            //Fixing tooltip position by attaching it to the element rather than page
            div.style('opacity', 0.9).style('color', this.props.theme.palette.neutralPrimary);
            div
                .html(text)
                .style('left', evt.pageX + 'px')
                .style('top', evt.pageY - 28 + 'px');
        }
    };
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    SankeyChartBase.prototype._hideTooltip = function (div) {
        div.style('opacity', 0);
    };
    SankeyChartBase.prototype._isChartEmpty = function () {
        var _a;
        var sankeyChartData = (_a = this.props.data) === null || _a === void 0 ? void 0 : _a.SankeyChartData;
        return !(sankeyChartData && sankeyChartData.nodes.length > 0 && sankeyChartData.links.length > 0);
    };
    SankeyChartBase.defaultProps = {
        enableReflow: true,
    };
    return SankeyChartBase;
}(React.Component));
exports.SankeyChartBase = SankeyChartBase;
//# sourceMappingURL=SankeyChart.base.js.map