text: 
  
      var layout = require('../../util/layout');
      var nest = require('../../util/array/nest');
      var zrUtil = require('zrender/lib/core/util');
  
      module.exports = function (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 = nodes.filter(function (node) {
                  return node.getLayout().value === 0;
              });
  
              var iterations = filteredNodes.length !== 0
                  ? 0 : seriesModel.get('layoutIterations');
  
              layoutSankey(nodes, edges, nodeWidth, nodeGap, width, height, iterations);
          });
      };
  
      /**
       * 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) {
          computeNodeBreadths(nodes, nodeWidth, width);
          computeNodeDepths(nodes, edges, height, nodeGap, iterations);
          computeEdgeDepths(nodes);
      }
  
      /**
       * compute the value of each node by summing the associated edge's value.
       * @param {module:echarts/data/Graph~Node} nodes
       */
      function computeNodeValues(nodes) {
          zrUtil.each(nodes, function (node) {
              var value1 = sum(node.outEdges, getEdgeValue);
              var value2 = sum(node.inEdges, getEdgeValue);
              var value = Math.max(value1, value2);
              node.setLayout({value: value}, true);
          });
      }
  
      /**
       * compute the x-position for each node.
       * @param {module:echarts/data/Graph~Node} nodes
       * @param  {number} nodeWidth
       * @param  {number} width
       */
      function computeNodeBreadths(nodes, nodeWidth, width) {
          var remainNodes = nodes;
          var nextNode = null;
          var x = 0;
          var kx = 0;
  
          while (remainNodes.length) {
              nextNode = [];
  
              for (var i = 0, len = remainNodes.length; i < len; i++) {
                  var node = remainNodes[i];
                  node.setLayout({x: x}, true);
                  node.setLayout({dx: nodeWidth}, true);
  
                  for (var j = 0, lenj = node.outEdges.length; j < lenj; j++) {
                      nextNode.push(node.outEdges[j].node2);
                  }
              }
              remainNodes = nextNode;
              ++x;
          }
  
          moveSinksRight(nodes, x);
          kx = (width - nodeWidth) / (x - 1);
  
          scaleNodeBreadths(nodes, kx);
      }
  
      /**
       * all the node without outEgdes are assigned maximum breadth and
       * be aligned in the last column.
       * @param {module:echarts/data/Graph~Node} nodes
       * @param {number} x
       */
      function moveSinksRight(nodes, x) {
          zrUtil.each(nodes, function (node) {
              if(!node.outEdges.length) {
                  node.setLayout({x: x-1}, true);
              }
          });
      }
  
      /**
       * scale node x-position to the width.
       * @param {module:echarts/data/Graph~Node} nodes
       * @param {number} kx
       */
      function scaleNodeBreadths(nodes, kx) {
          zrUtil.each(nodes, function(node) {
              var nodeX = node.getLayout().x * kx;
              node.setLayout({x: nodeX}, true);
          });
      }
  
      /**
       * using Gauss-Seidel iterations method to compute the node depth(y-position).
       * @param {module:echarts/data/Graph~Node} nodes
       * @param {module:echarts/data/Graph~Edge} edges
       * @param {number} height
       * @param {numbber} nodeGap
       * @param {number} iterations
       */
      function computeNodeDepths(nodes, edges, height, nodeGap, iterations) {
          var nodesByBreadth = nest()
              .key(function (d) {
                  return d.getLayout().x;
              })
              .sortKeys(ascending)
              .entries(nodes)
              .map(function (d) {
                  return d.values;
              });
  
          initializeNodeDepth(nodes, nodesByBreadth, edges, height, nodeGap);
          resolveCollisions(nodesByBreadth, nodeGap, height);
  
          for (var alpha = 1; iterations > 0; iterations--) {
              alpha *= 0.99;
              relaxRightToLeft(nodesByBreadth, alpha);
              resolveCollisions(nodesByBreadth, nodeGap, height);
              relaxLeftToRight(nodesByBreadth, alpha);
              resolveCollisions(nodesByBreadth, nodeGap, height);
          }
      }
  
      /**
       * compute the original y-position for each node.
       * @param {module:echarts/data/Graph~Node} nodes
       * @param {Array.<Array.<module:echarts/data/Graph~Node>>} nodesByBreadth
       * @param {module:echarts/data/Graph~Edge} edges
       * @param {number} height
       * @param {number} nodeGap
       */
      function initializeNodeDepth(nodes, nodesByBreadth, edges, height, nodeGap) {
          var kyArray = [];
          zrUtil.each(nodesByBreadth, function (nodes) {
              var n = nodes.length;
              var sum = 0;
              zrUtil.each(nodes, function (node) {
                  sum += node.getLayout().value;
              });
              var ky = (height - (n-1) * nodeGap) / sum;
              kyArray.push(ky);
          });
          kyArray.sort(function (a, b) {
              return a - b;
          });
          var ky0 = kyArray[0];
  
          zrUtil.each(nodesByBreadth, function (nodes) {
              zrUtil.each(nodes, function (node, i) {
                  node.setLayout({y: i}, true);
                  var nodeDy = node.getLayout().value * ky0;
                  node.setLayout({dy: nodeDy}, true);
              });
          });
  
          zrUtil.each(edges, function (edge) {
              var edgeDy = +edge.getValue() * ky0;
              edge.setLayout({dy: edgeDy}, true);
          });
      }
  
      /**
       * resolve the collision of initialized depth.
       * @param {Array.<Array.<module:echarts/data/Graph~Node>>} nodesByBreadth
       * @param {number} nodeGap
       * @param {number} height
       */
      function resolveCollisions(nodesByBreadth, nodeGap, height) {
          zrUtil.each(nodesByBreadth, function (nodes) {
              var node;
              var dy;
              var y0 = 0;
              var n = nodes.length;
              var i;
  
              nodes.sort(ascendingDepth);
  
              for (i = 0; i < n; i++) {
                  node = nodes[i];
                  dy = y0 - node.getLayout().y;
                  if(dy > 0) {
                      var nodeY = node.getLayout().y + dy;
                      node.setLayout({y: nodeY}, true);
                  }
                  y0 = node.getLayout().y + node.getLayout().dy + nodeGap;
              }
  
              // if the bottommost node goes outside the biunds, push it back up
              dy = y0 - nodeGap - height;
              if (dy > 0) {
                  var nodeY = node.getLayout().y -dy;
                  node.setLayout({y: nodeY}, true);
                  y0 = node.getLayout().y;
                  for (i = n - 2; i >= 0; --i) {
                      node = nodes[i];
                      dy = node.getLayout().y + node.getLayout().dy + nodeGap - y0;
                      if (dy > 0) {
                          nodeY = node.getLayout().y - dy;
                          node.setLayout({y: nodeY}, true);
                      }
                      y0 = node.getLayout().y;
                  }
              }
          });
      }
  
      /**
       * change the y-position of the nodes, except most the right side nodes.
       * @param {Array.<Array.<module:echarts/data/Graph~Node>>} nodesByBreadth
       * @param {number} alpha
       */
      function relaxRightToLeft(nodesByBreadth, alpha) {
          zrUtil.each(nodesByBreadth.slice().reverse(), function (nodes) {
              zrUtil.each(nodes, function (node) {
                  if (node.outEdges.length) {
                      var y = sum(node.outEdges, weightedTarget) / sum(node.outEdges, getEdgeValue);
                      var nodeY = node.getLayout().y + (y - center(node)) * alpha;
                      node.setLayout({y: nodeY}, true);
                  }
              });
          });
      }
  
      function weightedTarget(edge) {
          return center(edge.node2) * edge.getValue();
      }
  
      /**
       * change the y-position of the nodes, except most the left side nodes.
       * @param {Array.<Array.<module:echarts/data/Graph~Node>>} nodesByBreadth
       * @param {number} alpha
       */
      function relaxLeftToRight(nodesByBreadth, alpha) {
          zrUtil.each(nodesByBreadth, function (nodes) {
              zrUtil.each(nodes, function (node) {
                  if (node.inEdges.length) {
                      var y = sum(node.inEdges, weightedSource) / sum(node.inEdges, getEdgeValue);
                      var nodeY = node.getLayout().y + (y - center(node)) * alpha;
                      node.setLayout({y: nodeY}, true);
                  }
              });
          });
      }
  
      function weightedSource(edge) {
          return center(edge.node1) * edge.getValue();
      }
  
      /**
       * compute the depth(y-position) of each edge.
       * @param {module:echarts/data/Graph~Node} nodes
       */
      function computeEdgeDepths(nodes) {
          zrUtil.each(nodes, function (node) {
              node.outEdges.sort(ascendingTargetDepth);
              node.inEdges.sort(ascendingSourceDepth);
          });
          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;
              });
          });
      }
  
      function ascendingTargetDepth(a, b) {
          return a.node2.getLayout().y - b.node2.getLayout().y;
      }
  
      function ascendingSourceDepth(a, b) {
          return a.node1.getLayout().y - b.node1.getLayout().y;
      }
  
      function sum(array, f) {
          var s = 0;
          var n = array.length;
          var a;
          var i = -1;
          if (arguments.length === 1) {
              while (++i < n) {
                  a = +array[i];
                  if (!isNaN(a)) {
                      s += a;
                  }
              }
          }
          else {
              while (++i < n) {
                  a = +f.call(array, array[i], i);
                  if(!isNaN(a)) {
                      s += a;
                  }
              }
          }
          return s;
      }
  
      function center(node) {
          return node.getLayout().y + node.getLayout().dy / 2;
      }
  
      function ascendingDepth(a, b) {
          return a.getLayout().y - b.getLayout().y;
      }
  
      function ascending(a, b) {
          return a < b ? -1 : a > b ? 1 : a == b ? 0 : NaN;
      }
  
      function getEdgeValue(edge) {
          return edge.getValue();
      }
  
  