| 
/* 
 | 
* Licensed to the Apache Software Foundation (ASF) under one 
 | 
* or more contributor license agreements.  See the NOTICE file 
 | 
* distributed with this work for additional information 
 | 
* regarding copyright ownership.  The ASF licenses this file 
 | 
* to you under the Apache License, Version 2.0 (the 
 | 
* "License"); you may not use this file except in compliance 
 | 
* with the License.  You may obtain a copy of the License at 
 | 
* 
 | 
*   http://www.apache.org/licenses/LICENSE-2.0 
 | 
* 
 | 
* Unless required by applicable law or agreed to in writing, 
 | 
* software distributed under the License is distributed on an 
 | 
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 
 | 
* KIND, either express or implied.  See the License for the 
 | 
* specific language governing permissions and limitations 
 | 
* under the License. 
 | 
*/ 
 | 
  
 | 
  
 | 
/** 
 | 
 * AUTO-GENERATED FILE. DO NOT MODIFY. 
 | 
 */ 
 | 
  
 | 
/* 
 | 
* Licensed to the Apache Software Foundation (ASF) under one 
 | 
* or more contributor license agreements.  See the NOTICE file 
 | 
* distributed with this work for additional information 
 | 
* regarding copyright ownership.  The ASF licenses this file 
 | 
* to you under the Apache License, Version 2.0 (the 
 | 
* "License"); you may not use this file except in compliance 
 | 
* with the License.  You may obtain a copy of the License at 
 | 
* 
 | 
*   http://www.apache.org/licenses/LICENSE-2.0 
 | 
* 
 | 
* Unless required by applicable law or agreed to in writing, 
 | 
* software distributed under the License is distributed on an 
 | 
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 
 | 
* KIND, either express or implied.  See the License for the 
 | 
* specific language governing permissions and limitations 
 | 
* under the License. 
 | 
*/ 
 | 
import * as layout from '../../util/layout.js'; 
 | 
import * as zrUtil from 'zrender/lib/core/util.js'; 
 | 
import { groupData } from '../../util/model.js'; 
 | 
export default function sankeyLayout(ecModel, api) { 
 | 
  ecModel.eachSeriesByType('sankey', function (seriesModel) { 
 | 
    var nodeWidth = seriesModel.get('nodeWidth'); 
 | 
    var nodeGap = seriesModel.get('nodeGap'); 
 | 
    var layoutInfo = getViewRect(seriesModel, api); 
 | 
    seriesModel.layoutInfo = layoutInfo; 
 | 
    var width = layoutInfo.width; 
 | 
    var height = layoutInfo.height; 
 | 
    var graph = seriesModel.getGraph(); 
 | 
    var nodes = graph.nodes; 
 | 
    var edges = graph.edges; 
 | 
    computeNodeValues(nodes); 
 | 
    var filteredNodes = zrUtil.filter(nodes, function (node) { 
 | 
      return node.getLayout().value === 0; 
 | 
    }); 
 | 
    var iterations = filteredNodes.length !== 0 ? 0 : seriesModel.get('layoutIterations'); 
 | 
    var orient = seriesModel.get('orient'); 
 | 
    var nodeAlign = seriesModel.get('nodeAlign'); 
 | 
    layoutSankey(nodes, edges, nodeWidth, nodeGap, width, height, iterations, orient, nodeAlign); 
 | 
  }); 
 | 
} 
 | 
/** 
 | 
 * Get the layout position of the whole view 
 | 
 */ 
 | 
function getViewRect(seriesModel, api) { 
 | 
  return layout.getLayoutRect(seriesModel.getBoxLayoutParams(), { 
 | 
    width: api.getWidth(), 
 | 
    height: api.getHeight() 
 | 
  }); 
 | 
} 
 | 
function layoutSankey(nodes, edges, nodeWidth, nodeGap, width, height, iterations, orient, nodeAlign) { 
 | 
  computeNodeBreadths(nodes, edges, nodeWidth, width, height, orient, nodeAlign); 
 | 
  computeNodeDepths(nodes, edges, height, width, nodeGap, iterations, orient); 
 | 
  computeEdgeDepths(nodes, orient); 
 | 
} 
 | 
