UNPKG

d3-sankey-diagram

Version:
4,126 lines 110 kB
(function (global, factory) {
	typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('d3-array'), require('d3-collection'), require('d3-selection'), require('d3-transition'), require('d3-dispatch'), require('d3-format'), require('d3-interpolate')) :
	typeof define === 'function' && define.amd ? define(['exports', 'd3-array', 'd3-collection', 'd3-selection', 'd3-transition', 'd3-dispatch', 'd3-format', 'd3-interpolate'], factory) :
	(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.d3 = global.d3 || {}, global.d3, global.d3, global.d3, global.d3, global.d3, global.d3, global.d3));
})(this, (function (exports, d3Array, d3Collection, d3Selection, d3Transition, d3Dispatch, d3Format, d3Interpolate) { 'use strict';

	function getDefaultExportFromCjs (x) {
		return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
	}

	var DEFAULT_EDGE_NAME = "\x00";
	var GRAPH_NODE = "\x00";
	var EDGE_KEY_DELIM = "\x01";

	// Implementation notes:
	//
	//  * Node id query functions should return string ids for the nodes
	//  * Edge id query functions should return an "edgeObj", edge object, that is
	//    composed of enough information to uniquely identify an edge: {v, w, name}.
	//  * Internally we use an "edgeId", a stringified form of the edgeObj, to
	//    reference edges. This is because we need a performant way to look these
	//    edges up and, object properties, which have string keys, are the closest
	//    we're going to get to a performant hashtable in JavaScript.

	let Graph$3 = class Graph {
	  _isDirected = true;
	  _isMultigraph = false;
	  _isCompound = false;

	  // Label for the graph itself
	  _label;

	  // Defaults to be set when creating a new node
	  _defaultNodeLabelFn = () => undefined;

	  // Defaults to be set when creating a new edge
	  _defaultEdgeLabelFn = () => undefined;

	  // v -> label
	  _nodes = {};

	  // v -> edgeObj
	  _in = {};

	  // u -> v -> Number
	  _preds = {};

	  // v -> edgeObj
	  _out = {};

	  // v -> w -> Number
	  _sucs = {};

	  // e -> edgeObj
	  _edgeObjs = {};

	  // e -> label
	  _edgeLabels = {};

	  /* Number of nodes in the graph. Should only be changed by the implementation. */
	  _nodeCount = 0;

	  /* Number of edges in the graph. Should only be changed by the implementation. */
	  _edgeCount = 0;

	  _parent;

	  _children;

	  constructor(opts) {
	    if (opts) {
	      this._isDirected = opts.hasOwnProperty("directed") ? opts.directed : true;
	      this._isMultigraph = opts.hasOwnProperty("multigraph") ? opts.multigraph : false;
	      this._isCompound = opts.hasOwnProperty("compound") ? opts.compound : false;
	    }

	    if (this._isCompound) {
	      // v -> parent
	      this._parent = {};

	      // v -> children
	      this._children = {};
	      this._children[GRAPH_NODE] = {};
	    }
	  }

	  /* === Graph functions ========= */

	  /**
	   * Whether graph was created with 'directed' flag set to true or not.
	   */
	  isDirected() {
	    return this._isDirected;
	  }

	  /**
	   * Whether graph was created with 'multigraph' flag set to true or not.
	   */
	  isMultigraph() {
	    return this._isMultigraph;
	  }

	  /**
	   * Whether graph was created with 'compound' flag set to true or not.
	   */
	  isCompound() {
	    return this._isCompound;
	  }

	  /**
	   * Sets the label of the graph.
	   */
	  setGraph(label) {
	    this._label = label;
	    return this;
	  }

	  /**
	   * Gets the graph label.
	   */
	  graph() {
	    return this._label;
	  }


	  /* === Node functions ========== */

	  /**
	   * Sets the default node label. If newDefault is a function, it will be
	   * invoked ach time when setting a label for a node. Otherwise, this label
	   * will be assigned as default label in case if no label was specified while
	   * setting a node.
	   * Complexity: O(1).
	   */
	  setDefaultNodeLabel(newDefault) {
	    this._defaultNodeLabelFn = newDefault;
	    if (typeof newDefault !== 'function') {
	      this._defaultNodeLabelFn = () => newDefault;
	    }

	    return this;
	  }

	  /**
	   * Gets the number of nodes in the graph.
	   * Complexity: O(1).
	   */
	  nodeCount() {
	    return this._nodeCount;
	  }

	  /**
	   * Gets all nodes of the graph. Note, the in case of compound graph subnodes are
	   * not included in list.
	   * Complexity: O(1).
	   */
	  nodes() {
	    return Object.keys(this._nodes);
	  }

	  /**
	   * Gets list of nodes without in-edges.
	   * Complexity: O(|V|).
	   */
	  sources() {
	    var self = this;
	    return this.nodes().filter(v => Object.keys(self._in[v]).length === 0);
	  }

	  /**
	   * Gets list of nodes without out-edges.
	   * Complexity: O(|V|).
	   */
	  sinks() {
	    var self = this;
	    return this.nodes().filter(v => Object.keys(self._out[v]).length === 0);
	  }

	  /**
	   * Invokes setNode method for each node in names list.
	   * Complexity: O(|names|).
	   */
	  setNodes(vs, value) {
	    var args = arguments;
	    var self = this;
	    vs.forEach(function(v) {
	      if (args.length > 1) {
	        self.setNode(v, value);
	      } else {
	        self.setNode(v);
	      }
	    });
	    return this;
	  }

	  /**
	   * Creates or updates the value for the node v in the graph. If label is supplied
	   * it is set as the value for the node. If label is not supplied and the node was
	   * created by this call then the default node label will be assigned.
	   * Complexity: O(1).
	   */
	  setNode(v, value) {
	    if (this._nodes.hasOwnProperty(v)) {
	      if (arguments.length > 1) {
	        this._nodes[v] = value;
	      }
	      return this;
	    }

	    this._nodes[v] = arguments.length > 1 ? value : this._defaultNodeLabelFn(v);
	    if (this._isCompound) {
	      this._parent[v] = GRAPH_NODE;
	      this._children[v] = {};
	      this._children[GRAPH_NODE][v] = true;
	    }
	    this._in[v] = {};
	    this._preds[v] = {};
	    this._out[v] = {};
	    this._sucs[v] = {};
	    ++this._nodeCount;
	    return this;
	  }

	  /**
	   * Gets the label of node with specified name.
	   * Complexity: O(|V|).
	   */
	  node(v) {
	    return this._nodes[v];
	  }

	  /**
	   * Detects whether graph has a node with specified name or not.
	   */
	  hasNode(v) {
	    return this._nodes.hasOwnProperty(v);
	  }

	  /**
	   * Remove the node with the name from the graph or do nothing if the node is not in
	   * the graph. If the node was removed this function also removes any incident
	   * edges.
	   * Complexity: O(1).
	   */
	  removeNode(v) {
	    var self = this;
	    if (this._nodes.hasOwnProperty(v)) {
	      var removeEdge = e => self.removeEdge(self._edgeObjs[e]);
	      delete this._nodes[v];
	      if (this._isCompound) {
	        this._removeFromParentsChildList(v);
	        delete this._parent[v];
	        this.children(v).forEach(function(child) {
	          self.setParent(child);
	        });
	        delete this._children[v];
	      }
	      Object.keys(this._in[v]).forEach(removeEdge);
	      delete this._in[v];
	      delete this._preds[v];
	      Object.keys(this._out[v]).forEach(removeEdge);
	      delete this._out[v];
	      delete this._sucs[v];
	      --this._nodeCount;
	    }
	    return this;
	  }

	  /**
	   * Sets node p as a parent for node v if it is defined, or removes the
	   * parent for v if p is undefined. Method throws an exception in case of
	   * invoking it in context of noncompound graph.
	   * Average-case complexity: O(1).
	   */
	  setParent(v, parent) {
	    if (!this._isCompound) {
	      throw new Error("Cannot set parent in a non-compound graph");
	    }

	    if (parent === undefined) {
	      parent = GRAPH_NODE;
	    } else {
	      // Coerce parent to string
	      parent += "";
	      for (var ancestor = parent; ancestor !== undefined; ancestor = this.parent(ancestor)) {
	        if (ancestor === v) {
	          throw new Error("Setting " + parent+ " as parent of " + v +
	              " would create a cycle");
	        }
	      }

	      this.setNode(parent);
	    }

	    this.setNode(v);
	    this._removeFromParentsChildList(v);
	    this._parent[v] = parent;
	    this._children[parent][v] = true;
	    return this;
	  }

	  _removeFromParentsChildList(v) {
	    delete this._children[this._parent[v]][v];
	  }

	  /**
	   * Gets parent node for node v.
	   * Complexity: O(1).
	   */
	  parent(v) {
	    if (this._isCompound) {
	      var parent = this._parent[v];
	      if (parent !== GRAPH_NODE) {
	        return parent;
	      }
	    }
	  }

	  /**
	   * Gets list of direct children of node v.
	   * Complexity: O(1).
	   */
	  children(v = GRAPH_NODE) {
	    if (this._isCompound) {
	      var children = this._children[v];
	      if (children) {
	        return Object.keys(children);
	      }
	    } else if (v === GRAPH_NODE) {
	      return this.nodes();
	    } else if (this.hasNode(v)) {
	      return [];
	    }
	  }

	  /**
	   * Return all nodes that are predecessors of the specified node or undefined if node v is not in
	   * the graph. Behavior is undefined for undirected graphs - use neighbors instead.
	   * Complexity: O(|V|).
	   */
	  predecessors(v) {
	    var predsV = this._preds[v];
	    if (predsV) {
	      return Object.keys(predsV);
	    }
	  }

	  /**
	   * Return all nodes that are successors of the specified node or undefined if node v is not in
	   * the graph. Behavior is undefined for undirected graphs - use neighbors instead.
	   * Complexity: O(|V|).
	   */
	  successors(v) {
	    var sucsV = this._sucs[v];
	    if (sucsV) {
	      return Object.keys(sucsV);
	    }
	  }

	  /**
	   * Return all nodes that are predecessors or successors of the specified node or undefined if
	   * node v is not in the graph.
	   * Complexity: O(|V|).
	   */
	  neighbors(v) {
	    var preds = this.predecessors(v);
	    if (preds) {
	      const union = new Set(preds);
	      for (var succ of this.successors(v)) {
	        union.add(succ);
	      }

	      return Array.from(union.values());
	    }
	  }

	  isLeaf(v) {
	    var neighbors;
	    if (this.isDirected()) {
	      neighbors = this.successors(v);
	    } else {
	      neighbors = this.neighbors(v);
	    }
	    return neighbors.length === 0;
	  }

	  /**
	   * Creates new graph with nodes filtered via filter. Edges incident to rejected node
	   * are also removed. In case of compound graph, if parent is rejected by filter,
	   * than all its children are rejected too.
	   * Average-case complexity: O(|E|+|V|).
	   */
	  filterNodes(filter) {
	    var copy = new this.constructor({
	      directed: this._isDirected,
	      multigraph: this._isMultigraph,
	      compound: this._isCompound
	    });

	    copy.setGraph(this.graph());

	    var self = this;
	    Object.entries(this._nodes).forEach(function([v, value]) {
	      if (filter(v)) {
	        copy.setNode(v, value);
	      }
	    });

	    Object.values(this._edgeObjs).forEach(function(e) {
	      if (copy.hasNode(e.v) && copy.hasNode(e.w)) {
	        copy.setEdge(e, self.edge(e));
	      }
	    });

	    var parents = {};
	    function findParent(v) {
	      var parent = self.parent(v);
	      if (parent === undefined || copy.hasNode(parent)) {
	        parents[v] = parent;
	        return parent;
	      } else if (parent in parents) {
	        return parents[parent];
	      } else {
	        return findParent(parent);
	      }
	    }

	    if (this._isCompound) {
	      copy.nodes().forEach(v => copy.setParent(v, findParent(v)));
	    }

	    return copy;
	  }

	  /* === Edge functions ========== */

	  /**
	   * Sets the default edge label or factory function. This label will be
	   * assigned as default label in case if no label was specified while setting
	   * an edge or this function will be invoked each time when setting an edge
	   * with no label specified and returned value * will be used as a label for edge.
	   * Complexity: O(1).
	   */
	  setDefaultEdgeLabel(newDefault) {
	    this._defaultEdgeLabelFn = newDefault;
	    if (typeof newDefault !== 'function') {
	      this._defaultEdgeLabelFn = () => newDefault;
	    }

	    return this;
	  }

	  /**
	   * Gets the number of edges in the graph.
	   * Complexity: O(1).
	   */
	  edgeCount() {
	    return this._edgeCount;
	  }

	  /**
	   * Gets edges of the graph. In case of compound graph subgraphs are not considered.
	   * Complexity: O(|E|).
	   */
	  edges() {
	    return Object.values(this._edgeObjs);
	  }

	  /**
	   * Establish an edges path over the nodes in nodes list. If some edge is already
	   * exists, it will update its label, otherwise it will create an edge between pair
	   * of nodes with label provided or default label if no label provided.
	   * Complexity: O(|nodes|).
	   */
	  setPath(vs, value) {
	    var self = this;
	    var args = arguments;
	    vs.reduce(function(v, w) {
	      if (args.length > 1) {
	        self.setEdge(v, w, value);
	      } else {
	        self.setEdge(v, w);
	      }
	      return w;
	    });
	    return this;
	  }

	  /**
	   * Creates or updates the label for the edge (v, w) with the optionally supplied
	   * name. If label is supplied it is set as the value for the edge. If label is not
	   * supplied and the edge was created by this call then the default edge label will
	   * be assigned. The name parameter is only useful with multigraphs.
	   */
	  setEdge() {
	    var v, w, name, value;
	    var valueSpecified = false;
	    var arg0 = arguments[0];

	    if (typeof arg0 === "object" && arg0 !== null && "v" in arg0) {
	      v = arg0.v;
	      w = arg0.w;
	      name = arg0.name;
	      if (arguments.length === 2) {
	        value = arguments[1];
	        valueSpecified = true;
	      }
	    } else {
	      v = arg0;
	      w = arguments[1];
	      name = arguments[3];
	      if (arguments.length > 2) {
	        value = arguments[2];
	        valueSpecified = true;
	      }
	    }

	    v = "" + v;
	    w = "" + w;
	    if (name !== undefined) {
	      name = "" + name;
	    }

	    var e = edgeArgsToId(this._isDirected, v, w, name);
	    if (this._edgeLabels.hasOwnProperty(e)) {
	      if (valueSpecified) {
	        this._edgeLabels[e] = value;
	      }
	      return this;
	    }

	    if (name !== undefined && !this._isMultigraph) {
	      throw new Error("Cannot set a named edge when isMultigraph = false");
	    }

	    // It didn't exist, so we need to create it.
	    // First ensure the nodes exist.
	    this.setNode(v);
	    this.setNode(w);

	    this._edgeLabels[e] = valueSpecified ? value : this._defaultEdgeLabelFn(v, w, name);

	    var edgeObj = edgeArgsToObj(this._isDirected, v, w, name);
	    // Ensure we add undirected edges in a consistent way.
	    v = edgeObj.v;
	    w = edgeObj.w;

	    Object.freeze(edgeObj);
	    this._edgeObjs[e] = edgeObj;
	    incrementOrInitEntry(this._preds[w], v);
	    incrementOrInitEntry(this._sucs[v], w);
	    this._in[w][e] = edgeObj;
	    this._out[v][e] = edgeObj;
	    this._edgeCount++;
	    return this;
	  }

	  /**
	   * Gets the label for the specified edge.
	   * Complexity: O(1).
	   */
	  edge(v, w, name) {
	    var e = (arguments.length === 1
	      ? edgeObjToId(this._isDirected, arguments[0])
	      : edgeArgsToId(this._isDirected, v, w, name));
	    return this._edgeLabels[e];
	  }

	  /**
	   * Gets the label for the specified edge and converts it to an object.
	   * Complexity: O(1)
	   */
	  edgeAsObj() {
	    const edge = this.edge(...arguments);
	    if (typeof edge !== "object") {
	      return {label: edge};
	    }

	    return edge;
	  }

	  /**
	   * Detects whether the graph contains specified edge or not. No subgraphs are considered.
	   * Complexity: O(1).
	   */
	  hasEdge(v, w, name) {
	    var e = (arguments.length === 1
	      ? edgeObjToId(this._isDirected, arguments[0])
	      : edgeArgsToId(this._isDirected, v, w, name));
	    return this._edgeLabels.hasOwnProperty(e);
	  }

	  /**
	   * Removes the specified edge from the graph. No subgraphs are considered.
	   * Complexity: O(1).
	   */
	  removeEdge(v, w, name) {
	    var e = (arguments.length === 1
	      ? edgeObjToId(this._isDirected, arguments[0])
	      : edgeArgsToId(this._isDirected, v, w, name));
	    var edge = this._edgeObjs[e];
	    if (edge) {
	      v = edge.v;
	      w = edge.w;
	      delete this._edgeLabels[e];
	      delete this._edgeObjs[e];
	      decrementOrRemoveEntry(this._preds[w], v);
	      decrementOrRemoveEntry(this._sucs[v], w);
	      delete this._in[w][e];
	      delete this._out[v][e];
	      this._edgeCount--;
	    }
	    return this;
	  }

	  /**
	   * Return all edges that point to the node v. Optionally filters those edges down to just those
	   * coming from node u. Behavior is undefined for undirected graphs - use nodeEdges instead.
	   * Complexity: O(|E|).
	   */
	  inEdges(v, u) {
	    var inV = this._in[v];
	    if (inV) {
	      var edges = Object.values(inV);
	      if (!u) {
	        return edges;
	      }
	      return edges.filter(edge => edge.v === u);
	    }
	  }

	  /**
	   * Return all edges that are pointed at by node v. Optionally filters those edges down to just
	   * those point to w. Behavior is undefined for undirected graphs - use nodeEdges instead.
	   * Complexity: O(|E|).
	   */
	  outEdges(v, w) {
	    var outV = this._out[v];
	    if (outV) {
	      var edges = Object.values(outV);
	      if (!w) {
	        return edges;
	      }
	      return edges.filter(edge => edge.w === w);
	    }
	  }

	  /**
	   * Returns all edges to or from node v regardless of direction. Optionally filters those edges
	   * down to just those between nodes v and w regardless of direction.
	   * Complexity: O(|E|).
	   */
	  nodeEdges(v, w) {
	    var inEdges = this.inEdges(v, w);
	    if (inEdges) {
	      return inEdges.concat(this.outEdges(v, w));
	    }
	  }
	};

	function incrementOrInitEntry(map, k) {
	  if (map[k]) {
	    map[k]++;
	  } else {
	    map[k] = 1;
	  }
	}

	function decrementOrRemoveEntry(map, k) {
	  if (!--map[k]) { delete map[k]; }
	}

	function edgeArgsToId(isDirected, v_, w_, name) {
	  var v = "" + v_;
	  var w = "" + w_;
	  if (!isDirected && v > w) {
	    var tmp = v;
	    v = w;
	    w = tmp;
	  }
	  return v + EDGE_KEY_DELIM + w + EDGE_KEY_DELIM +
	             (name === undefined ? DEFAULT_EDGE_NAME : name);
	}

	function edgeArgsToObj(isDirected, v_, w_, name) {
	  var v = "" + v_;
	  var w = "" + w_;
	  if (!isDirected && v > w) {
	    var tmp = v;
	    v = w;
	    w = tmp;
	  }
	  var edgeObj =  { v: v, w: w };
	  if (name) {
	    edgeObj.name = name;
	  }
	  return edgeObj;
	}

	function edgeObjToId(isDirected, edgeObj) {
	  return edgeArgsToId(isDirected, edgeObj.v, edgeObj.w, edgeObj.name);
	}

	var graph = Graph$3;

	var version = '2.2.3';

	// Includes only the "core" of graphlib
	var lib$1 = {
	  Graph: graph,
	  version: version
	};

	var Graph$2 = graph;

	var json = {
	  write: write,
	  read: read
	};

	/**
	 * Creates a JSON representation of the graph that can be serialized to a string with
	 * JSON.stringify. The graph can later be restored using json.read.
	 */
	function write(g) {
	  var json = {
	    options: {
	      directed: g.isDirected(),
	      multigraph: g.isMultigraph(),
	      compound: g.isCompound()
	    },
	    nodes: writeNodes(g),
	    edges: writeEdges(g)
	  };

	  if (g.graph() !== undefined) {
	    json.value = structuredClone(g.graph());
	  }
	  return json;
	}

	function writeNodes(g) {
	  return g.nodes().map(function(v) {
	    var nodeValue = g.node(v);
	    var parent = g.parent(v);
	    var node = { v: v };
	    if (nodeValue !== undefined) {
	      node.value = nodeValue;
	    }
	    if (parent !== undefined) {
	      node.parent = parent;
	    }
	    return node;
	  });
	}

	function writeEdges(g) {
	  return g.edges().map(function(e) {
	    var edgeValue = g.edge(e);
	    var edge = { v: e.v, w: e.w };
	    if (e.name !== undefined) {
	      edge.name = e.name;
	    }
	    if (edgeValue !== undefined) {
	      edge.value = edgeValue;
	    }
	    return edge;
	  });
	}

	/**
	 * Takes JSON as input and returns the graph representation.
	 *
	 * @example
	 * var g2 = graphlib.json.read(JSON.parse(str));
	 * g2.nodes();
	 * // ['a', 'b']
	 * g2.edges()
	 * // [ { v: 'a', w: 'b' } ]
	 */
	function read(json) {
	  var g = new Graph$2(json.options).setGraph(json.value);
	  json.nodes.forEach(function(entry) {
	    g.setNode(entry.v, entry.value);
	    if (entry.parent) {
	      g.setParent(entry.v, entry.parent);
	    }
	  });
	  json.edges.forEach(function(entry) {
	    g.setEdge({ v: entry.v, w: entry.w, name: entry.name }, entry.value);
	  });
	  return g;
	}

	var components_1 = components;

	function components(g) {
	  var visited = {};
	  var cmpts = [];
	  var cmpt;

	  function dfs(v) {
	    if (visited.hasOwnProperty(v)) return;
	    visited[v] = true;
	    cmpt.push(v);
	    g.successors(v).forEach(dfs);
	    g.predecessors(v).forEach(dfs);
	  }

	  g.nodes().forEach(function(v) {
	    cmpt = [];
	    dfs(v);
	    if (cmpt.length) {
	      cmpts.push(cmpt);
	    }
	  });

	  return cmpts;
	}

	/**
	 * A min-priority queue data structure. This algorithm is derived from Cormen,
	 * et al., "Introduction to Algorithms". The basic idea of a min-priority
	 * queue is that you can efficiently (in O(1) time) get the smallest key in
	 * the queue. Adding and removing elements takes O(log n) time. A key can
	 * have its priority decreased in O(log n) time.
	 */

	let PriorityQueue$2 = class PriorityQueue {
	  _arr = [];
	  _keyIndices = {};

	  /**
	   * Returns the number of elements in the queue. Takes `O(1)` time.
	   */
	  size() {
	    return this._arr.length;
	  }

	  /**
	   * Returns the keys that are in the queue. Takes `O(n)` time.
	   */
	  keys() {
	    return this._arr.map(function(x) { return x.key; });
	  }

	  /**
	   * Returns `true` if **key** is in the queue and `false` if not.
	   */
	  has(key) {
	    return this._keyIndices.hasOwnProperty(key);
	  }

	  /**
	   * Returns the priority for **key**. If **key** is not present in the queue
	   * then this function returns `undefined`. Takes `O(1)` time.
	   *
	   * @param {Object} key
	   */
	  priority(key) {
	    var index = this._keyIndices[key];
	    if (index !== undefined) {
	      return this._arr[index].priority;
	    }
	  }

	  /**
	   * Returns the key for the minimum element in this queue. If the queue is
	   * empty this function throws an Error. Takes `O(1)` time.
	   */
	  min() {
	    if (this.size() === 0) {
	      throw new Error("Queue underflow");
	    }
	    return this._arr[0].key;
	  }

	  /**
	   * Inserts a new key into the priority queue. If the key already exists in
	   * the queue this function returns `false`; otherwise it will return `true`.
	   * Takes `O(n)` time.
	   *
	   * @param {Object} key the key to add
	   * @param {Number} priority the initial priority for the key
	   */
	  add(key, priority) {
	    var keyIndices = this._keyIndices;
	    key = String(key);
	    if (!keyIndices.hasOwnProperty(key)) {
	      var arr = this._arr;
	      var index = arr.length;
	      keyIndices[key] = index;
	      arr.push({key: key, priority: priority});
	      this._decrease(index);
	      return true;
	    }
	    return false;
	  }

	  /**
	   * Removes and returns the smallest key in the queue. Takes `O(log n)` time.
	   */
	  removeMin() {
	    this._swap(0, this._arr.length - 1);
	    var min = this._arr.pop();
	    delete this._keyIndices[min.key];
	    this._heapify(0);
	    return min.key;
	  }

	  /**
	   * Decreases the priority for **key** to **priority**. If the new priority is
	   * greater than the previous priority, this function will throw an Error.
	   *
	   * @param {Object} key the key for which to raise priority
	   * @param {Number} priority the new priority for the key
	   */
	  decrease(key, priority) {
	    var index = this._keyIndices[key];
	    if (priority > this._arr[index].priority) {
	      throw new Error("New priority is greater than current priority. " +
	          "Key: " + key + " Old: " + this._arr[index].priority + " New: " + priority);
	    }
	    this._arr[index].priority = priority;
	    this._decrease(index);
	  }

	  _heapify(i) {
	    var arr = this._arr;
	    var l = 2 * i;
	    var r = l + 1;
	    var largest = i;
	    if (l < arr.length) {
	      largest = arr[l].priority < arr[largest].priority ? l : largest;
	      if (r < arr.length) {
	        largest = arr[r].priority < arr[largest].priority ? r : largest;
	      }
	      if (largest !== i) {
	        this._swap(i, largest);
	        this._heapify(largest);
	      }
	    }
	  }

	  _decrease(index) {
	    var arr = this._arr;
	    var priority = arr[index].priority;
	    var parent;
	    while (index !== 0) {
	      parent = index >> 1;
	      if (arr[parent].priority < priority) {
	        break;
	      }
	      this._swap(index, parent);
	      index = parent;
	    }
	  }

	  _swap(i, j) {
	    var arr = this._arr;
	    var keyIndices = this._keyIndices;
	    var origArrI = arr[i];
	    var origArrJ = arr[j];
	    arr[i] = origArrJ;
	    arr[j] = origArrI;
	    keyIndices[origArrJ.key] = i;
	    keyIndices[origArrI.key] = j;
	  }
	};

	var priorityQueue = PriorityQueue$2;

	var PriorityQueue$1 = priorityQueue;

	var dijkstra_1 = dijkstra$1;

	var DEFAULT_WEIGHT_FUNC$1 = () => 1;

	function dijkstra$1(g, source, weightFn, edgeFn) {
	  return runDijkstra(g, String(source),
	    weightFn || DEFAULT_WEIGHT_FUNC$1,
	    edgeFn || function(v) { return g.outEdges(v); });
	}

	function runDijkstra(g, source, weightFn, edgeFn) {
	  var results = {};
	  var pq = new PriorityQueue$1();
	  var v, vEntry;

	  var updateNeighbors = function(edge) {
	    var w = edge.v !== v ? edge.v : edge.w;
	    var wEntry = results[w];
	    var weight = weightFn(edge);
	    var distance = vEntry.distance + weight;

	    if (weight < 0) {
	      throw new Error("dijkstra does not allow negative edge weights. " +
	                      "Bad edge: " + edge + " Weight: " + weight);
	    }

	    if (distance < wEntry.distance) {
	      wEntry.distance = distance;
	      wEntry.predecessor = v;
	      pq.decrease(w, distance);
	    }
	  };

	  g.nodes().forEach(function(v) {
	    var distance = v === source ? 0 : Number.POSITIVE_INFINITY;
	    results[v] = { distance: distance };
	    pq.add(v, distance);
	  });

	  while (pq.size() > 0) {
	    v = pq.removeMin();
	    vEntry = results[v];
	    if (vEntry.distance === Number.POSITIVE_INFINITY) {
	      break;
	    }

	    edgeFn(v).forEach(updateNeighbors);
	  }

	  return results;
	}

	var dijkstra = dijkstra_1;

	var dijkstraAll_1 = dijkstraAll;

	function dijkstraAll(g, weightFunc, edgeFunc) {
	  return g.nodes().reduce(function(acc, v) {
	    acc[v] = dijkstra(g, v, weightFunc, edgeFunc);
	    return acc;
	  }, {});
	}

	var tarjan_1 = tarjan$1;

	function tarjan$1(g) {
	  var index = 0;
	  var stack = [];
	  var visited = {}; // node id -> { onStack, lowlink, index }
	  var results = [];

	  function dfs(v) {
	    var entry = visited[v] = {
	      onStack: true,
	      lowlink: index,
	      index: index++
	    };
	    stack.push(v);

	    g.successors(v).forEach(function(w) {
	      if (!visited.hasOwnProperty(w)) {
	        dfs(w);
	        entry.lowlink = Math.min(entry.lowlink, visited[w].lowlink);
	      } else if (visited[w].onStack) {
	        entry.lowlink = Math.min(entry.lowlink, visited[w].index);
	      }
	    });

	    if (entry.lowlink === entry.index) {
	      var cmpt = [];
	      var w;
	      do {
	        w = stack.pop();
	        visited[w].onStack = false;
	        cmpt.push(w);
	      } while (v !== w);
	      results.push(cmpt);
	    }
	  }

	  g.nodes().forEach(function(v) {
	    if (!visited.hasOwnProperty(v)) {
	      dfs(v);
	    }
	  });

	  return results;
	}

	var tarjan = tarjan_1;

	var findCycles_1 = findCycles;

	function findCycles(g) {
	  return tarjan(g).filter(function(cmpt) {
	    return cmpt.length > 1 || (cmpt.length === 1 && g.hasEdge(cmpt[0], cmpt[0]));
	  });
	}

	var floydWarshall_1 = floydWarshall;

	var DEFAULT_WEIGHT_FUNC = () => 1;

	function floydWarshall(g, weightFn, edgeFn) {
	  return runFloydWarshall(g,
	    weightFn || DEFAULT_WEIGHT_FUNC,
	    edgeFn || function(v) { return g.outEdges(v); });
	}

	function runFloydWarshall(g, weightFn, edgeFn) {
	  var results = {};
	  var nodes = g.nodes();

	  nodes.forEach(function(v) {
	    results[v] = {};
	    results[v][v] = { distance: 0 };
	    nodes.forEach(function(w) {
	      if (v !== w) {
	        results[v][w] = { distance: Number.POSITIVE_INFINITY };
	      }
	    });
	    edgeFn(v).forEach(function(edge) {
	      var w = edge.v === v ? edge.w : edge.v;
	      var d = weightFn(edge);
	      results[v][w] = { distance: d, predecessor: v };
	    });
	  });

	  nodes.forEach(function(k) {
	    var rowK = results[k];
	    nodes.forEach(function(i) {
	      var rowI = results[i];
	      nodes.forEach(function(j) {
	        var ik = rowI[k];
	        var kj = rowK[j];
	        var ij = rowI[j];
	        var altDistance = ik.distance + kj.distance;
	        if (altDistance < ij.distance) {
	          ij.distance = altDistance;
	          ij.predecessor = kj.predecessor;
	        }
	      });
	    });
	  });

	  return results;
	}

	function topsort$1(g) {
	  var visited = {};
	  var stack = {};
	  var results = [];

	  function visit(node) {
	    if (stack.hasOwnProperty(node)) {
	      throw new CycleException();
	    }

	    if (!visited.hasOwnProperty(node)) {
	      stack[node] = true;
	      visited[node] = true;
	      g.predecessors(node).forEach(visit);
	      delete stack[node];
	      results.push(node);
	    }
	  }

	  g.sinks().forEach(visit);

	  if (Object.keys(visited).length !== g.nodeCount()) {
	    throw new CycleException();
	  }

	  return results;
	}

	class CycleException extends Error {
	  constructor() {
	    super(...arguments);
	  }
	}

	var topsort_1 = topsort$1;
	topsort$1.CycleException = CycleException;

	var topsort = topsort_1;

	var isAcyclic_1 = isAcyclic;

	function isAcyclic(g) {
	  try {
	    topsort(g);
	  } catch (e) {
	    if (e instanceof topsort.CycleException) {
	      return false;
	    }
	    throw e;
	  }
	  return true;
	}

	var dfs_1 = dfs$2;

	/*
	 * A helper that preforms a pre- or post-order traversal on the input graph
	 * and returns the nodes in the order they were visited. If the graph is
	 * undirected then this algorithm will navigate using neighbors. If the graph
	 * is directed then this algorithm will navigate using successors.
	 *
	 * If the order is not "post", it will be treated as "pre".
	 */
	function dfs$2(g, vs, order) {
	  if (!Array.isArray(vs)) {
	    vs = [vs];
	  }

	  var navigation = g.isDirected() ? v => g.successors(v) : v => g.neighbors(v);
	  var orderFunc = order === "post" ? postOrderDfs : preOrderDfs;

	  var acc = [];
	  var visited = {};
	  vs.forEach(v => {
	    if (!g.hasNode(v)) {
	      throw new Error("Graph does not have node: " + v);
	    }

	    orderFunc(v, navigation, visited, acc);
	  });

	  return acc;
	}

	function postOrderDfs(v, navigation, visited, acc) {
	  var stack = [[v, false]];
	  while (stack.length > 0) {
	    var curr = stack.pop();
	    if (curr[1]) {
	      acc.push(curr[0]);
	    } else {
	      if (!visited.hasOwnProperty(curr[0])) {
	        visited[curr[0]] = true;
	        stack.push([curr[0], true]);
	        forEachRight(navigation(curr[0]), w => stack.push([w, false]));
	      }
	    }
	  }
	}

	function preOrderDfs(v, navigation, visited, acc) {
	  var stack = [v];
	  while (stack.length > 0) {
	    var curr = stack.pop();
	    if (!visited.hasOwnProperty(curr)) {
	      visited[curr] = true;
	      acc.push(curr);
	      forEachRight(navigation(curr), w => stack.push(w));
	    }
	  }
	}

	function forEachRight(array, iteratee) {
	  var length = array.length;
	  while (length--) {
	    iteratee(array[length], length, array);
	  }

	  return array;
	}

	var dfs$1 = dfs_1;

	var postorder_1 = postorder;

	function postorder(g, vs) {
	  return dfs$1(g, vs, "post");
	}

	var dfs = dfs_1;

	var preorder_1 = preorder;

	function preorder(g, vs) {
	  return dfs(g, vs, "pre");
	}

	var Graph$1 = graph;
	var PriorityQueue = priorityQueue;

	var prim_1 = prim;

	function prim(g, weightFunc) {
	  var result = new Graph$1();
	  var parents = {};
	  var pq = new PriorityQueue();
	  var v;

	  function updateNeighbors(edge) {
	    var w = edge.v === v ? edge.w : edge.v;
	    var pri = pq.priority(w);
	    if (pri !== undefined) {
	      var edgeWeight = weightFunc(edge);
	      if (edgeWeight < pri) {
	        parents[w] = v;
	        pq.decrease(w, edgeWeight);
	      }
	    }
	  }

	  if (g.nodeCount() === 0) {
	    return result;
	  }

	  g.nodes().forEach(function(v) {
	    pq.add(v, Number.POSITIVE_INFINITY);
	    result.setNode(v);
	  });

	  // Start from an arbitrary node
	  pq.decrease(g.nodes()[0], 0);

	  var init = false;
	  while (pq.size() > 0) {
	    v = pq.removeMin();
	    if (parents.hasOwnProperty(v)) {
	      result.setEdge(v, parents[v]);
	    } else if (init) {
	      throw new Error("Input graph is not connected: " + g);
	    } else {
	      init = true;
	    }

	    g.nodeEdges(v).forEach(updateNeighbors);
	  }

	  return result;
	}

	var alg$1 = {
	  components: components_1,
	  dijkstra: dijkstra_1,
	  dijkstraAll: dijkstraAll_1,
	  findCycles: findCycles_1,
	  floydWarshall: floydWarshall_1,
	  isAcyclic: isAcyclic_1,
	  postorder: postorder_1,
	  preorder: preorder_1,
	  prim: prim_1,
	  tarjan: tarjan_1,
	  topsort: topsort_1
	};

	/**
	 * Copyright (c) 2014, Chris Pettitt
	 * All rights reserved.
	 *
	 * Redistribution and use in source and binary forms, with or without
	 * modification, are permitted provided that the following conditions are met:
	 *
	 * 1. Redistributions of source code must retain the above copyright notice, this
	 * list of conditions and the following disclaimer.
	 *
	 * 2. Redistributions in binary form must reproduce the above copyright notice,
	 * this list of conditions and the following disclaimer in the documentation
	 * and/or other materials provided with the distribution.
	 *
	 * 3. Neither the name of the copyright holder nor the names of its contributors
	 * may be used to endorse or promote products derived from this software without
	 * specific prior written permission.
	 *
	 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
	 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
	 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
	 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
	 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
	 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
	 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
	 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
	 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
	 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
	 */

	var lib = lib$1;

	var graphlib = {
	  Graph: lib.Graph,
	  json: json,
	  alg: alg$1,
	  version: lib.version
	};

	var pkg = /*@__PURE__*/getDefaultExportFromCjs(graphlib);

	/**
	 * Create a new graph where nodes in the same rank set are merged into one node.
	 *
	 * Depends on the "backwards" attribute of the nodes in G, and the "delta"
	 * atribute of the edges.
	 *
	 */
	function groupedGraph (G, rankSets = []) {
	  // Not multigraph because this is only used for calculating ranks
	  const GG = new graphlib.Graph({ directed: true });
	  if (G.nodes().length === 0) return GG

	  // Make sure there is a minimum-rank set
	  rankSets = ensureSmin(G, rankSets);

	  // Construct map of node ids to the set they are in, if any
	  const nodeSets = d3Collection.map();
	  let set;
	  let id;
	  let i;
	  let j;
	  for (i = 0; i < rankSets.length; ++i) {
	    set = rankSets[i];
	    if (!set.nodes || set.nodes.length === 0) continue
	    id = '' + i;
	    for (j = 0; j < set.nodes.length; ++j) {
	      nodeSets.set(set.nodes[j], id);
	    }
	    GG.setNode(id, { type: set.type, nodes: set.nodes });
	  }

	  // use i to keep counting new ids
	  G.nodes().forEach(u => {
	    if (!nodeSets.has(u)) {
	      id = '' + (i++);
	      set = { type: 'same', nodes: [u] };
	      nodeSets.set(u, id);
	      GG.setNode(id, set);
	    }
	  });

	  // Add edges between nodes/groups
	  G.edges().forEach(e => {
	    const sourceSet = nodeSets.get(e.v);
	    const targetSet = nodeSets.get(e.w);

	    // Minimum edge length depends on direction of nodes:
	    //  -> to -> : 1
	    //  -> to <- : 0
	    //  <- to -> : 0 (in opposite direction??)
	    //  <- to <- : 1 in opposite direction
	    const edge = GG.edge(sourceSet, targetSet) || { delta: 0 };
	    if (sourceSet === targetSet) {
	      edge.delta = 0;
	      GG.setEdge(sourceSet, targetSet, edge);
	    } else if (G.node(e.v).backwards) {
	      edge.delta = Math.max(edge.delta, G.node(e.w).backwards ? 1 : 0);
	      GG.setEdge(targetSet, sourceSet, edge);
	    } else {
	      edge.delta = Math.max(edge.delta, G.node(e.w).backwards ? 0 : 1);
	      GG.setEdge(sourceSet, targetSet, edge);
	    }
	  });

	  return GG
	}

	// export function linkDelta (nodeBackwards, link) {
	//   if (nodeBackwards(link.source)) {
	//     return nodeBackwards(link.target) ? 1 : 0
	//   } else {
	//     return nodeBackwards(link.target) ? 0 : 1
	//   }
	// }

	function ensureSmin (G, rankSets) {
	  for (let i = 0; i < rankSets.length; ++i) {
	    if (rankSets[i].type === 'min') {
	      return rankSets // ok
	    }
	  }

	  // find the first sourceSet node, or else use the first node
	  const sources = G.sources();
	  const n0 = sources.length ? sources[0] : G.nodes()[0];
	  return [{ type: 'min', nodes: [n0] }].concat(rankSets)
	}

	/**
	 * Reverse edges in G to make it acyclic
	 */
	function makeAcyclic (G, v0) {
	  const tree = findSpanningTree(G, v0);

	  G.edges().forEach(e => {
	    const rel = nodeRelationship(tree, e.v, e.w);
	    if (rel < 0) {
	      const label = G.edge(e) || {};
	      label.reversed = true;
	      G.removeEdge(e);
	      G.setEdge(e.w, e.v, label);
	    }
	  });

	  return G
	}

	// find spanning tree, starting from the given node.
	// return new graph where nodes have depth and thread
	function findSpanningTree (G, v0) {
	  const visited = d3Collection.set();
	  const tree = new graphlib.Graph({ directed: true });
	  const thread = [];

	  if (!G.hasNode(v0)) throw Error('node not in graph')

	  doDfs(G, v0, visited, tree, thread);
	  G.nodes().forEach(u => {
	    if (!visited.has(u)) {
	      doDfs(G, u, visited, tree, thread);
	    }
	  });

	  thread.forEach((u, i) => {
	    tree.node(u).thread = (i + 1 < thread.length) ? thread[i + 1] : thread[0];
	  });

	  return tree
	}

	/**
	 * Returns 1 if w is a descendent of v, -1 if v is a descendent of w, and 0 if
	 * they are unrelated.
	 */
	function nodeRelationship (tree, v, w) {
	  const V = tree.node(v);
	  const W = tree.node(w);
	  if (V.depth < W.depth) {
	    let u = V.thread; // next node
	    while (tree.node(u).depth > V.depth) {
	      if (u === w) return 1
	      u = tree.node(u).thread;
	    }
	  } else if (W.depth < V.depth) {
	    let u = W.thread; // next node
	    while (tree.node(u).depth > W.depth) {
	      if (u === v) return -1
	      u = tree.node(u).thread;
	    }
	  }
	  return 0
	}

	function doDfs (G, v, visited, tree, thread, depth = 0) {
	  if (!visited.has(v)) {
	    visited.add(v);
	    thread.push(v);
	    tree.setNode(v, { depth });

	    // It doesn't seem to cause a problem with letters as node ids, but numbers
	    // are sorted when using G.successors(). So use G.outEdges() instead.
	    const next = G.outEdges(v).map(e => e.w);
	    next.forEach((w, i) => {
	      if (!visited.has(w)) {
	        tree.setEdge(v, w, { delta: 1 });
	      }
	      doDfs(G, w, visited, tree, thread, depth + 1);
	    });
	  }
	}

	/**
	 * Take an acyclic graph and assign initial ranks to the nodes
	 */
	function assignInitialRanks (G) {
	  // Place nodes on queue when they have no unmarked in-edges. Initially, this
	  // means sources.
	  const queue = G.sources();
	  const seen = d3Collection.set();
	  const marked = d3Collection.set();

	  // Mark any loops, since they don't affect rank assignment
	  G.edges().forEach(e => {
	    if (e.v === e.w) marked.add(edgeIdString(e));
	  });

	  G.nodes().forEach(v => {
	    G.node(v).rank = 0;
	  });

	  while (queue.length > 0) {
	    const v = queue.shift();
	    seen.add(v);

	    let V = G.node(v);
	    if (!V) G.setNode(v, (V = {}));

	    // Set rank to minimum of incoming edges
	    V.rank = 0;
	    G.inEdges(v).forEach(e => {
	      const delta = G.edge(e).delta === undefined ? 1 : G.edge(e).delta;
	      V.rank = Math.max(V.rank, G.node(e.v).rank + delta);
	    });

	    // Mark outgoing edges
	    G.outEdges(v).forEach(e => {
	      marked.add(edgeIdString(e));
	    });

	    // Add nodes to queue when they have no unmarked in-edges.
	    G.nodes().forEach(n => {
	      if (queue.indexOf(n) < 0 && !seen.has(n) &&
	          !G.inEdges(n).some(e => !marked.has(edgeIdString(e)))) {
	        queue.push(n);
	      }
	    });
	  }
	}

	function edgeIdString (e) {
	  return e.v + '\x01' + e.w + '\x01' + e.name
	}

	/**
	 * Assign ranks to the nodes in G, according to rankSets.
	 */
	function assignRanks (G, rankSets) {
	  // Group nodes together, and add additional edges from Smin to sources
	  const GG = groupedGraph(G, rankSets);
	  if (GG.nodeCount() === 0) return

	  // Add additional edges from Smin to sources
	  addTemporaryEdges(GG);

	  // Make the graph acyclic
	  makeAcyclic(GG, '0');

	  // Assign the initial ranks
	  assignInitialRanks(GG);

	  // XXX improve initial ranking...
	  moveSourcesRight(GG);

	  // Apply calculated ranks to original graph
	  // const ranks = []
	  GG.nodes().forEach(u => {
	    const groupedNode = GG.node(u);
	    // while (node.rank >= ranks.length) ranks.push([])
	    groupedNode.nodes.forEach(v => {
	      G.node(v).rank = groupedNode.rank;
	    });
	  });
	  // return ranks
	}

	// export function nodeBackwards (link) {
	//   if (link.source.direction === 'l') {
	//     return link.target.direction === 'l' ? 1 : 0
	//   } else {
	//     return link.target.direction === 'l' ? 0 : 1
	//   }
	// }

	function addTemporaryEdges (GG) {
	  // Add temporary edges between Smin and sources
	  GG.sources().forEach(u => {
	    if (u !== '0') {
	      GG.setEdge('0', u, { temp: true, delta: 0 });
	    }
	  });

	  // XXX Should also add edges from sinks to Smax

	  // G.nodes().forEach(u => {
	  //   if (!nodeSets.has(u)) {
	  //     GG.
	  //   }
	  // });
	}

	function moveSourcesRight (GG) {
	  GG.edges().forEach(e => {
	    const edge = GG.edge(e);
	    if (edge.temp) moveRight(e.w);
	  });

	  function moveRight (v) {
	    const V = GG.node(v);
	    const rank = d3Array.min(GG.outEdges(v), e => GG.node(e.w).rank - GG.edge(e).delta);
	    if (rank !== undefined) V.rank = rank;
	  }
	}

	const { alg } = pkg;

	function initialOrdering (G, ranks) {
	  const order = [];
	  if (ranks.length === 0) return order

	  // Start with sources & nodes in rank 0
	  const start = G.sources();
	  const nodeRanks = d3Collection.map();
	  ranks.forEach((nodes, i) => {
	    order.push([]);
	    nodes.forEach(u => {
	      if (i === 0 && start.indexOf(u) < 0) start.push(u);
	      nodeRanks.set(u, i);
	    });
	  });

	  alg.preorder(G, start).forEach(u => {
	    order[nodeRanks.get(u)].push(u);
	  });

	  return order
	}

	/** @module node-ordering/count-crossings */

	/**
	 * Count the total number of crossings between 2 layers.
	 *
	 * This is the sum of the countBetweenCrossings and countLoopCrossings.
	 *
	 * @param {Graph} G - The graph.
	 * @param {Array} orderA - List of node ids on left side.
	 * @param {Array} orderB - List of node ids on right side.
	 */
	function countCrossings (G, orderA, orderB) {
	  return (
	    countBetweenCrossings(G, orderA, orderB) // +
	    // countLoopCrossings(G, orderA, orderB)
	  )
	}

	/**
	 * Count the number of crossings of edges passing between 2 layers.
	 *
	 * Algorithm from
	 * http://jgaa.info/accepted/2004/BarthMutzelJuenger2004.8.2.pdf
	 *
	 * @param {Graph} G - The graph.
	 * @param {Array} orderA - List of node ids on left side.
	 * @param {Array} orderB - List of node ids on right side.
	 */
	function countBetweenCrossings (G, orderA, orderB) {
	  let north;
	  let south;

	  if (orderA.length > orderB.length) {
	    north = orderA;
	    south = orderB;
	  } else {
	    north = orderB;
	    south = orderA;
	  }
	  const q = south.length;

	  // lexicographically sorted edges from north to south
	  const southSeq = [];
	  north.forEach(u => {
	    south.forEach((v, j) => {
	      if (G.hasEdge(u, v) || G.hasEdge(v, u)) southSeq.push(j);
	    });
	  });

	  // build accumulator tree
	  let firstIndex = 1;
	  while (firstIndex < q) firstIndex *= 2;
	  const treeSize = 2 * firstIndex - 1; // number of tree nodes
	  firstIndex -= 1; // index of leftmost leaf

	  const tree = new Array(treeSize);
	  for (let i = 0; i < treeSize; i++) tree[i] = 0;

	  // count the crossings
	  let count = 0;
	  southSeq.forEach(k => {
	    let index = k + firstIndex;
	    tree[index]++;
	    while (index > 0) {
	      if (index % 2) count += tree[index + 1];
	      index = Math.floor((index - 1) / 2);
	      tree[index]++;
	    }
	  });

	  return count
	}

	function swapNodes (G, order) {
	  let improved = true;
	  while (improved) {
	    improved = false;
	    for (let i = 0; i < order.length; ++i) {
	      for (let j = 0; j < order[i].length - 1; ++j) {
	        const count0 = allCrossings$1(G, order, i);
	        transpose(order[i], j, j + 1);
	        const count1 = allCrossings$1(G, order, i);

	        if (count1 < count0) {
	          improved = true;
	        } else {
	          transpose(order[i], j, j + 1); // put back
	        }
	      }
	    }
	  }
	}

	function allCrossings$1 (G, order, i) {
	  let count = 0;
	  if (i > 0) {
	    count += countCrossings(G, order[i - 1], order[i]);
	  }
	  if (i + 1 < order.length) {
	    count += countCrossings(G, order[i], order[i + 1]);
	  }
	  return count
	}

	function transpose (list, i, j) {
	  const tmp = list[i];
	  list[i] = list[j];
	  list[j] = tmp;
	}

	function medianValue (positions) {
	  const m = Math.floor(positions.length / 2);
	  if (positions.length === 0) {
	    return -1
	  } else if (positions.length % 2 === 1) {
	    return positions[m]
	  } else if (positions.length === 2) {
	    return (positions[0] + positions[1]) / 2
	  } else {
	    const left = positions[m - 1] - positions[0];
	    const right = positions[positions.length - 1] - positions[m];
	    return (positions[m - 1] * right + positions[m] * left) / (left + right)
	  }
	}

	function neighbourPositions (G, order, i, j, u, includeLoops = false) {
	  // current rank i
	  // neighbour rank j
	  const thisRank = order[i];
	  const otherRank = order[j];

	  const positions = [];

	  // neighbouring positions on other rank
	  otherRank.forEach((n, i) => {
	    if (G.nodeEdges(n, u).length > 0) {
	      positions.push(i);
	    }
	  });

	  if (positions.length === 0 && includeLoops) {
	    // if no neighbours in other rank, look for loops to this rank
	    // XXX only on one side?
	    thisRank.forEach((n, i) => {
	      if (G.nodeEdges(n, u).length > 0) {
	        positions.push(i + 0.5);
	      }
	    });
	  }

	  positions.sort((a, b) => a - b);

	  return positions
	}

	/**
	 * Sort arr according to order. -1 in order means stay in same position.
	 */
	function sortByPositions (arr, order) {
	  const origOrder = d3Collection.map(arr.map((d, i) => [d, i]), d => d[0]);

	  // console.log('sorting', arr, order, origOrder)
	  for (let i = 1; i < arr.length; ++i) {
	    // console.group('start', i, arr[i])
	    for (let k = i; k > 0; --k) {
	      let j = k - 1;
	      let a = order.get(arr[j]);
	      let b = order.get(arr[k]);

	      // count back over any fixed positions (-1)
	      while ((a = order.get(arr[j])) === -1 && j > 0) j--;

	      // console.log(j, k, arr[j], arr[k], a, b)
	      if (b === -1 || a === -1) {
	        // console.log('found -1', a, b, 'skipping', j, k)
	        break
	      }

	      if (a === b) {
	        a = origOrder.get(arr[j]);
	        b = origOrder.get(arr[k]);
	        // console.log('a == b, switching to orig order', a, b)
	      }

	      if (b >= a) {
	        // console.log('k > k -1, stopping')
	        break
	      }
	      // console.log('swapping', arr[k], arr[j])
	      // swap arr[k], arr[j]
	      [arr[k], arr[j]] = [arr[j], arr[k]];
	      // console.log(arr)
	    }
	    // console.groupEnd()
	  }
	  // console.log('-->', arr)
	}

	function sortNodes$1 (G, order, sweepDirection = 1, includeLoops = false) {
	  if (sweepDirection > 0) {
	    for (let r = 1; r < order.length; ++r) {
	      const medians = d3Collection.map();
	      order[r].forEach(u => {
	        const neighbour = medianValue(neighbourPositions(G, order, r, r - 1, u, includeLoops));
	        medians.set(u, neighbour);
	      });
	      sortByPositions(order[r], medians);
	    }
	  } else {
	    for (let r = order.length - 2; r >= 0; --r) {
	      const medians = d3Collection.map();
	      order[r].forEach(u => {
	        const neighbour = medianValue(neighbourPositions(G, order, r, r + 1, u, includeLoops));
	        medians.set(u, neighbour);
	      });
	      sortByPositions(order[r], medians);
	    }
	  }
	}

	/** @module node-ordering */


	/**
	 * Sorts the nodes in G, setting the `depth` attribute on each.
	 *
	 * @param {Graph} G - The graph. Nodes must have a `rank` attribute.
	 *
	 */
	function sortNodes (G, maxIterations = 25) {
	  const ranks = getRanks(G);
	  const order = initialOrdering(G, ranks);
	  let best = order;
	  let i = 0;

	  while (i++ < maxIterations) {
	    sortNodes$1(G, order, (i % 2 === 0));
	    swapNodes(G, order);
	    if (allCrossings(G, order) < allCrossings(G, best)) {
	      // console.log('improved', allCrossings(G, order), order);
	      best = copy(order);
	    }
	  }

	  // Assign depth to nodes
	  // const depths = map()
	  best.forEach(nodes => {
	    nodes.forEach((u, i) => {
	      // depths.set(u, i)
	      G.node(u).depth = i;
	    });
	  });
	}

	function getRanks (G) {
	  const ranks = [];
	  G.nodes().forEach(u => {
	    const r = G.node(u).rank || 0;
	    while (r >= ranks.length) ranks.push([]);
	    ranks[r].push(u);
	  });
	  return ranks
	}

	function allCrossings (G, order) {
	  let count = 0;
	  for (let i = 0; i < order.length - 1; ++i) {
	    count += countCrossings(G, order[i], order[i + 1]);
	  }
	  return count
	}

	function copy (order) {
	  const result = [];
	  order.forEach(rank => {
	    result.push(rank.map(d => d));
	  });
	  return result
	}

	function addDummyNodes (G) {
	  // Add edges & dummy nodes
	  if (typeof G.graph() !== 'object') G.setGraph({});
	  G.graph().dummyChains = [];
	  G.edges().forEach(e => normaliseEdge(G, e));
	}

	// based on https://github.com/cpettitt/dagre/blob/master/lib/normalize.js
	function normaliseEdge (G, e) {
	  const edge = G.edge(e);
	  const dummies = dummyNodes(G.node(e.v), G.node(e.w));
	  if (dummies.length === 0) return

	  G.removeEdge(e);

	  let v = e.v;
	  dummies.forEach((dummy, i) => {
	    const id = `__${e.v}_${e.w}_${i}`;
	    if (!G.hasNode(id)) {
	      dummy.dummy = 'edge';
	      G.setNode(id, dummy);
	      if (i === 0) {
	        G.graph().dummyChains.push(id);
	      }
	    }
	    addDummyEdge(v, (v = id));
	  });
	  addDummyEdge(v, e.w);

	  function addDummyEdge (v, w) {
	    const label = { points: [], value: edge.value, origEdge: e, origLabel: edge };
	    G.setEdge(v, w, label, e.name);
	  }
	}

	function removeDummyNodes (G) {
	  const chains = G.graph().dummyChains || [];
	  chains.forEach(v => {
	    let node = G.node(v);
	    let dummyEdges = G.inEdges(v).map(e => G.edge(e));

	    // Set dy and starting point of edge and add back to graph
	    dummyEdges.forEach(dummyEdge => {
	      dummyEdge.origLabel.dy = dummyEdge.dy;
	      dummyEdge.origLabel.x0 = dummyEdge.x0;
	      dummyEdge.origLabel.y0 = dummyEdge.y0;
	      dummyEdge.origLabel.r0 = dummyEdge.r0;
	      dummyEdge.origLabel.d0 = dummyEdge.d0;
	      G.setEdge(dummyEdge.origEdge, dummyEdge.origLabel);
	    });
	    let r1s = dummyEdges.map(dummyEdge => dummyEdge.r1);

	    // Walk through chain
	    let w;
	    while (node.dummy) {
	      dummyEdges = G.outEdges(v).map(e => G.edge(e));
	      dummyEdges.forEach((dummyEdge, i) => {
	        dummyEdge.origLabel.points.push({
	          x: (node.x0 + node.x1) / 2,
	          y: dummyEdge.y0,
	          d: dummyEdge.d0,
	          ro: dummyEdge.r0,
	          ri: r1s[i] // from last edge
	        });
	      });
	      r1s = dummyEdges.map(dummyEdge => dummyEdge.r1);

	      // move on
	      w = G.successors(v)[0];
	      G.removeNode(v);
	      node = G.node(v = w);
	    }

	    // Set ending point of edge
	    dummyEdges.forEach(dummyEdge => {
	      dummyEdge.origLabel.x1 = dummyEdge.x1;
	      dummyEdge.origLabel.y1 = dummyEdge.y1;
	      dummyEdge.origLabel.r1 = dummyEdge.r1;
	      dummyEdge.origLabel.d1 = dummyEdge.d1;
	    });
	  });
	}

	function dummyNodes (source, target) {
	  const dummyNodes = [];
	  let r = source.rank;

	  if (r + 1 <= target.rank) {
	    // add more to get forwards
	    if (source.backwards) {
	      dummyNodes.push({ rank: r, backwards: false }); // turn around
	    }
	    while (++r < target.rank) {
	      dummyNodes.push({ rank: r, backwards: false });
	    }
	    if (target.backwards) {
	      dummyNodes.push({ rank: r, backwards: false }); // turn around
	    }
	  } else if (r > target.rank) {
	    // add more to get backwards
	    if (!source.backwards) {
	      dummyNodes.push({ rank: r, backwards: true }); // turn around
	    }
	    while (r-- > target.rank + 1) {
	      dummyNodes.push({ rank: r, backwards: true });
	    }
	    if (!target.backwards) {
	      dummyNodes.push({ rank: r, backwards: true }); // turn around
	    }
	  }

	  return dummyNodes
	}

	function nestGraph (nodes) {
	  const maxRank = d3Array.max(nodes, d => d.rank || 0) || 0;
	  const maxBand = d3Array.max(nodes, d => d.band || 0) || 0;

	  // const nodes = graph.nodes().concat(graph.dummyNodes())

	  const nested = d3Collection.nest()
	    .key(d => d.rank || 0)
	    .key(d => d.band || 0)
	    .sortValues((a, b) => a.depth - b.depth)
	    .map(nodes);

	  const result = new Array(maxRank + 1);
	  let rank;
	  for (let i = 0; i <= maxRank; ++i) {
	    result[i] = new Array(maxBand + 1);
	    rank = nested.get(i);
	    if (rank) {
	      for (let j = 0; j <= maxBand; ++j) {
	        result[i][j] = rank.get(j) || [];
	      }
	    } else {
	      for (let j = 0; j <= maxBand; ++j) {
	        result[i][j] = [];
	      }
	    }
	  }

	  result.bandValues = bandValues(result);

	  return result
	}

	function bandValues (nested) {
	  if (nested.length === 0 || nested[0].length === 0) return []

	  const Nb = nested[0].length;
	  const values = new Array(Nb);
	  for (let i = 0; i < Nb; i++) values[i] = 0;

	  nested.forEach(rank => {
	    rank.forEach((band, j) => {
	      const total = d3Array.sum(band, d => d.value);
	      values[j] = Math.max(values[j], total);
	    });
	  });

	  return values
	}

	// export function minEdgeDx (w, y0, y1) {
	//   console.log('mindx', w, y0, y1)
	//   const dy = y1 - y0
	//   const ay = Math.abs(dy) - w  // final sign doesn't matter
	//   const dx2 = w * w - ay * ay
	//   const dx = dx2 >= 0 ? Math.sqrt(dx2) : w
	//   return dx
	// }

	function positionHorizontally (G, width, nodeWidth) {
	  // const minWidths = new Array(maxRank).fill(0)
	  // G.edges().forEach(e => {
	  //   const r0 = G.node(e.v).rank || 0
	  //   minWidths[r0] = Math.max(minWidths[r0], minEdgeDx(G.edge(e).dy, G.node(e.v).y, G.node(e.w).y))
	  // })
	  // for (let i = 0; i < nested.length - 1; ++i) {
	  //   minWidths[i] = 0
	  //   nested[i].forEach(band => {
	  //     band.forEach(d => {
	  //       // edges for dummy nodes, outgoing for real nodes
	  //       (d.outgoing || d.edges).forEach(e => {
	  //         minWidths[i] = Math.max(minWidths[i], minEdgeDx(e))
	  //       })
	  //     })
	  //   })
	  // }
	  // const totalMinWidth = sum(minWidths)
	  // let dx
	  // if (totalMinWidth > width) {
	  //   // allocate fairly
	  //   dx = minWidths.map(w => width * w / totalMinWidth)
	  // } else {
	  //   const spare = (width - totalMinWidth) / (nested.length - 1)
	  //   dx = minWidths.map(w => w + spare)
	  // }

	  const maxRank = d3Array.max(G.nodes(), u => G.node(u).rank || 0) || 0;
	  const dx = (width - nodeWidth) / maxRank;

	  G.nodes().forEach(u => {
	    const node = G.node(u);
	    node.x0 = dx * (node.rank || 0);
	    node.x1 = node.x0 + nodeWidth;
	  });
	}

	function defaultSeparation (a, b) {
	  return 1
	}

	function positionNodesVertically$1 () {
	  let separation = defaultSeparation;

	  function layout (nested, totalHeight, whitespace) {
	    nested.forEach(layer => {
	      let y = 0;
	      layer.forEach((band, j) => {
	        // Height of this band, based on fraction of value
	        const bandHeight = nested.bandValues[j] / d3Array.sum(nested.bandValues) * totalHeight;

	        const margin = whitespace * bandHeight / 5;
	        const height = bandHeight - 2 * margin;
	        const total = d3Array.sum(band, d => d.dy);
	        const gaps = band.map((d, i) => {
	          if (!d.value) return 0
	          return band[i + 1] ? separation(band[i], band[i + 1], layout) : 0
	        });
	        const space = Math.max(0, height - total);
	        const kg = d3Array.sum(gaps) ? space / d3Array.sum(gaps) : 0;

	        let yy = y + margin;
	        if (band.length === 1) {
	          // centre vertically
	          yy += (height - band[0].dy) / 2;
	        }

	        let prevGap = Number.MAX_VALUE ; // edge of graph
	        band.forEach((node, i) => {
	          node.y = yy;
	          node.spaceAbove = prevGap;
	          node.spaceBelow = gaps[i] * kg;
	          yy += node.dy + node.spaceBelow;
	          prevGap = node.spaceBelow;

	          // XXX is this a good idea?
	          if (node.data && node.data.forceY !== undefined) {
	            node.y = margin + node.data.forceY * (height - node.dy);
	          }
	        });
	        if (band.length > 0) {
	          band[band.length - 1].spaceBelow = Number.MAX_VALUE ; // edge of graph
	        }

	        y += bandHeight;
	      });
	    });
	  }

	  layout.separation = function (x) {
	    if (!arguments.length) return separation
	    separation = required$3(x);
	    return layout
	  };

	  return layout
	}

	function required$3 (f) {
	  if (typeof f !== 'function') throw new Error()
	  return f
	}

	function prepareNodePorts (G, sortPorts) {
	  G.nodes().forEach(u => {
	    const node = G.node(u);
	    const ports = d3Collection.map();
	    function getOrSet (id, side) {
	      if (ports.has(id)) return ports.get(id)
	      const port = { id, node: node.data, side, incoming: [], outgoing: [] };
	      ports.set(id, port);
	      return port
	    }

	    G.inEdges(u).forEach(e => {
	      const edge = G.edge(e);
	      const port = getOrSet(edge.targetPortId || 'in', node.direction !== 'l' ? 'west' : 'east');
	      port.incoming.push(e);
	      edge.targetPort = port;
	    });
	    G.outEdges(u).forEach(e => {
	      const edge = G.edge(e);
	      const port = getOrSet(edge.sourcePortId || 'out', node.direction !== 'l' ? 'east' : 'west');
	      port.outgoing.push(e);
	      edge.sourcePort = port;
	    });

	    node.ports = ports.values();
	    node.ports.sort(sortPorts);

	    // Initialise from/to elsewhere lists
	    // XXX need to take more care with node directions
	    node.fromElsewhere = node.fromElsewhere || [];
	    node.toElsewhere = node.toElsewhere || [];
	    let fromElsewhereDy = 0;
	    node.fromElsewhere.forEach(link => {
	      link.x1 = node.x0;
	      fromElsewhereDy += link.dy;
	    });

	    // Set positions of ports, roughly -- so the other endpoints of links are
	    // known approximately when being sorted.
	    const y = { west: fromElsewhereDy, east: 0 };
	    const i = { west: 0, east: 0 };
	    node.ports.forEach(port => {
	      port.y = y[port.side];
	      port.index = i[port.side];
	      port.dy = Math.max(d3Array.sum(port.incoming, e => G.edge(e).dy),
	        d3Array.sum(port.outgoing, e => G.edge(e).dy));
	      const x = (port.side === 'west' ? node.x0 : node.x1);

	      port.outgoing.forEach(e => {
	        const link = G.edge(e);
	        link.x0 = x;
	        link.y0 = node.y + port.y + link.dy / 2;
	      });
	      port.incoming.forEach(e => {
	        const link = G.edge(e);
	        link.x1 = x;
	        link.y1 = node.y + port.y + link.dy / 2;
	      });
	      y[port.side] += port.dy;
	      i[port.side] += 1;
	    });

	    node.toElsewhere.forEach(link => {
	      link.x0 = node.x1;
	    });
	  });
	}

	function linkDirection (G, e, head = true) {
	  if (e.v === e.w) {
	    // pretend self-links go downwards
	    return Math.PI / 2 * (head ? +1 : -1)
	  } else {
	    // const source = G.node(e.v)
	    // const target = G.node(e.w)
	    // return Math.atan2(target.y - source.y,
	    //                   target.x0 - source.x1)
	    const link = G.edge(e);
	    return Math.atan2(link.y1 - link.y0,
	      link.x1 - link.x0)
	  }
	}

	/** @module edge-ordering */


	/**
	 * Order the edges at all nodes.
	 */
	function orderEdges (G, opts) {
	  G.nodes().forEach(u => orderEdgesOne(G, u));
	}

	/**
	 * Order the edges at the given node.
	 * The ports have already been setup and sorted.
	 */
	function orderEdgesOne (G, v) {
	  const node = G.node(v);
	  node.ports.forEach(port => {
	    port.incoming.sort(compareDirection(G, node, false));
	    port.outgoing.sort(compareDirection(G, node, true));
	  });
	}

	/**
	 * Sort links based on their endpoints & type
	 */
	function compareDirection (G, node, head = true) {
	  return function (a, b) {
	    const da = linkDirection(G, a, head);
	    const db = linkDirection(G, b, head);
	    const c = head ? 1 : -1;

	    // links between same node, sort on type
	    if (a.v === b.v && a.w === b.w && Math.abs(da - db) < 1e-3) {
	      if (typeof a.name === 'number' && typeof b.name === 'number') {
	        return a.name - b.name
	      } else if (typeof a.name === 'string' && typeof b.name === 'string') {
	        return a.name.localeCompare(b.name)
	      } else {
	        return 0
	      }
	    }

	    // loops to same slice based on y-position
	    if (Math.abs(da - db) < 1e-3) {
	      if (a.w === b.w) {
	        return G.node(b.v).y - G.node(a.v).y
	      } else if (a.v === b.v) {
	        return G.node(b.w).y - G.node(a.w).y
	      } else {
	        return 0
	      }
	    }

	    // otherwise sort by direction
	    return c * (da - db)
	  }
	}

	function findFirst (links, p) {
	  let jmid = null;
	  for (let j = 0; j < links.length; ++j) {
	    if (p(links[j])) { jmid = j; break }
	  }
	  return jmid
	}

	/**
	 * Adjust radii of curvature to avoid overlaps, as much as possible.
	 * @param links - the list of links, ordered from outside to inside of bend
	 * @param rr - "r0" or "r1", the side to work on
	 */
	function sweepCurvatureInwards (links, rr) {
	  if (links.length === 0) return

	  // sweep from inside of curvature towards outside
	  let Rmin = 0; let h;
	  for (let i = links.length - 1; i >= 0; --i) {
	    h = links[i].dy / 2;
	    if (links[i][rr] - h < Rmin) { // inner radius
	      links[i][rr] = Math.min(links[i].Rmax, Rmin + h);
	    }
	    Rmin = links[i][rr] + h;
	  }

	  // sweep from outside of curvature towards centre
	  let Rmax = links[0].Rmax + links[0].dy / 2;
	  for (let i = 0; i < links.length; ++i) {
	    h = links[i].dy / 2;
	    if (links[i][rr] + h > Rmax) { // outer radius
	      links[i][rr] = Math.max(h, Rmax - h);
	    }
	    Rmax = links[i][rr] - h;
	  }
	}

	/**
	 * Edge positioning.
	 *
	 * @module link-positioning
	 */


	/*
	 * Requires incoming and outgoing attributes on nodes
	 */
	function layoutLinks (G) {
	  setEdgeEndpoints(G);
	  setEdgeCurvatures(G);
	  return G
	}

	function setEdgeEndpoints (G) {
	  G.nodes().forEach(u => {
	    const node = G.node(u);

	    let sy = node.y;
	    let ty = node.y;

	    node.fromElsewhere.forEach(link => {
	      link.y1 = ty + link.dy / 2;
	      link.d1 = node.backwards ? 'l' : 'r';
	      ty += link.dy;
	    });

	    node.ports.forEach(port => {
	      sy = node.y + port.y;
	      ty = node.y + port.y;

	      port.outgoing.forEach(e => {
	        const link = G.edge(e);
	        // link.x0 = node.x1
	        link.y0 = sy + link.dy / 2;
	        link.d0 = node.backwards ? 'l' : 'r';
	        sy += link.dy;
	      });

	      port.incoming.forEach(e => {
	        const link = G.edge(e);
	        // link.x1 = node.x0
	        link.y1 = ty + link.dy / 2;
	        link.d1 = node.backwards ? 'l' : 'r';
	        ty += link.dy;
	      });
	    });

	    node.toElsewhere.forEach(link => {
	      link.y0 = sy + link.dy / 2;
	      link.d0 = node.backwards ? 'l' : 'r';
	      sy += link.dy;
	    });
	  });
	}

	function setEdgeCurvatures (G) {
	  G.nodes().forEach(u => {
	    const node = G.node(u);
	    setEdgeEndCurvatures(node.toElsewhere, 'r0');
	    setEdgeEndCurvatures(node.fromElsewhere, 'r1');
	    node.ports.forEach(port => {
	      setEdgeEndCurvatures(port.outgoing.map(e => G.edge(e)), 'r0');
	      setEdgeEndCurvatures(port.incoming.map(e => G.edge(e)), 'r1');
	    });
	  });
	}

	function maximumRadiusOfCurvature (link) {
	  const Dx = link.x1 - link.x0;
	  const Dy = link.y1 - link.y0;
	  if (link.d0 !== link.d1) {
	    return Math.abs(Dy) / 2.1
	  } else {
	    return (Dy !== 0) ? (Dx * Dx + Dy * Dy) / Math.abs(4 * Dy) : Infinity
	  }
	}

	function setEdgeEndCurvatures (links, rr) {
	  // initialise segments, find reversal of curvature
	  links.forEach(link => {
	    // const link = (i < 0) ? link.segments[link.segments.length + i] : link.segments[i]
	    link.Rmax = maximumRadiusOfCurvature(link);
	    link[rr] = Math.max(link.dy / 2, (link.d0 === link.d1 ? link.Rmax * 0.6 : (5 + link.dy / 2)));
	  });

	  let jmid = (rr === 'r0'
	    ? findFirst(links, f => f.y1 > f.y0)
	    : findFirst(links, f => f.y0 > f.y1));
	  if (jmid === null) jmid = links.length;

	  // Set maximum radius down from middle
	  sweepCurvatureInwards(links.slice(jmid), rr);

	  // Set maximum radius up from middle
	  if (jmid > 0) {
	    const links2 = [];
	    for (let j = jmid - 1; j >= 0; j--) links2.push(links[j]);
	    sweepCurvatureInwards(links2, rr);
	  }
	}

	const { Graph } = pkg;

	function buildGraph (graph, nodeId, nodeBackwards, sourceId, targetId, linkType, linkValue) {
	  const G = new Graph({ directed: true, multigraph: true });
	  graph.nodes.forEach(function (node, i) {
	    const id = nodeId(node, i);
	    if (G.hasNode(id)) throw new Error('duplicate: ' + id)
	    G.setNode(id, {
	      data: node,
	      index: i,
	      backwards: nodeBackwards(node, i),
	      fromElsewhere: node.fromElsewhere || [],
	      toElsewhere: node.toElsewhere || [],
	      // XXX don't need these now have nodePositions?
	      x0: node.x0,
	      x1: node.x1,
	      y: node.y0
	    });
	  });

	  graph.links.forEach(function (link, i) {
	    const v = idAndPort(sourceId(link, i));
	    const w = idAndPort(targetId(link, i));
	    const label = {
	      data: link,
	      sourcePortId: v.port,
	      targetPortId: w.port,
	      index: i,
	      points: [],
	      value: linkValue(link, i),
	      type: linkType(link, i)
	    };
	    if (!G.hasNode(v.id)) throw new Error('missing: ' + v.id)
	    if (!G.hasNode(w.id)) throw new Error('missing: ' + w.id)
	    G.setEdge(v.id, w.id, label, linkType(link, i));
	  });

	  G.setGraph({});

	  return G
	}

	function idAndPort (x) {
	  if (typeof x === 'object') return x
	  return { id: x, port: undefined }
	}

	/**
	 */


	function defaultNodes (graph) {
	  return graph.nodes
	}

	function defaultLinks (graph) {
	  return graph.links
	}

	function defaultNodeId (d) {
	  return d.id
	}

	function defaultNodeBackwards (d) {
	  return d.direction && d.direction.toLowerCase() === 'l'
	}

	function defaultSourceId (d) {
	  // return typeof d.source === 'object' ? d.source.id : d.source
	  return {
	    id: typeof d.source === 'object' ? d.source.id : d.source,
	    port: typeof d.sourcePort === 'object' ? d.sourcePort.id : d.sourcePort
	  }
	}

	function defaultTargetId (d) {
	  // return typeof d.target === 'object' ? d.target.id : d.target
	  return {
	    id: typeof d.target === 'object' ? d.target.id : d.target,
	    port: typeof d.targetPort === 'object' ? d.targetPort.id : d.targetPort
	  }
	}

	function defaultLinkType (d) {
	  return d.type
	}

	function defaultSortPorts (a, b) {
	  // XXX weighted sum
	  return a.id.localeCompare(b.id)
	}

	// function defaultNodeSubdivisions

	function sankeyLayout () {
	  let nodes = defaultNodes;
	  let links = defaultLinks;
	  let nodeId = defaultNodeId;
	  let nodeBackwards = defaultNodeBackwards;
	  let sourceId = defaultSourceId;
	  let targetId = defaultTargetId;
	  let linkType = defaultLinkType;
	  let ordering = null;
	  let rankSets = [];
	  const maxIterations = 25; // XXX setter/getter
	  let nodePosition = null;
	  let sortPorts = defaultSortPorts;

	  // extent
	  let x0 = 0;
	  let y0 = 0;
	  let x1 = 1;
	  let y1 = 1;

	  // node width
	  let dx = 1;

	  let scale = null;
	  let linkValue = function (e) { return e.value };
	  let whitespace = 0.5;
	  let verticalLayout = positionNodesVertically$1();

	  function sankey () {
	    const graph = { nodes: nodes.apply(null, arguments), links: links.apply(null, arguments) };
	    const G = buildGraph(graph, nodeId, nodeBackwards, sourceId, targetId, linkType, linkValue);

	    setNodeValues(G, linkValue);

	    if (nodePosition) {
	      // hard-coded node positions

	      G.nodes().forEach(u => {
	        const node = G.node(u);
	        const pos = nodePosition(node.data);
	        node.x0 = pos[0];
	        node.x1 = pos[0] + dx;
	        node.y = pos[1];
	      });
	      setWidths(G, scale);
	    } else {
	      // calculate node positions

	      if (ordering !== null) {
	        applyOrdering(G, ordering);
	      } else {
	        assignRanks(G, rankSets);
	        sortNodes(G, maxIterations);
	      }

	      addDummyNodes(G);
	      setNodeValues(G, linkValue);
	      if (ordering === null) {
	        // XXX sort nodes?
	        sortNodes(G, maxIterations);
	      }

	      const nested = nestGraph(G.nodes().map(u => G.node(u)));
	      maybeScaleToFit(G, nested);
	      setWidths(G, scale);

	      // position nodes
	      verticalLayout(nested, y1 - y0, whitespace);
	      positionHorizontally(G, x1 - x0, dx);

	      // adjust origin
	      G.nodes().forEach(u => {
	        const node = G.node(u);
	        node.x0 += x0;
	        node.x1 += x0;
	        node.y += y0;
	      });
	    }

	    // sort & position links
	    prepareNodePorts(G, sortPorts);
	    orderEdges(G);
	    layoutLinks(G);

	    removeDummyNodes(G);
	    addLinkEndpoints(G);

	    copyResultsToGraph(G);

	    return graph
	  }

	  sankey.update = function (graph, doOrderLinks) {
	    const G = buildGraph(graph, nodeId, nodeBackwards, sourceId, targetId, linkType, linkValue);
	    setNodeValues(G, linkValue);
	    const nested = nestGraph(G.nodes().map(u => G.node(u)));
	    maybeScaleToFit(G, nested);
	    setWidths(G, scale);

	    prepareNodePorts(G, sortPorts);
	    orderEdges(G);
	    layoutLinks(G);

	    // removeDummyNodes(G)
	    addLinkEndpoints(G);

	    copyResultsToGraph(G);

	    return graph
	  };
	  //   if (scale === null) sankey.scaleToFit(graph)
	  //   // set node and edge sizes
	  //   setNodeValues(graph, linkValue, scale)
	  //   if (doOrderLinks) {
	  //     orderLinks(graph)
	  //   }
	  //   layoutLinks(graph)
	  //   return graph
	  // }

	  sankey.nodes = function (x) {
	    if (arguments.length) {
	      nodes = required$2(x);
	      return sankey
	    }
	    return nodes
	  };

	  sankey.links = function (x) {
	    if (arguments.length) {
	      links = required$2(x);
	      return sankey
	    }
	    return links
	  };

	  sankey.nodeId = function (x) {
	    if (arguments.length) {
	      nodeId = required$2(x);
	      return sankey
	    }
	    return nodeId
	  };

	  sankey.nodeBackwards = function (x) {
	    if (arguments.length) {
	      nodeBackwards = required$2(x);
	      return sankey
	    }
	    return nodeBackwards
	  };

	  sankey.sourceId = function (x) {
	    if (arguments.length) {
	      sourceId = required$2(x);
	      return sankey
	    }
	    return sourceId
	  };

	  sankey.targetId = function (x) {
	    if (arguments.length) {
	      targetId = required$2(x);
	      return sankey
	    }
	    return targetId
	  };

	  sankey.linkType = function (x) {
	    if (arguments.length) {
	      linkType = required$2(x);
	      return sankey
	    }
	    return linkType
	  };

	  sankey.sortPorts = function (x) {
	    if (arguments.length) {
	      sortPorts = required$2(x);
	      return sankey
	    }
	    return sortPorts
	  };

	  // sankey.scaleToFit = function (graph) {
	  function maybeScaleToFit (G, nested) {
	    if (scale !== null) return
	    const maxValue = d3Array.sum(nested.bandValues);
	    if (maxValue <= 0) {
	      scale = 1;
	    } else {
	      scale = (y1 - y0) / maxValue;
	      if (whitespace !== 1) scale *= (1 - whitespace);
	    }
	  }

	  sankey.ordering = function (x) {
	    if (!arguments.length) return ordering
	    ordering = x;
	    return sankey
	  };

	  sankey.rankSets = function (x) {
	    if (!arguments.length) return rankSets
	    rankSets = x;
	    return sankey
	  };

	  sankey.nodeWidth = function (x) {
	    if (!arguments.length) return dx
	    dx = x;
	    return sankey
	  };

	  sankey.nodePosition = function (x) {
	    if (!arguments.length) return nodePosition
	    nodePosition = x;
	    return sankey
	  };

	  sankey.size = function (x) {
	    if (!arguments.length) return [x1 - x0, y1 - y0]
	    x0 = y0 = 0;
	    x1 = +x[0];
	    y1 = +x[1];
	    return sankey
	  };

	  sankey.extent = function (x) {
	    if (!arguments.length) return [[x0, y0], [x1, y1]]
	    x0 = +x[0][0];
	    y0 = +x[0][1];
	    x1 = +x[1][0];
	    y1 = +x[1][1];
	    return sankey
	  };

	  sankey.whitespace = function (x) {
	    if (!arguments.length) return whitespace
	    whitespace = x;
	    return sankey
	  };

	  sankey.scale = function (x) {
	    if (!arguments.length) return scale
	    scale = x;
	    return sankey
	  };

	  sankey.linkValue = function (x) {
	    if (!arguments.length) return linkValue
	    linkValue = x;
	    return sankey
	  };

	  sankey.verticalLayout = function (x) {
	    if (!arguments.length) return verticalLayout
	    verticalLayout = required$2(x);
	    return sankey
	  };

	  function applyOrdering (G, ordering) {
	    ordering.forEach((x, i) => {
	      x.forEach((u, j) => {
	        if (u.forEach) {
	          u.forEach((v, k) => {
	            const d = G.node(v);
	            if (d) {
	              d.rank = i;
	              d.band = j;
	              d.depth = k;
	            }
	          });
	        } else {
	          const d = G.node(u);
	          if (d) {
	            d.rank = i;
	            // d.band = 0
	            d.depth = j;
	          }
	        }
	      });
	    });
	  }

	  return sankey
	}

	function setNodeValues (G, linkValue) {
	  G.nodes().forEach(u => {
	    const d = G.node(u);
	    let incoming = d3Array.sum(G.inEdges(u), e => G.edge(e).value);
	    let outgoing = d3Array.sum(G.outEdges(u), e => G.edge(e).value);
	    incoming += d3Array.sum(d.fromElsewhere || [], link => linkValue(link));
	    outgoing += d3Array.sum(d.toElsewhere || [], link => linkValue(link));
	    d.value = Math.max(incoming, outgoing);
	  });
	}

	function setWidths (G, scale) {
	  G.edges().forEach(e => {
	    const edge = G.edge(e);
	    edge.dy = edge.value * scale;
	  });
	  G.nodes().forEach(u => {
	    const node = G.node(u);
	    node.dy = node.value * scale;

	    // Initialise from/to elsewhere lists
	    node.fromElsewhere = (node.fromElsewhere || []);
	    node.toElsewhere = (node.toElsewhere || []);
	    node.fromElsewhere.forEach(link => {
	      link.dy = link.value * scale;
	      link.source = { id: '__from_elsewhere_' + u };
	      link.target = node.data;
	    });
	    node.toElsewhere.forEach(link => {
	      link.dy = link.value * scale;
	      link.source = node.data;
	      link.target = { id: '__to_elsewhere_' + u };
	    });
	  });
	}

	function required$2 (f) {
	  if (typeof f !== 'function') throw new Error()
	  return f
	}

	function addLinkEndpoints (G) {
	  G.edges().forEach(e => {
	    const edge = G.edge(e);
	    edge.points.unshift({ x: edge.x0, y: edge.y0, ro: edge.r0, d: edge.d0 });
	    edge.points.push({ x: edge.x1, y: edge.y1, ri: edge.r1, d: edge.d1 });
	  });

	  G.nodes().forEach(u => {
	    const node = G.node(u);
	    node.fromElsewhere.forEach(link => {
	      link.points = [{ x: link.x1, y: link.y1, ri: link.r1, d: link.d1, style: 'down-right' }];
	    });
	    node.toElsewhere.forEach(link => {
	      link.points = [{ x: link.x0, y: link.y0, ri: link.r0, d: link.d0, style: 'right-down' }];
	    });
	  });
	}

	function copyResultsToGraph (G, graph) {
	  G.nodes().forEach(u => {
	    const node = G.node(u);

	    // Build lists of edge data objects
	    node.data.incoming = [];
	    node.data.outgoing = [];
	    node.data.ports = node.ports;
	    node.data.ports.forEach(port => {
	      port.incoming = [];
	      port.outgoing = [];
	    });

	    node.data.dy = node.dy;
	    node.data.x0 = node.x0;
	    node.data.x1 = node.x1;
	    node.data.y0 = node.y;
	    node.data.y1 = node.y + node.dy;
	    node.data.rank = node.rank;
	    node.data.band = node.band;
	    node.data.depth = node.depth;
	    node.data.value = node.value;
	    node.data.spaceAbove = node.spaceAbove;
	    node.data.spaceBelow = node.spaceBelow;
	  });

	  G.edges().forEach(e => {
	    const edge = G.edge(e);
	    edge.data.source = G.node(e.v).data;
	    edge.data.target = G.node(e.w).data;
	    edge.data.sourcePort = edge.sourcePort;
	    edge.data.targetPort = edge.targetPort;
	    // console.log(edge)
	    edge.data.source.outgoing.push(edge.data);
	    edge.data.target.incoming.push(edge.data);
	    if (edge.data.sourcePort) edge.data.sourcePort.outgoing.push(edge.data);
	    if (edge.data.targetPort) edge.data.targetPort.incoming.push(edge.data);
	    edge.data.value = edge.value;
	    edge.data.type = edge.type;
	    edge.data.dy = edge.dy;
	    edge.data.points = edge.points || [];
	    // edge.data.id = `${e.v}-${e.w}-${e.name}`
	  });
	}

	function positionNodesVertically () {
	  let iterations = 25;
	  let nodePadding = 8;

	  function layout (nested, height) {
	    initializeNodeDepth();
	    resolveCollisions();
	    for (let alpha = 1, i = iterations; i > 0; --i) {
	      relaxRightToLeft(alpha *= 0.99);
	      resolveCollisions();
	      relaxLeftToRight(alpha);
	      resolveCollisions();
	    }

	    function initializeNodeDepth () {
	      nested.forEach(layer => {
	        let i = 0;
	        layer.forEach(band => {
	          // ignore bands for this layout
	          band.forEach(node => {
	            node.y = i++;
	          });
	        });
	      });
	    }

	    function relaxLeftToRight (alpha) {
	      nested.forEach(layer => {
	        layer.forEach(band => {
	          band.forEach(node => {
	            const edges = node.incoming || node.edges;
	            if (edges.length) {
	              const y = d3Array.sum(edges, weightedSource) / d3Array.sum(edges, value);
	              node.y += (y - center(node)) * alpha;
	            }
	          });
	        });
	      });

	      function weightedSource (link) {
	        return center(link.source) * link.value
	      }
	    }

	    function relaxRightToLeft (alpha) {
	      nested.slice().reverse().forEach(layer => {
	        layer.forEach(band => {
	          band.forEach(node => {
	            const edges = node.outgoing || node.edges;
	            if (edges.length) {
	              const y = d3Array.sum(edges, weightedTarget) / d3Array.sum(edges, value);
	              node.y += (y - center(node)) * alpha;
	            }
	          });
	        });
	      });

	      function weightedTarget (link) {
	        return center(link.target) * link.value
	      }
	    }

	    function resolveCollisions () {
	      nested.forEach(layer => {
	        layer.forEach(nodes => {
	          let node;
	          let dy;
	          let y0 = 0;
	          const n = nodes.length;
	          let i;

	          // Push any overlapping nodes down.
	          nodes.sort(ascendingDepth);
	          for (i = 0; i < n; ++i) {
	            node = nodes[i];
	            dy = y0 - node.y;
	            if (dy > 0) node.y += dy;
	            y0 = node.y + node.dy + nodePadding;
	          }

	          // If the bottommost node goes outside the bounds, push it back up.
	          dy = y0 - nodePadding - height;
	          if (dy > 0) {
	            y0 = node.y -= dy;

	            // Push any overlapping nodes back up.
	            for (i = n - 2; i >= 0; --i) {
	              node = nodes[i];
	              dy = node.y + node.dy + nodePadding - y0;
	              if (dy > 0) node.y -= dy;
	              y0 = node.y;
	            }
	          }
	        });
	      });
	    }
	  }

	  layout.iterations = function (x) {
	    if (!arguments.length) return iterations
	    iterations = +x;
	    return layout
	  };

	  layout.padding = function (x) {
	    if (!arguments.length) return nodePadding
	    nodePadding = +x;
	    return layout
	  };

	  return layout
	}

	function center (node) {
	  return node.y + node.dy / 2
	}

	function value (link) {
	  return link.value
	}

	function ascendingDepth (a, b) {
	  return a.y - b.y
	}

	// function defaultSegments (d) {
	//   return d.segments
	// }

	function defaultMinWidth (d) {
	  return (d.dy === 0) ? 0 : 2
	}

	function sankeyLink () {
	  // var segments = defaultSegments
	  let minWidth = defaultMinWidth;

	  function radiusBounds (d) {
	    const Dx = d.x1 - d.x0;
	    const Dy = d.y1 - d.y0;
	    const Rmin = d.dy / 2;
	    const Rmax = (Dx * Dx + Dy * Dy) / Math.abs(4 * Dy);
	    return [Rmin, Rmax]
	  }

	  function link (d) {
	    if (d.points.length === 1) {
	      return toOrFromElsewherePath(d)
	    }

	    let path = '';
	    let seg;
	    for (let i = 0; i < d.points.length - 1; ++i) {
	      seg = {
	        x0: d.points[i].x,
	        y0: d.points[i].y,
	        x1: d.points[i + 1].x,
	        y1: d.points[i + 1].y,
	        r0: d.points[i].ro,
	        r1: d.points[i + 1].ri,
	        d0: d.points[i].d,
	        d1: d.points[i + 1].d,
	        dy: d.dy
	      };
	      path += segmentPath(seg);
	    }
	    return path
	  }

	  function segmentPath (d) {
	    const dir = (d.d0 || 'r') + (d.d1 || 'r');
	    if (d.source && d.source === d.target) {
	      return selfLink(d)
	    }
	    if (dir === 'rl') {
	      return fbLink(d)
	    }
	    if (dir === 'rd') {
	      return fdLink(d)
	    }
	    if (dir === 'dr') {
	      return dfLink(d)
	    }
	    if (dir === 'lr') {
	      return bfLink(d)
	    }

	    // Minimum thickness 2px
	    const h = Math.max(minWidth(d), d.dy) / 2;
	    let x0 = d.x0;
	    let x1 = d.x1;
	    let y0 = d.y0;
	    let y1 = d.y1;

	    if (x1 < x0) {
	      [x0, x1] = [x1, x0];
	      [y0, y1] = [y1, y0];
	    }

	    const f = y1 > y0 ? 1 : -1;
	    const fx = 1; // dir === 'll' ? -1 : 1;

	    const Rlim = radiusBounds(d);
	    const defaultRadius = Math.max(Rlim[0], Math.min(Rlim[1], (x1 - x0) / 3));

	    let r0 = Math.max(Rlim[0], Math.min(Rlim[1], (d.r0 || defaultRadius)));
	    let r1 = Math.max(Rlim[0], Math.min(Rlim[1], (d.r1 || defaultRadius)));

	    const dcx = (x1 - x0);
	    const dcy = (y1 - y0) - f * (r0 + r1);
	    const D = Math.sqrt(dcx * dcx + dcy * dcy);

	    const phi = -f * Math.acos(Math.min(1, (r0 + r1) / D));
	    const psi = Math.atan2(dcy, dcx);

	    let theta = Math.PI / 2 + f * (psi + phi);

	    let hs = h * f * Math.sin(theta);
	    let hc = h * Math.cos(theta);
	    let x2 = x0 + fx * r0 * Math.sin(Math.abs(theta));
	    let x3 = x1 - fx * r1 * Math.sin(Math.abs(theta));
	    let y2 = y0 + r0 * f * (1 - Math.cos(theta));
	    let y3 = y1 - r1 * f * (1 - Math.cos(theta));

	    if (isNaN(theta) || Math.abs(theta) < 1e-3) {
	      theta = r0 = r1 = 0;
	      x2 = x0;
	      x3 = x1;
	      y2 = y0;
	      y3 = y1;
	      hs = 0;
	      hc = h;
	    }

	    function arc (dir, r) {
	      const f = (dir * (y1 - y0) > 0) ? 1 : 0;
	      let rr = (fx * dir * (y1 - y0) > 0) ? (r + h) : (r - h);
	      // straight line
	      if (theta === 0) { rr = r; }
	      return 'A' + rr + ' ' + rr + ' ' + Math.abs(theta) + ' 0 ' + f + ' '
	    }

	    // if (fx * (x2 - x3) < 0 || Math.abs(y1 - y0) > 4*h) {
	    // XXX this causes juddering during transitions

	    const path = (
	      'M' + [x0, y0 - h] + ' ' +
	      arc(+1, r0) + [x2 + hs, y2 - hc] + ' ' +
	      'L' + [x3 + hs, y3 - hc] + ' ' +
	      arc(-1, r1) + [x1, y1 - h] + ' ' +
	      'L' + [x1, y1 + h] + ' ' +
	      arc(+1, r1) + [x3 - hs, y3 + hc] + ' ' +
	      'L' + [x2 - hs, y2 + hc] + ' ' +
	      arc(-1, r0) + [x0, y0 + h] + ' ' +
	      'Z'
	    );

	    if (/NaN/.test(path)) {
	      console.error('path NaN', d, path);
	    }
	    return path
	  }

	  function selfLink (d) {
	    const h = Math.max(minWidth(d), d.dy) / 2;
	    const r = h * 1.5;
	    const theta = 2 * Math.PI;
	    const x0 = d.x0;
	    const y0 = d.y0;

	    function arc (dir) {
	      const f = (dir > 0) ? 1 : 0;
	      const rr = (dir > 0) ? (r + h) : (r - h);
	      return 'A' + rr + ' ' + rr + ' ' + Math.abs(theta) + ' 1 ' + f + ' '
	    }

	    return ('M' + [x0 + 0.1, y0 - h] + ' ' +
	            arc(+1) + [x0 - 0.1, y0 - h] + ' ' +
	            'L' + [x0 - 0.1, y0 + h] + ' ' +
	            arc(-1) + [x0 + 0.1, y0 + h] + ' ' +
	            'Z')
	  }

	  function fbLink (d) {
	    // Minimum thickness 2px
	    const h = Math.max(minWidth(d), d.dy) / 2;
	    const x0 = d.x0;
	    const x1 = d.x1;
	    const y0 = d.y0;
	    const y1 = d.y1;
	    const Dx = d.x1 - d.x0;
	    const Dy = d.y1 - d.y0;
	    // Rlim = radiusBounds(d),
	    const defaultRadius = ((d.r0 + d.r1) / 2) || (5 + h); // Math.max(Rlim[0], Math.min(Rlim[1], Dx/3)),
	    const r = Math.min(Math.abs(y1 - y0) / 2.1, defaultRadius); // 2*(d.r || defaultRadius),
	    const theta = Math.atan2(Dy - 2 * r, Dx);
	    const f = d.y1 > d.y0 ? 1 : -1;
	    const hs = h * Math.sin(theta);
	    const hc = h * Math.cos(theta);
	    const x2 = d.x0 + r * Math.sin(Math.abs(theta));
	    const x3 = d.x1 + r * Math.sin(Math.abs(theta));
	    const y2 = d.y0 + r * f * (1 - Math.cos(theta));
	    const y3 = d.y1 - r * f * (1 - Math.cos(theta));

	    function arc (dir) {
	      const f = (dir * theta > 0) ? 1 : 0;
	      let rr = (dir * theta > 0) ? (r + h) : (r - h);
	      // straight line
	      if (theta === 0) { rr = r; }
	      return 'A' + rr + ' ' + rr + ' ' + Math.abs(theta) + ' 0 ' + f + ' '
	    }

	    return ('M' + [x0, y0 - h] + ' ' +
	            arc(+1) + [x2 + hs, y2 - hc] + ' ' +
	            'L' + [x3 + hs, y3 - hc] + ' ' +
	            arc(+1) + [x1, y1 + h] + ' ' +
	            'L' + [x1, y1 - h] + ' ' +
	            arc(-1) + [x3 - hs, y3 + hc] + ' ' +
	            'L' + [x2 - hs, y2 + hc] + ' ' +
	            arc(-1) + [x0, y0 + h] + ' ' +
	            'Z')
	  }

	  function fdLink (d) {
	    // Minimum thickness 2px
	    const h = Math.max(minWidth(d), d.dy) / 2;
	    const x0 = d.x0;
	    const x1 = d.x1;
	    const y0 = d.y0;
	    const y1 = d.y1;
	    const theta = Math.PI / 2;
	    const r = Math.max(0, x1 - x0);
	    const y2 = y0 + r;

	    function arc (dir) {
	      const f = (dir * theta > 0) ? 1 : 0;
	      let rr = (dir * theta > 0) ? (r + h) : (r - h);
	      // straight line
	      if (theta === 0) { rr = r; }
	      return 'A' + rr + ' ' + rr + ' ' + Math.abs(theta) + ' 0 ' + f + ' '
	    }

	    return ('M' + [x0, y0 - h] + ' ' +
	            arc(+1) + [x1 + h, y2] + ' ' +
	            'L' + [x1 + h, y1] + ' ' +
	            '' + [x1 - h, y1] + ' ' +
	            '' + [x1 - h, y2] + ' ' +
	            arc(-1) + [x0, y0 + h] + ' ' +
	            'Z')
	  }

	  function dfLink (d) {
	    // Minimum thickness 2px
	    const h = Math.max(minWidth(d), d.dy) / 2;
	    const x0 = d.x0;
	    const x1 = d.x1;
	    const y0 = d.y0;
	    const y1 = d.y1;
	    const theta = Math.PI / 2;
	    const r = Math.max(0, x1 - x0);
	    const y2 = y1 - r;

	    function arc (dir) {
	      const f = (dir * theta > 0) ? 1 : 0;
	      let rr = (dir * theta > 0) ? (r + h) : (r - h);
	      // straight line
	      if (theta === 0) { rr = r; }
	      return 'A' + rr + ' ' + rr + ' ' + Math.abs(theta) + ' 0 ' + f + ' '
	    }

	    return ('M' + [x0 - h, y0] + ' ' +
	            'L' + [x0 + h, y0] + ' ' +
	            '' + [x0 + h, y2] + ' ' +
	            arc(-1) + [x1, y1 - h] + ' ' +
	            'L' + [x1, y1 + h] + ' ' +
	            arc(+1) + [x0 - h, y2] + ' ' +
	            'Z')
	  }

	  function bfLink (d) {
	    // Minimum thickness 2px
	    const h = Math.max(minWidth(d), d.dy) / 2;
	    const x0 = d.x0;
	    const x1 = d.x1;
	    const y0 = d.y0;
	    const y1 = d.y1;
	    const Dx = d.x1 - d.x0;
	    const Dy = d.y1 - d.y0;
	    // Rlim = radiusBounds(d),
	    const defaultRadius = ((d.r0 + d.r1) / 2) || (5 + h); // Math.max(Rlim[0], Math.min(Rlim[1], Dx/3)),
	    const r = Math.min(Math.abs(Dy) / 2.1, defaultRadius); // 2*(d.r || defaultRadius),
	    const theta = Math.atan2(Dy - 2 * r, Dx);
	    // const l = Math.sqrt(Math.max(0, Dx * Dx + (Dy - 2 * r) * (Dy - 2 * r)))
	    const f = d.y1 > d.y0 ? 1 : -1;
	    const hs = h * Math.sin(theta);
	    const hc = h * Math.cos(theta);
	    const x2 = d.x0 - r * Math.sin(Math.abs(theta));
	    const x3 = d.x1 - r * Math.sin(Math.abs(theta));
	    const y2 = d.y0 + r * f * (1 - Math.cos(theta));
	    const y3 = d.y1 - r * f * (1 - Math.cos(theta));

	    function arc (dir) {
	      const f = (dir * theta > 0) ? 1 : 0;
	      let rr = (-dir * theta > 0) ? (r + h) : (r - h);
	      // straight line
	      if (theta === 0) { rr = r; }
	      return 'A' + rr + ' ' + rr + ' ' + Math.abs(theta) + ' 0 ' + f + ' '
	    }

	    return ('M' + [x0, y0 - h] + ' ' +
	            arc(-1) + [x2 - hs, y2 - hc] + ' ' +
	            'L' + [x3 - hs, y3 - hc] + ' ' +
	            arc(-1) + [x1, y1 + h] + ' ' +
	            'L' + [x1, y1 - h] + ' ' +
	            arc(+1) + [x3 + hs, y3 - hc] + ' ' +
	            'L' + [x2 + hs, y2 - hc] + ' ' +
	            arc(+1) + [x0, y0 + h] + ' ' +
	            'Z')
	  }

	  function toOrFromElsewherePath (d) {
	    const p = d.points[0];
	    const h = Math.max(minWidth(d), d.dy) / 2;

	    // XXX draw these properly with curves and appropriate radii

	    if (p.style === 'down-right') {
	      return ('M' + [p.x - 20, p.y - h] + ' ' +
	              'L' + [p.x, p.y - h] + ' ' +
	              'L' + [p.x, p.y + h] + ' ' +
	              'L' + [p.x - 20, p.y + h] + ' ' +
	              'Z')
	    }
	    if (p.style === 'right-down') {
	      return ('M' + [p.x, p.y - h] + ' ' +
	              'L' + [p.x + 20, p.y - h] + ' ' +
	              'L' + [p.x + 20, p.y + h] + ' ' +
	              'L' + [p.x, p.y + h] + ' ' +
	              'Z')
	    }
	  }

	  link.minWidth = function (x) {
	    if (arguments.length) {
	      minWidth = required$1(x);
	      return link
	    }
	    return minWidth
	  };

	  return link
	}

	function required$1 (f) {
	  if (typeof f !== 'function') throw new Error()
	  return f
	}

	function sankeyNode () {
	  let nodeTitle = (d) => d.title !== undefined ? d.title : d.id;
	  let nodeValue = (d) => null;
	  let nodeVisible = (d) => !!nodeTitle(d);

	  function sankeyNode (context) {
	    const selection = context.selection ? context.selection() : context;

	    if (selection.select('text').empty()) {
	      selection.append('title');
	      selection.append('line')
	        .attr('x1', 0)
	        .attr('x2', 0);
	      selection.append('rect')
	        .attr('class', 'node-body');
	      selection.append('text')
	        .attr('class', 'node-value')
	        .attr('dy', '.35em')
	        .attr('text-anchor', 'middle');
	      selection.append('text')
	        .attr('class', 'node-title')
	        .attr('dy', '.35em');
	      selection.append('rect')
	        .attr('class', 'node-click-target')
	        .attr('x', -5)
	        .attr('y', -5)
	        .attr('width', 10)
	        .style('fill', 'none')
	        .style('visibility', 'hidden')
	        .style('pointer-events', 'all');

	      selection
	        .attr('transform', nodeTransform);
	    }

	    selection.each(function (d) {
	      const title = d3Selection.select(this).select('title');
	      const value = d3Selection.select(this).select('.node-value');
	      let text = d3Selection.select(this).select('.node-title');
	      let line = d3Selection.select(this).select('line');
	      let body = d3Selection.select(this).select('.node-body');
	      let clickTarget = d3Selection.select(this).select('.node-click-target');

	      // Local var for title position of each node
	      const layoutData = titlePosition(d);
	      layoutData.dy = (d.y0 === d.y1) ? 0 : Math.max(1, d.y1 - d.y0);

	      const separateValue = (d.x1 - d.x0) > 2;
	      const titleText = nodeTitle(d) + ((!separateValue && nodeValue(d))
	        ? ' (' + nodeValue(d) + ')'
	        : '');

	      // Update un-transitioned
	      title
	        .text(titleText);

	      value
	        .text(nodeValue)
	        .style('display', separateValue ? 'inline' : 'none');

	      text
	        .attr('text-anchor', layoutData.right ? 'end' : 'start')
	        .text(titleText)
	        .each(wrap, 100);

	      // Are we in a transition?
	      if (context !== selection) {
	        text = text.transition(context);
	        line = line.transition(context);
	        body = body.transition(context);
	        clickTarget = clickTarget.transition(context);
	      }

	      // Update
	      context
	        .attr('transform', nodeTransform);

	      line
	        .attr('y1', function (d) { return layoutData.titleAbove ? -5 : 0 })
	        .attr('y2', function (d) { return layoutData.dy })
	        .style('display', function (d) {
	          return (d.y0 === d.y1 || !nodeVisible(d)) ? 'none' : 'inline'
	        });

	      clickTarget
	        .attr('height', function (d) { return layoutData.dy + 5 });

	      body
	        .attr('width', function (d) { return d.x1 - d.x0 })
	        .attr('height', function (d) { return layoutData.dy });

	      text
	        .attr('transform', textTransform)
	        .style('display', function (d) {
	          return (d.y0 === d.y1 || !nodeVisible(d)) ? 'none' : 'inline'
	        });

	      value
	        .style('font-size', function (d) { return Math.min(d.x1 - d.x0 - 4, d.y1 - d.y0 - 4) + 'px' })
	        .attr('transform', function (d) {
	          const dx = d.x1 - d.x0;
	          const dy = d.y1 - d.y0;
	          const theta = dx > dy ? 0 : -90;
	          return 'translate(' + (dx / 2) + ',' + (dy / 2) + ') rotate(' + theta + ')'
	        });

	      function textTransform (d) {
	        const layout = layoutData;
	        const y = layout.titleAbove ? -10 : (d.y1 - d.y0) / 2;
	        let x;
	        if (layout.titleAbove) {
	          x = (layout.right ? 4 : -4);
	        } else {
	          x = (layout.right ? -4 : d.x1 - d.x0 + 4);
	        }
	        return 'translate(' + x + ',' + y + ')'
	      }
	    });
	  }

	  sankeyNode.nodeVisible = function (x) {
	    if (arguments.length) {
	      nodeVisible = required(x);
	      return sankeyNode
	    }
	    return nodeVisible
	  };

	  sankeyNode.nodeTitle = function (x) {
	    if (arguments.length) {
	      nodeTitle = required(x);
	      return sankeyNode
	    }
	    return nodeTitle
	  };

	  sankeyNode.nodeValue = function (x) {
	    if (arguments.length) {
	      nodeValue = required(x);
	      return sankeyNode
	    }
	    return nodeValue
	  };

	  return sankeyNode
	}

	function nodeTransform (d) {
	  return 'translate(' + d.x0 + ',' + d.y0 + ')'
	}

	function titlePosition (d) {
	  let titleAbove = false;
	  let right = false;

	  // If thin, and there's enough space, put above
	  if (d.spaceAbove > 20 && d.style !== 'type') {
	    titleAbove = true;
	  } else {
	    titleAbove = false;
	  }

	  if (d.incoming.length === 0) {
	    right = true;
	    titleAbove = false;
	  } else if (d.outgoing.length === 0) {
	    right = false;
	    titleAbove = false;
	  }

	  return { titleAbove, right }
	}

	function wrap (d, width) {
	  const text = d3Selection.select(this);
	  const lines = text.text().split(/\n/);
	  const lineHeight = 1.1; // ems
	  if (lines.length === 1) return
	  text.text(null);
	  lines.forEach(function (line, i) {
	    text.append('tspan')
	      .attr('x', 0)
	      .attr('dy', (i === 0 ? 0.7 - lines.length / 2 : 1) * lineHeight + 'em')
	      .text(line);
	  });
	}

	function required (f) {
	  if (typeof f !== 'function') throw new Error()
	  return f
	}

	function positionGroup (nodes, group) {
	  const rect = {
	    top: Number.MAX_VALUE,
	    left: Number.MAX_VALUE,
	    bottom: 0,
	    right: 0
	  };

	  group.nodes.forEach(n => {
	    const node = nodes.get(n);
	    if (!node) return
	    if (node.x0 < rect.left) rect.left = node.x0;
	    if (node.x1 > rect.right) rect.right = node.x1;
	    if (node.y0 < rect.top) rect.top = node.y0;
	    if (node.y1 > rect.bottom) rect.bottom = node.y1;
	  });

	  group.rect = rect;
	  return group
	}

	// The reusable SVG component for the sliced Sankey diagram


	function linkTitleGenerator (nodeTitle, typeTitle, fmt) {
	  return function (d) {
	    const parts = [];
	    const sourceTitle = nodeTitle(d.source);
	    const targetTitle = nodeTitle(d.target);
	    const matTitle = typeTitle(d);

	    parts.push(`${sourceTitle} → ${targetTitle}`);
	    if (matTitle) parts.push(matTitle);
	    parts.push(fmt(d.value));
	    return parts.join('\n')
	  }
	}

	function sankeyDiagram () {
	  let margin = { top: 0, right: 0, bottom: 0, left: 0 };
	  let selectedEdge = null;

	  let groups = [];

	  const fmt = d3Format.format('.3s');
	  const node = sankeyNode();
	  const link = sankeyLink();

	  let linkColor = d => null;
	  let linkTitle = linkTitleGenerator(node.nodeTitle(), d => d.type, fmt);
	  let linkLabel = defaultLinkLabel;
	  let linkImportance = defaultLinkImportance;
	  let linkImportanceAgg = defaultLinkImportanceAgg;

	  const listeners = d3Dispatch.dispatch('selectNode', 'selectGroup', 'selectLink');

	  /* Main chart */

	  function exports (context) {
	    const selection = context.selection ? context.selection() : context;

	    selection.each(function (G) {
	      // Create the skeleton, if it doesn't already exist
	      const svg = d3Selection.select(this);

	      let sankey = svg.selectAll('.sankey')
	        .data([{ type: 'sankey' }]);

	      const sankeyEnter = sankey.enter()
	        .append('g')
	        .classed('sankey', true);

	      sankeyEnter.append('g').classed('groups', true);
	      sankeyEnter.append('g').classed('links', true); // Links below nodes
	      sankeyEnter.append('g').classed('nodes', true);
	      sankeyEnter.append('g').classed('slice-titles', true); // Slice titles

	      sankey = sankey.merge(sankeyEnter);

	      // Update margins
	      sankey
	        .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
	        // .select('.slice-titles')
	        // .attr('transform', 'translate(' + margin.left + ',0)')

	      // Groups of nodes
	      const nodeMap = d3Collection.map(G.nodes, n => n.id);
	      const groupsPositioned = (groups || []).map(g => positionGroup(nodeMap, g));

	      // All links -- including "from elsewhere" and "to elsewhere" ones
	      const links = Array.from(G.links);
	      G.nodes.forEach(node => {
	        Array.prototype.push.apply(links, node.fromElsewhere || []);
	        Array.prototype.push.apply(links, node.toElsewhere || []);
	      });

	      // Render
	      updateNodes(sankey, context, G.nodes);
	      updateLinks(sankey, context, links);
	      updateGroups(svg, groupsPositioned);
	      // updateSlices(svg, layout.slices(nodes));

	      // Events
	      svg.on('click', function () {
	        listeners.call('selectNode', this, null);
	        listeners.call('selectLink', this, null);
	      });
	    });
	  }

	  function updateNodes (sankey, context, nodes) {
	    let nodeSel = sankey
	      .select('.nodes')
	      .selectAll('.node')
	      .data(nodes, d => d.id);

	    // EXIT
	    nodeSel.exit().remove();

	    nodeSel = nodeSel.merge(
	      nodeSel.enter()
	        .append('g')
	        .attr('class', 'node')
	        .call(node)
	        .on('click', selectNode));

	    if (context instanceof d3Transition.transition) {
	      nodeSel.transition(context)
	        .call(node);
	    } else {
	      nodeSel.call(node);
	    }
	  }

	  function updateLinks (sankey, context, edges) {
	    let linkSel = sankey
	      .select('.links')
	      .selectAll('.link')
	      .data(edges, d => d.source.id + '-' + d.target.id + '-' + d.type);

	    // EXIT

	    linkSel.exit().remove();

	    // ENTER

	    const linkEnter = linkSel.enter()
	      .append('g')
	      .attr('class', 'link')
	      .on('click', selectLink);

	    linkEnter.append('path')
	      .attr('d', link)
	      .style('fill', 'white')
	      .each(function (d) { this._current = d; });

	    linkEnter.append('title');

	    linkEnter.append('text')
	      .attr('class', 'label')
	      .attr('dy', '0.35em')
	      .attr('x', d => d.points[0].x + 4)
	      .attr('y', d => d.points[0].y);

	    // UPDATE

	    linkSel = linkSel.merge(linkEnter);

	    // Calculate group importance for sorting
	    calculateGroupImportance(edges);

	    // Non-transition updates
	    linkSel.classed('selected', (d) => d.id === selectedEdge);
	    linkSel.sort(linkOrder);

	    // Transition updates, if available
	    if (context instanceof d3Transition.transition) {
	      linkSel = linkSel.transition(context);
	      linkSel
	        .select('path')
	        .style('fill', linkColor)
	        .each(function (d) {
	          d3Selection.select(this)
	            .transition(context)
	            .attrTween('d', interpolateLink);
	        });
	    } else {
	      linkSel
	        .select('path')
	        .style('fill', linkColor)
	        .attr('d', link);
	    }

	    linkSel.select('title')
	      .text(linkTitle);

	    linkSel.select('.label')
	      .text(linkLabel)
	      .attr('x', d => d.points[0].x + 4)
	      .attr('y', d => d.points[0].y);
	  }

	  function calculateGroupImportance (edges) {
	    // Group links by source-target pair
	    const groups = new Map();

	    // Calculate individual importance for each link
	    edges.forEach(edge => {
	      const groupKey = edge.source.id + '-' + edge.target.id;
	      if (!groups.has(groupKey)) {
	        groups.set(groupKey, []);
	      }
	      const importance = linkImportance(edge);
	      edge._importance = importance;
	      groups.get(groupKey).push({ edge, importance });
	    });

	    // Calculate aggregated importance for each group and assign to all links in group
	    groups.forEach(linkGroup => {
	      const importanceValues = linkGroup.map(item => item.importance);
	      const groupImportance = linkImportanceAgg(importanceValues);
	      linkGroup.forEach(item => {
	        item.edge._groupImportance = groupImportance;
	      });
	    });
	  }

	  // function updateSlices(svg, slices) {
	  //   var slice = svg.select('.slice-titles').selectAll('.slice')
	  //         .data(slices, function(d) { return d.id; });

	  //   var textWidth = (slices.length > 1 ?
	  //                    0.9 * (slices[1].x - slices[0].x) :
	  //                    null);

	  //   slice.enter().append('g')
	  //     .attr('class', 'slice')
	  //     .append('foreignObject')
	  //     .attr('requiredFeatures',
	  //           'http://www.w3.org/TR/SVG11/feature#Extensibility')
	  //     .attr('height', margin.top)
	  //     .attr('class', 'title')
	  //     .append('xhtml:div')
	  //     .style('text-align', 'center')
	  //     .style('word-wrap', 'break-word');
	  //   // .text(pprop('sliceMetadata', 'title'));

	  //   slice
	  //     .attr('transform', function(d) {
	  //       return 'translate(' + (d.x - textWidth / 2) + ',0)'; })
	  //     .select('foreignObject')
	  //     .attr('width', textWidth)
	  //     .select('div');
	  //   // .text(pprop('sliceMetadata', 'title'));

	  //   slice.exit().remove();
	  // }

	  function updateGroups (svg, groups) {
	    let group = svg.select('.groups').selectAll('.group')
	      .data(groups);

	    // EXIT
	    group.exit().remove();

	    // ENTER
	    const enter = group.enter().append('g')
	      .attr('class', 'group');
	    // .on('click', selectGroup);

	    enter.append('rect');
	    enter.append('text')
	      .attr('x', -10)
	      .attr('y', -25);

	    group = group.merge(enter);

	    group
	      .style('display', d => d.title ? 'inline' : 'none')
	      .attr('transform', d => `translate(${d.rect.left},${d.rect.top})`)
	      .select('rect')
	      .attr('x', -10)
	      .attr('y', -20)
	      .attr('width', d => d.rect.right - d.rect.left + 20)
	      .attr('height', d => d.rect.bottom - d.rect.top + 30);

	    group.select('text')
	      .text(d => d.title);
	  }

	  function interpolateLink (b) {
	    // XXX should limit radius better
	    b.points.forEach(function (p) {
	      if (p.ri > 1e3) p.ri = 1e3;
	      if (p.ro > 1e3) p.ro = 1e3;
	    });
	    const interp = d3Interpolate.interpolate(linkGeom(this._current), b);
	    const that = this;
	    return function (t) {
	      that._current = interp(t);
	      return link(that._current)
	    }
	  }

	  function linkGeom (l) {
	    return {
	      points: l.points,
	      dy: l.dy
	    }
	  }

	  function linkOrder (a, b) {
	    // Selected links always on top
	    if (a.id === selectedEdge) return +1
	    if (b.id === selectedEdge) return -1
	    // All other sorting based on group importance
	    return a._groupImportance - b._groupImportance
	  }

	  function selectLink (d) {
	    d3Selection.event.stopPropagation();
	    const el = d3Selection.select(this).node();
	    listeners.call('selectLink', el, d);
	  }

	  function selectNode (d) {
	    d3Selection.event.stopPropagation();
	    const el = d3Selection.select(this).node();
	    listeners.call('selectNode', el, d);
	  }

	  // function selectGroup(d) {
	  //   d3.event.stopPropagation();
	  //   var el = d3.select(this)[0][0];
	  //   dispatch.selectGroup.call(el, d);
	  // }

	  exports.margins = function (_x) {
	    if (!arguments.length) return margin
	    margin = {
	      top: _x.top === undefined ? margin.top : _x.top,
	      left: _x.left === undefined ? margin.left : _x.left,
	      bottom: _x.bottom === undefined ? margin.bottom : _x.bottom,
	      right: _x.right === undefined ? margin.right : _x.right
	    };
	    return this
	  };

	  exports.groups = function (_x) {
	    if (!arguments.length) return groups
	    groups = _x;
	    return this
	  };

	  // Node styles and title
	  exports.nodeTitle = function (_x) {
	    if (!arguments.length) return node.nodeTitle()
	    node.nodeTitle(_x);
	    linkTitle = linkTitleGenerator(_x, d => d.type, fmt);
	    return this
	  };

	  exports.nodeValue = function (_x) {
	    if (!arguments.length) return node.nodeValue()
	    node.nodeValue(_x);
	    return this
	  };

	  // Link styles and titles
	  exports.linkTitle = function (_x) {
	    if (!arguments.length) return linkTitle
	    linkTitle = _x;
	    return this
	  };

	  exports.linkLabel = function (_x) {
	    if (!arguments.length) return linkLabel
	    linkLabel = _x;
	    return this
	  };

	  exports.linkColor = function (_x) {
	    if (!arguments.length) return linkColor
	    linkColor = _x;
	    return this
	  };

	  exports.linkMinWidth = function (_x) {
	    if (!arguments.length) return link.minWidth()
	    link.minWidth(_x);
	    return this
	  };

	  exports.linkImportance = function (_x) {
	    if (!arguments.length) return linkImportance
	    linkImportance = _x;
	    return this
	  };

	  exports.linkImportanceAgg = function (_x) {
	    if (!arguments.length) return linkImportanceAgg
	    linkImportanceAgg = _x;
	    return this
	  };

	  exports.selectNode = function (_x) {
	    return this
	  };

	  exports.selectLink = function (_x) {
	    selectedEdge = _x;
	    return this
	  };

	  exports.on = function () {
	    const value = listeners.on.apply(listeners, arguments);
	    return value === listeners ? exports : value
	  };

	  return exports
	}

	function defaultLinkLabel (d) {
	  return null
	}

	function defaultLinkImportance (d) {
	  // Return negative values for special cases to put them at the bottom
	  if (!d.source || (d.target && d.target.direction === 'd')) return -2
	  if (!d.target || (d.source && d.source.direction === 'd')) return -1
	  // Return dy (visual width) for normal links
	  return d.dy
	}

	function defaultLinkImportanceAgg (values) {
	  // Sum of importance values for all links in the group
	  return values.reduce((a, b) => a + b, 0)
	}

	exports.sankey = sankeyLayout;
	exports.sankeyDiagram = sankeyDiagram;
	exports.sankeyLink = sankeyLink;
	exports.sankeyLinkTitle = linkTitleGenerator;
	exports.sankeyNode = sankeyNode;
	exports.sankeyPositionJustified = positionNodesVertically$1;
	exports.sankeyPositionRelaxation = positionNodesVertically;

}));