/** 
 | 
 * Compute the value of each node by summing the associated edge's value 
 | 
 */ 
 | 
function computeNodeValues(nodes) { 
 | 
  zrUtil.each(nodes, function (node) { 
 | 
    var value1 = sum(node.outEdges, getEdgeValue); 
 | 
    var value2 = sum(node.inEdges, getEdgeValue); 
 | 
    var nodeRawValue = node.getValue() || 0; 
 | 
    var value = Math.max(value1, value2, nodeRawValue); 
 | 
    node.setLayout({ 
 | 
      value: value 
 | 
    }, true); 
 | 
  }); 
 | 
} 
 | 
/** 
 | 
 * Compute the x-position for each node. 
 | 
 * 
 | 
 * Here we use Kahn algorithm to detect cycle when we traverse 
 | 
 * the node to computer the initial x position. 
 | 
 */ 
 | 
function computeNodeBreadths(nodes, edges, nodeWidth, width, height, orient, nodeAlign) { 
 | 
  // Used to mark whether the edge is deleted. if it is deleted, 
 | 
  // the value is 0, otherwise it is 1. 
 | 
  var remainEdges = []; 
 | 
  // Storage each node's indegree. 
 | 
  var indegreeArr = []; 
 | 
  // Used to storage the node with indegree is equal to 0. 
 | 
  var zeroIndegrees = []; 
 | 
  var nextTargetNode = []; 
 | 
  var x = 0; 
 | 
  // let kx = 0; 
 | 
  for (var i = 0; i < edges.length; i++) { 
 | 
    remainEdges[i] = 1; 
 | 
  } 
 | 
  for (var i = 0; i < nodes.length; i++) { 
 | 
    indegreeArr[i] = nodes[i].inEdges.length; 
 | 
    if (indegreeArr[i] === 0) { 
 | 
      zeroIndegrees.push(nodes[i]); 
 | 
    } 
 | 
  } 
 | 
  var maxNodeDepth = -1; 
 | 
  // Traversing nodes using topological sorting to calculate the 
 | 
  // horizontal(if orient === 'horizontal') or vertical(if orient === 'vertical') 
 | 
  // position of the nodes. 
 | 
  while (zeroIndegrees.length) { 
 | 
    for (var idx = 0; idx < zeroIndegrees.length; idx++) { 
 | 
      var node = zeroIndegrees[idx]; 
 | 
      var item = node.hostGraph.data.getRawDataItem(node.dataIndex); 
 | 
      var isItemDepth = item.depth != null && item.depth >= 0; 
 | 
      if (isItemDepth && item.depth > maxNodeDepth) { 
 | 
        maxNodeDepth = item.depth; 
 | 
      } 
 | 
      node.setLayout({ 
 | 
        depth: isItemDepth ? item.depth : x 
 | 
      }, true); 
 | 
      orient === 'vertical' ? node.setLayout({ 
 | 
        dy: nodeWidth 
 | 
      }, true) : node.setLayout({ 
 | 
        dx: nodeWidth 
 | 
      }, true); 
 | 
      for (var edgeIdx = 0; edgeIdx < node.outEdges.length; edgeIdx++) { 
 | 
        var edge = node.outEdges[edgeIdx]; 
 | 
        var indexEdge = edges.indexOf(edge); 
 | 
        remainEdges[indexEdge] = 0; 
 | 
        var targetNode = edge.node2; 
 | 
        var nodeIndex = nodes.indexOf(targetNode); 
 | 
        if (--indegreeArr[nodeIndex] === 0 && nextTargetNode.indexOf(targetNode) < 0) { 
 | 
          nextTargetNode.push(targetNode); 
 | 
        } 
 | 
      } 
 | 
    } 
 | 
    ++x; 
 | 
    zeroIndegrees = nextTargetNode; 
 | 
    nextTargetNode = []; 
 | 
  } 
 | 
  for (var i = 0; i < remainEdges.length; i++) { 
 | 
    if (remainEdges[i] === 1) { 
 | 
      throw new Error('Sankey is a DAG, the original data has cycle!'); 
 | 
    } 
 | 
  } 
 | 
  var maxDepth = maxNodeDepth > x - 1 ? maxNodeDepth : x - 1; 
 | 
  if (nodeAlign && nodeAlign !== 'left') { 
 | 
    adjustNodeWithNodeAlign(nodes, nodeAlign, orient, maxDepth); 
 | 
  } 
 | 
  var kx = orient === 'vertical' ? (height - nodeWidth) / maxDepth : (width - nodeWidth) / maxDepth; 
 | 
  scaleNodeBreadths(nodes, kx, orient); 
 | 
} 
 | 
function isNodeDepth(node) { 
 | 
  var item = node.hostGraph.data.getRawDataItem(node.dataIndex); 
 | 
  return item.depth != null && item.depth >= 0; 
 | 
} 
 | 
function adjustNodeWithNodeAlign(nodes, nodeAlign, orient, maxDepth) { 
 | 
  if (nodeAlign === 'right') { 
 | 
    var nextSourceNode = []; 
 | 
    var remainNodes = nodes; 
 | 
    var nodeHeight = 0; 
 | 
    while (remainNodes.length) { 
 | 
      for (var i = 0; i < remainNodes.length; i++) { 
 | 
        var node = remainNodes[i]; 
 | 
        node.setLayout({ 
 | 
          skNodeHeight: nodeHeight 
 | 
        }, true); 
 | 
        for (var j = 0; j < node.inEdges.length; j++) { 
 | 
          var edge = node.inEdges[j]; 
 | 
          if (nextSourceNode.indexOf(edge.node1) < 0) { 
 | 
            nextSourceNode.push(edge.node1); 
 | 
          } 
 | 
        } 
 | 
      } 
 | 
      remainNodes = nextSourceNode; 
 | 
      nextSourceNode = []; 
 | 
      ++nodeHeight; 
 | 
    } 
 | 
    zrUtil.each(nodes, function (node) { 
 | 
      if (!isNodeDepth(node)) { 
 | 
        node.setLayout({ 
 | 
          depth: Math.max(0, maxDepth - node.getLayout().skNodeHeight) 
 | 
        }, true); 
 | 
      } 
 | 
    }); 
 | 
  } else if (nodeAlign === 'justify') { 
 | 
    moveSinksRight(nodes, maxDepth); 
 | 
  } 
 | 
} 
 | 
/** 
 | 
 * All the node without outEgdes are assigned maximum x-position and 
 | 
 *     be aligned in the last column. 
 | 
 * 
 | 
 * @param nodes.  node of sankey view. 
 | 
 * @param maxDepth.  use to assign to node without outEdges as x-position. 
 | 
 */ 
 | 
function moveSinksRight(nodes, maxDepth) { 
 | 
  zrUtil.each(nodes, function (node) { 
 | 
    if (!isNodeDepth(node) && !node.outEdges.length) { 
 | 
      node.setLayout({ 
 | 
        depth: maxDepth 
 | 
      }, true); 
 | 
    } 
 | 
  }); 
 | 
} 
 | 
/** 
 | 
 * Scale node x-position to the width 
 | 
 * 
 | 
 * @param nodes  node of sankey view 
 | 
 * @param kx   multiple used to scale nodes 
 | 
 */ 
 | 
function scaleNodeBreadths(nodes, kx, orient) { 
 | 
  zrUtil.each(nodes, function (node) { 
 | 
    var nodeDepth = node.getLayout().depth * kx; 
 | 
    orient === 'vertical' ? node.setLayout({ 
 | 
      y: nodeDepth 
 | 
    }, true) : node.setLayout({ 
 | 
      x: nodeDepth 
 | 
    }, true); 
 | 
  }); 
 | 
} 
 | 
/** 
 | 
 * Using Gauss-Seidel iterations method to compute the node depth(y-position) 
 | 
 * 
 | 
 * @param nodes  node of sankey view 
 | 
 * @param edges  edge of sankey view 
 | 
 * @param height  the whole height of the area to draw the view 
 | 
 * @param nodeGap  the vertical distance between two nodes 
 | 
 *     in the same column. 
 | 
 * @param iterations  the number of iterations for the algorithm 
 | 
 */ 
 | 
function computeNodeDepths(nodes, edges, height, width, nodeGap, iterations, orient) { 
 | 
  var nodesByBreadth = prepareNodesByBreadth(nodes, orient); 
 | 
  initializeNodeDepth(nodesByBreadth, edges, height, width, nodeGap, orient); 
 | 
  resolveCollisions(nodesByBreadth, nodeGap, height, width, orient); 
 | 
  for (var alpha = 1; iterations > 0; iterations--) { 
 | 
    // 0.99 is a experience parameter, ensure that each iterations of 
 | 
    // changes as small as possible. 
 | 
    alpha *= 0.99; 
 | 
    relaxRightToLeft(nodesByBreadth, alpha, orient); 
 | 
    resolveCollisions(nodesByBreadth, nodeGap, height, width, orient); 
 | 
    relaxLeftToRight(nodesByBreadth, alpha, orient); 
 | 
    resolveCollisions(nodesByBreadth, nodeGap, height, width, orient); 
 | 
  } 
 | 
} 
 | 
function prepareNodesByBreadth(nodes, orient) { 
 | 
  var nodesByBreadth = []; 
 | 
  var keyAttr = orient === 'vertical' ? 'y' : 'x'; 
 | 
  var groupResult = groupData(nodes, function (node) { 
 | 
    return node.getLayout()[keyAttr]; 
 | 
  }); 
 | 
  groupResult.keys.sort(function (a, b) { 
 | 
    return a - b; 
 | 
  }); 
 | 
  zrUtil.each(groupResult.keys, function (key) { 
 | 
    nodesByBreadth.push(groupResult.buckets.get(key)); 
 | 
  }); 
 | 
  return nodesByBreadth; 
 | 
} 
 | 
/** 
 | 
 * Compute the original y-position for each node 
 | 
 */ 
 | 
function initializeNodeDepth(nodesByBreadth, edges, height, width, nodeGap, orient) { 
 | 
  var minKy = Infinity; 
 | 
  zrUtil.each(nodesByBreadth, function (nodes) { 
 | 
    var n = nodes.length; 
 | 
    var sum = 0; 
 | 
    zrUtil.each(nodes, function (node) { 
 | 
      sum += node.getLayout().value; 
 | 
    }); 
 | 
    var ky = orient === 'vertical' ? (width - (n - 1) * nodeGap) / sum : (height - (n - 1) * nodeGap) / sum; 
 | 
    if (ky < minKy) { 
 | 
      minKy = ky; 
 | 
    } 
 | 
  }); 
 | 
  zrUtil.each(nodesByBreadth, function (nodes) { 
 | 
    zrUtil.each(nodes, function (node, i) { 
 | 
      var nodeDy = node.getLayout().value * minKy; 
 | 
      if (orient === 'vertical') { 
 | 
        node.setLayout({ 
 | 
          x: i 
 | 
        }, true); 
 | 
        node.setLayout({ 
 | 
          dx: nodeDy 
 | 
        }, true); 
 | 
      } else { 
 | 
        node.setLayout({ 
 | 
          y: i 
 | 
        }, true); 
 | 
        node.setLayout({ 
 | 
          dy: nodeDy 
 | 
        }, true); 
 | 
      } 
 | 
    }); 
 | 
  }); 
 | 
  zrUtil.each(edges, function (edge) { 
 | 
    var edgeDy = +edge.getValue() * minKy; 
 | 
    edge.setLayout({ 
 | 
      dy: edgeDy 
 | 
    }, true); 
 | 
  }); 
 | 
} 
 | 
/** 
 | 
 * Resolve the collision of initialized depth (y-position) 
 | 
 */ 
 | 
function resolveCollisions(nodesByBreadth, nodeGap, height, width, orient) { 
 | 
  var keyAttr = orient === 'vertical' ? 'x' : 'y'; 
 | 
  zrUtil.each(nodesByBreadth, function (nodes) { 
 | 
    nodes.sort(function (a, b) { 
 | 
      return a.getLayout()[keyAttr] - b.getLayout()[keyAttr]; 
 | 
    }); 
 | 
    var nodeX; 
 | 
    var node; 
 | 
    var dy; 
 | 
    var y0 = 0; 
 | 
    var n = nodes.length; 
 | 
    var nodeDyAttr = orient === 'vertical' ? 'dx' : 'dy'; 
 | 
    for (var i = 0; i < n; i++) { 
 | 
      node = nodes[i]; 
 | 
      dy = y0 - node.getLayout()[keyAttr]; 
 | 
      if (dy > 0) { 
 | 
        nodeX = node.getLayout()[keyAttr] + dy; 
 | 
        orient === 'vertical' ? node.setLayout({ 
 | 
          x: nodeX 
 | 
        }, true) : node.setLayout({ 
 | 
          y: nodeX 
 | 
        }, true); 
 | 
      } 
 | 
      y0 = node.getLayout()[keyAttr] + node.getLayout()[nodeDyAttr] + nodeGap; 
 | 
    } 
 | 
    var viewWidth = orient === 'vertical' ? width : height; 
 | 
    // If the bottommost node goes outside the bounds, push it back up 
 | 
    dy = y0 - nodeGap - viewWidth; 
 | 
    if (dy > 0) { 
 | 
      nodeX = node.getLayout()[keyAttr] - dy; 
 | 
      orient === 'vertical' ? node.setLayout({ 
 | 
        x: nodeX 
 | 
      }, true) : node.setLayout({ 
 | 
        y: nodeX 
 | 
      }, true); 
 | 
      y0 = nodeX; 
 | 
      for (var i = n - 2; i >= 0; --i) { 
 | 
        node = nodes[i]; 
 | 
        dy = node.getLayout()[keyAttr] + node.getLayout()[nodeDyAttr] + nodeGap - y0; 
 | 
        if (dy > 0) { 
 | 
          nodeX = node.getLayout()[keyAttr] - dy; 
 | 
          orient === 'vertical' ? node.setLayout({ 
 | 
            x: nodeX 
 | 
          }, true) : node.setLayout({ 
 | 
            y: nodeX 
 | 
          }, true); 
 | 
        } 
 | 
        y0 = node.getLayout()[keyAttr]; 
 | 
      } 
 | 
    } 
 | 
  }); 
 | 
} 
 | 
/** 
 | 
 * Change the y-position of the nodes, except most the right side nodes 
 | 
 * @param nodesByBreadth 
 | 
 * @param alpha  parameter used to adjust the nodes y-position 
 | 
 */ 
 | 
function relaxRightToLeft(nodesByBreadth, alpha, orient) { 
 | 
  zrUtil.each(nodesByBreadth.slice().reverse(), function (nodes) { 
 | 
    zrUtil.each(nodes, function (node) { 
 | 
      if (node.outEdges.length) { 
 | 
        var y = sum(node.outEdges, weightedTarget, orient) / sum(node.outEdges, getEdgeValue); 
 | 
        if (isNaN(y)) { 
 | 
          var len = node.outEdges.length; 
 | 
          y = len ? sum(node.outEdges, centerTarget, orient) / len : 0; 
 | 
        } 
 | 
        if (orient === 'vertical') { 
 | 
          var nodeX = node.getLayout().x + (y - center(node, orient)) * alpha; 
 | 
          node.setLayout({ 
 | 
            x: nodeX 
 | 
          }, true); 
 | 
        } else { 
 | 
          var nodeY = node.getLayout().y + (y - center(node, orient)) * alpha; 
 | 
          node.setLayout({ 
 | 
            y: nodeY 
 | 
          }, true); 
 | 
        } 
 | 
      } 
 | 
    }); 
 | 
  }); 
 | 
} 
 | 
function weightedTarget(edge, orient) { 
 | 
  return center(edge.node2, orient) * edge.getValue(); 
 | 
} 
 | 
function centerTarget(edge, orient) { 
 | 
  return center(edge.node2, orient); 
 | 
} 
 | 
function weightedSource(edge, orient) { 
 | 
  return center(edge.node1, orient) * edge.getValue(); 
 | 
} 
 | 
function centerSource(edge, orient) { 
 | 
  return center(edge.node1, orient); 
 | 
} 
 | 
function center(node, orient) { 
 | 
  return orient === 'vertical' ? node.getLayout().x + node.getLayout().dx / 2 : node.getLayout().y + node.getLayout().dy / 2; 
 | 
} 
 | 
function getEdgeValue(edge) { 
 | 
  return edge.getValue(); 
 | 
} 
 | 
function sum(array, cb, orient) { 
 | 
  var sum = 0; 
 | 
  var len = array.length; 
 | 
  var i = -1; 
 | 
  while (++i < len) { 
 | 
    var value = +cb(array[i], orient); 
 | 
    if (!isNaN(value)) { 
 | 
      sum += value; 
 | 
    } 
 | 
  } 
 | 
  return sum; 
 | 
} 
 | 
/** 
 | 
 * Change the y-position of the nodes, except most the left side nodes 
 | 
 */ 
 | 
function relaxLeftToRight(nodesByBreadth, alpha, orient) { 
 | 
  zrUtil.each(nodesByBreadth, function (nodes) { 
 | 
    zrUtil.each(nodes, function (node) { 
 | 
      if (node.inEdges.length) { 
 | 
        var y = sum(node.inEdges, weightedSource, orient) / sum(node.inEdges, getEdgeValue); 
 | 
        if (isNaN(y)) { 
 | 
          var len = node.inEdges.length; 
 | 
          y = len ? sum(node.inEdges, centerSource, orient) / len : 0; 
 | 
        } 
 | 
        if (orient === 'vertical') { 
 | 
          var nodeX = node.getLayout().x + (y - center(node, orient)) * alpha; 
 | 
          node.setLayout({ 
 | 
            x: nodeX 
 | 
          }, true); 
 | 
        } else { 
 | 
          var nodeY = node.getLayout().y + (y - center(node, orient)) * alpha; 
 | 
          node.setLayout({ 
 | 
            y: nodeY 
 | 
          }, true); 
 | 
        } 
 | 
      } 
 | 
    }); 
 | 
  }); 
 | 
} 
 | 
/** 
 | 
 * Compute the depth(y-position) of each edge 
 | 
 */ 
 | 
function computeEdgeDepths(nodes, orient) { 
 | 
  var keyAttr = orient === 'vertical' ? 'x' : 'y'; 
 | 
  zrUtil.each(nodes, function (node) { 
 | 
    node.outEdges.sort(function (a, b) { 
 | 
      return a.node2.getLayout()[keyAttr] - b.node2.getLayout()[keyAttr]; 
 | 
    }); 
 | 
    node.inEdges.sort(function (a, b) { 
 | 
      return a.node1.getLayout()[keyAttr] - b.node1.getLayout()[keyAttr]; 
 | 
    }); 
 | 
  }); 
 | 
  zrUtil.each(nodes, function (node) { 
 | 
    var sy = 0; 
 | 
    var ty = 0; 
 | 
    zrUtil.each(node.outEdges, function (edge) { 
 | 
      edge.setLayout({ 
 | 
        sy: sy 
 | 
      }, true); 
 | 
      sy += edge.getLayout().dy; 
 | 
    }); 
 | 
    zrUtil.each(node.inEdges, function (edge) { 
 | 
      edge.setLayout({ 
 | 
        ty: ty 
 | 
      }, true); 
 | 
      ty += edge.getLayout().dy; 
 | 
    }); 
 | 
  }); 
 | 
} 
 |