Source code

Revision control

Copy as Markdown

Other Tools

;(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
var global=self;/**
* @license
* Copyright (c) 2012-2013 Chris Pettitt
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
global.dagreD3 = require('./index');
},{"./index":2}],2:[function(require,module,exports){
/**
* @license
* Copyright (c) 2012-2013 Chris Pettitt
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
module.exports = {
Digraph: require('graphlib').Digraph,
Renderer: require('./lib/Renderer'),
json: require('graphlib').converter.json,
layout: require('dagre').layout,
version: require('./lib/version')
};
},{"./lib/Renderer":3,"./lib/version":4,"dagre":11,"graphlib":28}],3:[function(require,module,exports){
var layout = require('dagre').layout;
var d3;
try { d3 = require('d3'); } catch (_) { d3 = window.d3; }
module.exports = Renderer;
function Renderer() {
// Set up defaults...
this._layout = layout();
this.drawNodes(defaultDrawNodes);
this.drawEdgeLabels(defaultDrawEdgeLabels);
this.drawEdgePaths(defaultDrawEdgePaths);
this.positionNodes(defaultPositionNodes);
this.positionEdgeLabels(defaultPositionEdgeLabels);
this.positionEdgePaths(defaultPositionEdgePaths);
this.transition(defaultTransition);
this.postLayout(defaultPostLayout);
this.postRender(defaultPostRender);
this.edgeInterpolate('bundle');
this.edgeTension(0.95);
}
Renderer.prototype.layout = function(layout) {
if (!arguments.length) { return this._layout; }
this._layout = layout;
return this;
};
Renderer.prototype.drawNodes = function(drawNodes) {
if (!arguments.length) { return this._drawNodes; }
this._drawNodes = bind(drawNodes, this);
return this;
};
Renderer.prototype.drawEdgeLabels = function(drawEdgeLabels) {
if (!arguments.length) { return this._drawEdgeLabels; }
this._drawEdgeLabels = bind(drawEdgeLabels, this);
return this;
};
Renderer.prototype.drawEdgePaths = function(drawEdgePaths) {
if (!arguments.length) { return this._drawEdgePaths; }
this._drawEdgePaths = bind(drawEdgePaths, this);
return this;
};
Renderer.prototype.positionNodes = function(positionNodes) {
if (!arguments.length) { return this._positionNodes; }
this._positionNodes = bind(positionNodes, this);
return this;
};
Renderer.prototype.positionEdgeLabels = function(positionEdgeLabels) {
if (!arguments.length) { return this._positionEdgeLabels; }
this._positionEdgeLabels = bind(positionEdgeLabels, this);
return this;
};
Renderer.prototype.positionEdgePaths = function(positionEdgePaths) {
if (!arguments.length) { return this._positionEdgePaths; }
this._positionEdgePaths = bind(positionEdgePaths, this);
return this;
};
Renderer.prototype.transition = function(transition) {
if (!arguments.length) { return this._transition; }
this._transition = bind(transition, this);
return this;
};
Renderer.prototype.postLayout = function(postLayout) {
if (!arguments.length) { return this._postLayout; }
this._postLayout = bind(postLayout, this);
return this;
};
Renderer.prototype.postRender = function(postRender) {
if (!arguments.length) { return this._postRender; }
this._postRender = bind(postRender, this);
return this;
};
Renderer.prototype.edgeInterpolate = function(edgeInterpolate) {
if (!arguments.length) { return this._edgeInterpolate; }
this._edgeInterpolate = edgeInterpolate;
return this;
};
Renderer.prototype.edgeTension = function(edgeTension) {
if (!arguments.length) { return this._edgeTension; }
this._edgeTension = edgeTension;
return this;
};
Renderer.prototype.run = function(graph, svg) {
// First copy the input graph so that it is not changed by the rendering
// process.
graph = copyAndInitGraph(graph);
// Create layers
svg
.selectAll('g.edgePaths, g.edgeLabels, g.nodes')
.data(['edgePaths', 'edgeLabels', 'nodes'])
.enter()
.append('g')
.attr('class', function(d) { return d; });
// Create node and edge roots, attach labels, and capture dimension
// information for use with layout.
var svgNodes = this._drawNodes(graph, svg.select('g.nodes'));
var svgEdgeLabels = this._drawEdgeLabels(graph, svg.select('g.edgeLabels'));
svgNodes.each(function(u) { calculateDimensions(this, graph.node(u)); });
svgEdgeLabels.each(function(e) { calculateDimensions(this, graph.edge(e)); });
// Now apply the layout function
var result = runLayout(graph, this._layout);
// Run any user-specified post layout processing
this._postLayout(result, svg);
var svgEdgePaths = this._drawEdgePaths(graph, svg.select('g.edgePaths'));
// Apply the layout information to the graph
this._positionNodes(result, svgNodes);
this._positionEdgeLabels(result, svgEdgeLabels);
this._positionEdgePaths(result, svgEdgePaths);
this._postRender(result, svg);
return result;
};
function copyAndInitGraph(graph) {
var copy = graph.copy();
// Init labels if they were not present in the source graph
copy.nodes().forEach(function(u) {
var value = copy.node(u);
if (value === undefined) {
value = {};
copy.node(u, value);
}
if (!('label' in value)) { value.label = ''; }
});
copy.edges().forEach(function(e) {
var value = copy.edge(e);
if (value === undefined) {
value = {};
copy.edge(e, value);
}
if (!('label' in value)) { value.label = ''; }
});
return copy;
}
function calculateDimensions(group, value) {
var bbox = group.getBBox();
value.width = bbox.width;
value.height = bbox.height;
}
function runLayout(graph, layout) {
var result = layout.run(graph);
// Copy labels to the result graph
graph.eachNode(function(u, value) { result.node(u).label = value.label; });
graph.eachEdge(function(e, u, v, value) { result.edge(e).label = value.label; });
return result;
}
function defaultDrawNodes(g, root) {
var nodes = g.nodes().filter(function(u) { return !isComposite(g, u); });
var svgNodes = root
.selectAll('g.node')
.classed('enter', false)
.data(nodes, function(u) { return u; });
svgNodes.selectAll('*').remove();
svgNodes
.enter()
.append('g')
.style('opacity', 0)
.attr('class', 'node enter');
svgNodes.each(function(u) { addLabel(g.node(u), d3.select(this), 10, 10); });
this._transition(svgNodes.exit())
.style('opacity', 0)
.remove();
return svgNodes;
}
function defaultDrawEdgeLabels(g, root) {
var svgEdgeLabels = root
.selectAll('g.edgeLabel')
.classed('enter', false)
.data(g.edges(), function (e) { return e; });
svgEdgeLabels.selectAll('*').remove();
svgEdgeLabels
.enter()
.append('g')
.style('opacity', 0)
.attr('class', 'edgeLabel enter');
svgEdgeLabels.each(function(e) { addLabel(g.edge(e), d3.select(this), 0, 0); });
this._transition(svgEdgeLabels.exit())
.style('opacity', 0)
.remove();
return svgEdgeLabels;
}
var defaultDrawEdgePaths = function(g, root) {
var svgEdgePaths = root
.selectAll('g.edgePath')
.classed('enter', false)
.data(g.edges(), function(e) { return e; });
svgEdgePaths
.enter()
.append('g')
.attr('class', 'edgePath enter')
.append('path')
.style('opacity', 0)
.attr('marker-end', 'url(#arrowhead)');
this._transition(svgEdgePaths.exit())
.style('opacity', 0)
.remove();
return svgEdgePaths;
};
function defaultPositionNodes(g, svgNodes, svgNodesEnter) {
function transform(u) {
var value = g.node(u);
return 'translate(' + value.x + ',' + value.y + ')';
}
// For entering nodes, position immediately without transition
svgNodes.filter('.enter').attr('transform', transform);
this._transition(svgNodes)
.style('opacity', 1)
.attr('transform', transform);
}
function defaultPositionEdgeLabels(g, svgEdgeLabels) {
function transform(e) {
var value = g.edge(e);
var point = findMidPoint(value.points);
return 'translate(' + point.x + ',' + point.y + ')';
}
// For entering edge labels, position immediately without transition
svgEdgeLabels.filter('.enter').attr('transform', transform);
this._transition(svgEdgeLabels)
.style('opacity', 1)
.attr('transform', transform);
}
function defaultPositionEdgePaths(g, svgEdgePaths) {
var interpolate = this._edgeInterpolate,
tension = this._edgeTension;
function calcPoints(e) {
var value = g.edge(e);
var source = g.node(g.incidentNodes(e)[0]);
var target = g.node(g.incidentNodes(e)[1]);
var points = value.points.slice();
var p0 = points.length === 0 ? target : points[0];
var p1 = points.length === 0 ? source : points[points.length - 1];
points.unshift(intersectRect(source, p0));
// TODO: use bpodgursky's shortening algorithm here
points.push(intersectRect(target, p1));
return d3.svg.line()
.x(function(d) { return d.x; })
.y(function(d) { return d.y; })
.interpolate(interpolate)
.tension(tension)
(points);
}
svgEdgePaths.filter('.enter').selectAll('path')
.attr('d', calcPoints);
this._transition(svgEdgePaths.selectAll('path'))
.attr('d', calcPoints)
.style('opacity', 1);
}
// By default we do not use transitions
function defaultTransition(selection) {
return selection;
}
function defaultPostLayout() {
// Do nothing
}
function defaultPostRender(graph, root) {
if (graph.isDirected() && root.select('#arrowhead').empty()) {
root
.append('svg:defs')
.append('svg:marker')
.attr('id', 'arrowhead')
.attr('viewBox', '0 0 10 10')
.attr('refX', 8)
.attr('refY', 5)
.attr('markerUnits', 'strokewidth')
.attr('markerWidth', 8)
.attr('markerHeight', 5)
.attr('orient', 'auto')
.attr('style', 'fill: #333')
.append('svg:path')
.attr('d', 'M 0 0 L 10 5 L 0 10 z');
}
}
function addLabel(node, root, marginX, marginY) {
// Add the rect first so that it appears behind the label
var label = node.label;
var rect = root.append('rect');
var labelSvg = root.append('g');
if (label[0] === '<') {
addForeignObjectLabel(label, labelSvg);
// No margin for HTML elements
marginX = marginY = 0;
} else {
addTextLabel(label,
labelSvg,
Math.floor(node.labelCols),
node.labelCut);
}
var bbox = root.node().getBBox();
labelSvg.attr('transform',
'translate(' + (-bbox.width / 2) + ',' + (-bbox.height / 2) + ')');
rect
.attr('rx', 5)
.attr('ry', 5)
.attr('x', -(bbox.width / 2 + marginX))
.attr('y', -(bbox.height / 2 + marginY))
.attr('width', bbox.width + 2 * marginX)
.attr('height', bbox.height + 2 * marginY);
}
function addForeignObjectLabel(label, root) {
var fo = root
.append('foreignObject')
.attr('width', '100000');
var w, h;
fo
.append('xhtml:div')
.style('float', 'left')
// TODO find a better way to get dimensions for foreignObjects...
.html(function() { return label; })
.each(function() {
w = this.clientWidth;
h = this.clientHeight;
});
fo
.attr('width', w)
.attr('height', h);
}
function addTextLabel(label, root, labelCols, labelCut) {
if (labelCut === undefined) labelCut = "false";
labelCut = (labelCut.toString().toLowerCase() === "true");
var node = root
.append('text')
.attr('text-anchor', 'left');
label = label.replace(/\\n/g, "\n");
var arr = labelCols ? wordwrap(label, labelCols, labelCut) : label;
arr = arr.split("\n");
for (var i = 0; i < arr.length; i++) {
node
.append('tspan')
.attr('dy', '1em')
.attr('x', '1')
.text(arr[i]);
}
}
// Thanks to
function wordwrap (str, width, cut, brk) {
brk = brk || '\n';
width = width || 75;
cut = cut || false;
if (!str) { return str; }
var regex = '.{1,' +width+ '}(\\s|$)' + (cut ? '|.{' +width+ '}|.+$' : '|\\S+?(\\s|$)');
return str.match( RegExp(regex, 'g') ).join( brk );
}
function findMidPoint(points) {
var midIdx = points.length / 2;
if (points.length % 2) {
return points[Math.floor(midIdx)];
} else {
var p0 = points[midIdx - 1];
var p1 = points[midIdx];
return {x: (p0.x + p1.x) / 2, y: (p0.y + p1.y) / 2};
}
}
function intersectRect(rect, point) {
var x = rect.x;
var y = rect.y;
// For now we only support rectangles
// Rectangle intersection algorithm from:
var dx = point.x - x;
var dy = point.y - y;
var w = rect.width / 2;
var h = rect.height / 2;
var sx, sy;
if (Math.abs(dy) * w > Math.abs(dx) * h) {
// Intersection is top or bottom of rect.
if (dy < 0) {
h = -h;
}
sx = dy === 0 ? 0 : h * dx / dy;
sy = h;
} else {
// Intersection is left or right of rect.
if (dx < 0) {
w = -w;
}
sx = w;
sy = dx === 0 ? 0 : w * dy / dx;
}
return {x: x + sx, y: y + sy};
}
function isComposite(g, u) {
return 'children' in g && g.children(u).length;
}
function bind(func, thisArg) {
// For some reason PhantomJS occassionally fails when using the builtin bind,
// so we check if it is available and if not, use a degenerate polyfill.
if (func.bind) {
return func.bind(thisArg);
}
return function() {
return func.apply(thisArg, arguments);
};
}
},{"d3":10,"dagre":11}],4:[function(require,module,exports){
module.exports = '0.1.5';
},{}],5:[function(require,module,exports){
exports.Set = require('./lib/Set');
exports.PriorityQueue = require('./lib/PriorityQueue');
exports.version = require('./lib/version');
},{"./lib/PriorityQueue":6,"./lib/Set":7,"./lib/version":9}],6:[function(require,module,exports){
module.exports = PriorityQueue;
/**
* 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.
*/
function PriorityQueue() {
this._arr = [];
this._keyIndices = {};
}
/**
* Returns the number of elements in the queue. Takes `O(1)` time.
*/
PriorityQueue.prototype.size = function() {
return this._arr.length;
};
/**
* Returns the keys that are in the queue. Takes `O(n)` time.
*/
PriorityQueue.prototype.keys = function() {
return this._arr.map(function(x) { return x.key; });
};
/**
* Returns `true` if **key** is in the queue and `false` if not.
*/
PriorityQueue.prototype.has = function(key) {
return key in this._keyIndices;
};
/**
* 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
*/
PriorityQueue.prototype.priority = function(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.
*/
PriorityQueue.prototype.min = function() {
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
*/
PriorityQueue.prototype.add = function(key, priority) {
var keyIndices = this._keyIndices;
if (!(key in keyIndices)) {
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.
*/
PriorityQueue.prototype.removeMin = function() {
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
*/
PriorityQueue.prototype.decrease = function(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);
};
PriorityQueue.prototype._heapify = function(i) {
var arr = this._arr;
var l = 2 * i,
r = l + 1,
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);
}
}
};
PriorityQueue.prototype._decrease = function(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;
}
};
PriorityQueue.prototype._swap = function(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;
};
},{}],7:[function(require,module,exports){
var util = require('./util');
module.exports = Set;
/**
* Constructs a new Set with an optional set of `initialKeys`.
*
* It is important to note that keys are coerced to String for most purposes
* with this object, similar to the behavior of JavaScript's Object. For
* example, the following will add only one key:
*
* var s = new Set();
* s.add(1);
* s.add("1");
*
* However, the type of the key is preserved internally so that `keys` returns
* the original key set uncoerced. For the above example, `keys` would return
* `[1]`.
*/
function Set(initialKeys) {
this._size = 0;
this._keys = {};
if (initialKeys) {
for (var i = 0, il = initialKeys.length; i < il; ++i) {
this.add(initialKeys[i]);
}
}
}
/**
* Returns a new Set that represents the set intersection of the array of given
* sets.
*/
Set.intersect = function(sets) {
if (sets.length === 0) {
return new Set();
}
var result = new Set(!util.isArray(sets[0]) ? sets[0].keys() : sets[0]);
for (var i = 1, il = sets.length; i < il; ++i) {
var resultKeys = result.keys(),
other = !util.isArray(sets[i]) ? sets[i] : new Set(sets[i]);
for (var j = 0, jl = resultKeys.length; j < jl; ++j) {
var key = resultKeys[j];
if (!other.has(key)) {
result.remove(key);
}
}
}
return result;
};
/**
* Returns a new Set that represents the set union of the array of given sets.
*/
Set.union = function(sets) {
var totalElems = util.reduce(sets, function(lhs, rhs) {
return lhs + (rhs.size ? rhs.size() : rhs.length);
}, 0);
var arr = new Array(totalElems);
var k = 0;
for (var i = 0, il = sets.length; i < il; ++i) {
var cur = sets[i],
keys = !util.isArray(cur) ? cur.keys() : cur;
for (var j = 0, jl = keys.length; j < jl; ++j) {
arr[k++] = keys[j];
}
}
return new Set(arr);
};
/**
* Returns the size of this set in `O(1)` time.
*/
Set.prototype.size = function() {
return this._size;
};
/**
* Returns the keys in this set. Takes `O(n)` time.
*/
Set.prototype.keys = function() {
return values(this._keys);
};
/**
* Tests if a key is present in this Set. Returns `true` if it is and `false`
* if not. Takes `O(1)` time.
*/
Set.prototype.has = function(key) {
return key in this._keys;
};
/**
* Adds a new key to this Set if it is not already present. Returns `true` if
* the key was added and `false` if it was already present. Takes `O(1)` time.
*/
Set.prototype.add = function(key) {
if (!(key in this._keys)) {
this._keys[key] = key;
++this._size;
return true;
}
return false;
};
/**
* Removes a key from this Set. If the key was removed this function returns
* `true`. If not, it returns `false`. Takes `O(1)` time.
*/
Set.prototype.remove = function(key) {
if (key in this._keys) {
delete this._keys[key];
--this._size;
return true;
}
return false;
};
/*
* Returns an array of all values for properties of **o**.
*/
function values(o) {
var ks = Object.keys(o),
len = ks.length,
result = new Array(len),
i;
for (i = 0; i < len; ++i) {
result[i] = o[ks[i]];
}
return result;
}
},{"./util":8}],8:[function(require,module,exports){
/*
* This polyfill comes from
*/
if(!Array.isArray) {
exports.isArray = function (vArg) {
return Object.prototype.toString.call(vArg) === '[object Array]';
};
} else {
exports.isArray = Array.isArray;
}
/*
* Slightly adapted polyfill from
*/
if ('function' !== typeof Array.prototype.reduce) {
exports.reduce = function(array, callback, opt_initialValue) {
'use strict';
if (null === array || 'undefined' === typeof array) {
// At the moment all modern browsers, that support strict mode, have
// native implementation of Array.prototype.reduce. For instance, IE8
// does not support strict mode, so this check is actually useless.
throw new TypeError(
'Array.prototype.reduce called on null or undefined');
}
if ('function' !== typeof callback) {
throw new TypeError(callback + ' is not a function');
}
var index, value,
length = array.length >>> 0,
isValueSet = false;
if (1 < arguments.length) {
value = opt_initialValue;
isValueSet = true;
}
for (index = 0; length > index; ++index) {
if (array.hasOwnProperty(index)) {
if (isValueSet) {
value = callback(value, array[index], index, array);
}
else {
value = array[index];
isValueSet = true;
}
}
}
if (!isValueSet) {
throw new TypeError('Reduce of empty array with no initial value');
}
return value;
};
} else {
exports.reduce = function(array, callback, opt_initialValue) {
return array.reduce(callback, opt_initialValue);
};
}
},{}],9:[function(require,module,exports){
module.exports = '1.1.3';
},{}],10:[function(require,module,exports){
require("./d3");
module.exports = d3;
(function () { delete this.d3; })(); // unset global
},{}],11:[function(require,module,exports){
/*
Copyright (c) 2012-2013 Chris Pettitt
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
exports.Digraph = require("graphlib").Digraph;
exports.Graph = require("graphlib").Graph;
exports.layout = require("./lib/layout");
exports.version = require("./lib/version");
},{"./lib/layout":12,"./lib/version":27,"graphlib":28}],12:[function(require,module,exports){
var util = require('./util'),
rank = require('./rank'),
order = require('./order'),
CGraph = require('graphlib').CGraph,
CDigraph = require('graphlib').CDigraph;
module.exports = function() {
// External configuration
var config = {
// How much debug information to include?
debugLevel: 0,
// Max number of sweeps to perform in order phase
orderMaxSweeps: order.DEFAULT_MAX_SWEEPS,
// Use network simplex algorithm in ranking
rankSimplex: false,
// Rank direction. Valid values are (TB, LR)
rankDir: 'TB'
};
// Phase functions
var position = require('./position')();
// This layout object
var self = {};
self.orderIters = util.propertyAccessor(self, config, 'orderMaxSweeps');
self.rankSimplex = util.propertyAccessor(self, config, 'rankSimplex');
self.nodeSep = delegateProperty(position.nodeSep);
self.edgeSep = delegateProperty(position.edgeSep);
self.universalSep = delegateProperty(position.universalSep);
self.rankSep = delegateProperty(position.rankSep);
self.rankDir = util.propertyAccessor(self, config, 'rankDir');
self.debugAlignment = delegateProperty(position.debugAlignment);
self.debugLevel = util.propertyAccessor(self, config, 'debugLevel', function(x) {
util.log.level = x;
position.debugLevel(x);
});
self.run = util.time('Total layout', run);
self._normalize = normalize;
return self;
/*
* Constructs an adjacency graph using the nodes and edges specified through
* config. For each node and edge we add a property `dagre` that contains an
* object that will hold intermediate and final layout information. Some of
* the contents include:
*
* 1) A generated ID that uniquely identifies the object.
* 2) Dimension information for nodes (copied from the source node).
* 3) Optional dimension information for edges.
*
* After the adjacency graph is constructed the code no longer needs to use
* the original nodes and edges passed in via config.
*/
function initLayoutGraph(inputGraph) {
var g = new CDigraph();
inputGraph.eachNode(function(u, value) {
if (value === undefined) value = {};
g.addNode(u, {
width: value.width,
height: value.height
});
if (value.hasOwnProperty('rank')) {
g.node(u).prefRank = value.rank;
}
});
// Set up subgraphs
if (inputGraph.parent) {
inputGraph.nodes().forEach(function(u) {
g.parent(u, inputGraph.parent(u));
});
}
inputGraph.eachEdge(function(e, u, v, value) {
if (value === undefined) value = {};
var newValue = {
e: e,
minLen: value.minLen || 1,
width: value.width || 0,
height: value.height || 0,
points: []
};
g.addEdge(null, u, v, newValue);
});
// Initial graph attributes
var graphValue = inputGraph.graph() || {};
g.graph({
rankDir: graphValue.rankDir || config.rankDir,
orderRestarts: graphValue.orderRestarts
});
return g;
}
function run(inputGraph) {
var rankSep = self.rankSep();
var g;
try {
// Build internal graph
g = util.time('initLayoutGraph', initLayoutGraph)(inputGraph);
if (g.order() === 0) {
return g;
}
// Make space for edge labels
g.eachEdge(function(e, s, t, a) {
a.minLen *= 2;
});
self.rankSep(rankSep / 2);
// Determine the rank for each node. Nodes with a lower rank will appear
// above nodes of higher rank.
util.time('rank.run', rank.run)(g, config.rankSimplex);
// Normalize the graph by ensuring that every edge is proper (each edge has
// a length of 1). We achieve this by adding dummy nodes to long edges,
// thus shortening them.
util.time('normalize', normalize)(g);
// Order the nodes so that edge crossings are minimized.
util.time('order', order)(g, config.orderMaxSweeps);
// Find the x and y coordinates for every node in the graph.
util.time('position', position.run)(g);
// De-normalize the graph by removing dummy nodes and augmenting the
// original long edges with coordinate information.
util.time('undoNormalize', undoNormalize)(g);
// Reverses points for edges that are in a reversed state.
util.time('fixupEdgePoints', fixupEdgePoints)(g);
// Restore delete edges and reverse edges that were reversed in the rank
// phase.
util.time('rank.restoreEdges', rank.restoreEdges)(g);
// Construct final result graph and return it
return util.time('createFinalGraph', createFinalGraph)(g, inputGraph.isDirected());
} finally {
self.rankSep(rankSep);
}
}
/*
* This function is responsible for 'normalizing' the graph. The process of
* normalization ensures that no edge in the graph has spans more than one
* rank. To do this it inserts dummy nodes as needed and links them by adding
* dummy edges. This function keeps enough information in the dummy nodes and
* edges to ensure that the original graph can be reconstructed later.
*
* This method assumes that the input graph is cycle free.
*/
function normalize(g) {
var dummyCount = 0;
g.eachEdge(function(e, s, t, a) {
var sourceRank = g.node(s).rank;
var targetRank = g.node(t).rank;
if (sourceRank + 1 < targetRank) {
for (var u = s, rank = sourceRank + 1, i = 0; rank < targetRank; ++rank, ++i) {
var v = '_D' + (++dummyCount);
var node = {
width: a.width,
height: a.height,
edge: { id: e, source: s, target: t, attrs: a },
rank: rank,
dummy: true
};
// If this node represents a bend then we will use it as a control
// point. For edges with 2 segments this will be the center dummy
// node. For edges with more than two segments, this will be the
// first and last dummy node.
if (i === 0) node.index = 0;
else if (rank + 1 === targetRank) node.index = 1;
g.addNode(v, node);
g.addEdge(null, u, v, {});
u = v;
}
g.addEdge(null, u, t, {});
g.delEdge(e);
}
});
}
/*
* Reconstructs the graph as it was before normalization. The positions of
* dummy nodes are used to build an array of points for the original 'long'
* edge. Dummy nodes and edges are removed.
*/
function undoNormalize(g) {
g.eachNode(function(u, a) {
if (a.dummy) {
if ('index' in a) {
var edge = a.edge;
if (!g.hasEdge(edge.id)) {
g.addEdge(edge.id, edge.source, edge.target, edge.attrs);
}
var points = g.edge(edge.id).points;
points[a.index] = { x: a.x, y: a.y, ul: a.ul, ur: a.ur, dl: a.dl, dr: a.dr };
}
g.delNode(u);
}
});
}
/*
* For each edge that was reversed during the `acyclic` step, reverse its
* array of points.
*/
function fixupEdgePoints(g) {
g.eachEdge(function(e, s, t, a) { if (a.reversed) a.points.reverse(); });
}
function createFinalGraph(g, isDirected) {
var out = isDirected ? new CDigraph() : new CGraph();
out.graph(g.graph());
g.eachNode(function(u, value) { out.addNode(u, value); });
g.eachNode(function(u) { out.parent(u, g.parent(u)); });
g.eachEdge(function(e, u, v, value) {
out.addEdge(value.e, u, v, value);
});
// Attach bounding box information
var maxX = 0, maxY = 0;
g.eachNode(function(u, value) {
if (!g.children(u).length) {
maxX = Math.max(maxX, value.x + value.width / 2);
maxY = Math.max(maxY, value.y + value.height / 2);
}
});
g.eachEdge(function(e, u, v, value) {
var maxXPoints = Math.max.apply(Math, value.points.map(function(p) { return p.x; }));
var maxYPoints = Math.max.apply(Math, value.points.map(function(p) { return p.y; }));
maxX = Math.max(maxX, maxXPoints + value.width / 2);
maxY = Math.max(maxY, maxYPoints + value.height / 2);
});
out.graph().width = maxX;
out.graph().height = maxY;
return out;
}
/*
* Given a function, a new function is returned that invokes the given
* function. The return value from the function is always the `self` object.
*/
function delegateProperty(f) {
return function() {
if (!arguments.length) return f();
f.apply(null, arguments);
return self;
};
}
};
},{"./order":13,"./position":18,"./rank":19,"./util":26,"graphlib":28}],13:[function(require,module,exports){
var util = require('./util'),
crossCount = require('./order/crossCount'),
initLayerGraphs = require('./order/initLayerGraphs'),
initOrder = require('./order/initOrder'),
sortLayer = require('./order/sortLayer');
module.exports = order;
// The maximum number of sweeps to perform before finishing the order phase.
var DEFAULT_MAX_SWEEPS = 24;
order.DEFAULT_MAX_SWEEPS = DEFAULT_MAX_SWEEPS;
/*
* Runs the order phase with the specified `graph, `maxSweeps`, and
* `debugLevel`. If `maxSweeps` is not specified we use `DEFAULT_MAX_SWEEPS`.
* If `debugLevel` is not set we assume 0.
*/
function order(g, maxSweeps) {
if (arguments.length < 2) {
maxSweeps = DEFAULT_MAX_SWEEPS;
}
var restarts = g.graph().orderRestarts || 0;
var layerGraphs = initLayerGraphs(g);
// TODO: remove this when we add back support for ordering clusters
layerGraphs.forEach(function(lg) {
lg = lg.filterNodes(function(u) { return !g.children(u).length; });
});
var iters = 0,
currentBestCC,
allTimeBestCC = Number.MAX_VALUE,
allTimeBest = {};
function saveAllTimeBest() {
g.eachNode(function(u, value) { allTimeBest[u] = value.order; });
}
for (var j = 0; j < Number(restarts) + 1 && allTimeBestCC !== 0; ++j) {
currentBestCC = Number.MAX_VALUE;
initOrder(g, restarts > 0);
util.log(2, 'Order phase start cross count: ' + g.graph().orderInitCC);
var i, lastBest, cc;
for (i = 0, lastBest = 0; lastBest < 4 && i < maxSweeps && currentBestCC > 0; ++i, ++lastBest, ++iters) {
sweep(g, layerGraphs, i);
cc = crossCount(g);
if (cc < currentBestCC) {
lastBest = 0;
currentBestCC = cc;
if (cc < allTimeBestCC) {
saveAllTimeBest();
allTimeBestCC = cc;
}
}
util.log(3, 'Order phase start ' + j + ' iter ' + i + ' cross count: ' + cc);
}
}
Object.keys(allTimeBest).forEach(function(u) {
if (!g.children || !g.children(u).length) {
g.node(u).order = allTimeBest[u];
}
});
g.graph().orderCC = allTimeBestCC;
util.log(2, 'Order iterations: ' + iters);
util.log(2, 'Order phase best cross count: ' + g.graph().orderCC);
}
function predecessorWeights(g, nodes) {
var weights = {};
nodes.forEach(function(u) {
weights[u] = g.inEdges(u).map(function(e) {
return g.node(g.source(e)).order;
});
});
return weights;
}
function successorWeights(g, nodes) {
var weights = {};
nodes.forEach(function(u) {
weights[u] = g.outEdges(u).map(function(e) {
return g.node(g.target(e)).order;
});
});
return weights;
}
function sweep(g, layerGraphs, iter) {
if (iter % 2 === 0) {
sweepDown(g, layerGraphs, iter);
} else {
sweepUp(g, layerGraphs, iter);
}
}
function sweepDown(g, layerGraphs) {
var cg;
for (i = 1; i < layerGraphs.length; ++i) {
cg = sortLayer(layerGraphs[i], cg, predecessorWeights(g, layerGraphs[i].nodes()));
}
}
function sweepUp(g, layerGraphs) {
var cg;
for (i = layerGraphs.length - 2; i >= 0; --i) {
sortLayer(layerGraphs[i], cg, successorWeights(g, layerGraphs[i].nodes()));
}
}
},{"./order/crossCount":14,"./order/initLayerGraphs":15,"./order/initOrder":16,"./order/sortLayer":17,"./util":26}],14:[function(require,module,exports){
var util = require('../util');
module.exports = crossCount;
/*
* Returns the cross count for the given graph.
*/
function crossCount(g) {
var cc = 0;
var ordering = util.ordering(g);
for (var i = 1; i < ordering.length; ++i) {
cc += twoLayerCrossCount(g, ordering[i-1], ordering[i]);
}
return cc;
}
/*
* This function searches through a ranked and ordered graph and counts the
* number of edges that cross. This algorithm is derived from:
*
* W. Barth et al., Bilayer Cross Counting, JGAA, 8(2) 179–194 (2004)
*/
function twoLayerCrossCount(g, layer1, layer2) {
var indices = [];
layer1.forEach(function(u) {
var nodeIndices = [];
g.outEdges(u).forEach(function(e) { nodeIndices.push(g.node(g.target(e)).order); });
nodeIndices.sort(function(x, y) { return x - y; });
indices = indices.concat(nodeIndices);
});
var firstIndex = 1;
while (firstIndex < layer2.length) firstIndex <<= 1;
var treeSize = 2 * firstIndex - 1;
firstIndex -= 1;
var tree = [];
for (var i = 0; i < treeSize; ++i) { tree[i] = 0; }
var cc = 0;
indices.forEach(function(i) {
var treeIndex = i + firstIndex;
++tree[treeIndex];
while (treeIndex > 0) {
if (treeIndex % 2) {
cc += tree[treeIndex + 1];
}
treeIndex = (treeIndex - 1) >> 1;
++tree[treeIndex];
}
});
return cc;
}
},{"../util":26}],15:[function(require,module,exports){
var nodesFromList = require('graphlib').filter.nodesFromList,
/* jshint -W079 */
Set = require('cp-data').Set;
module.exports = initLayerGraphs;
/*
* This function takes a compound layered graph, g, and produces an array of
* layer graphs. Each entry in the array represents a subgraph of nodes
* relevant for performing crossing reduction on that layer.
*/
function initLayerGraphs(g) {
var ranks = [];
function dfs(u) {
if (u === null) {
g.children(u).forEach(function(v) { dfs(v); });
return;
}
var value = g.node(u);
value.minRank = ('rank' in value) ? value.rank : Number.MAX_VALUE;
value.maxRank = ('rank' in value) ? value.rank : Number.MIN_VALUE;
var uRanks = new Set();
g.children(u).forEach(function(v) {
var rs = dfs(v);
uRanks = Set.union([uRanks, rs]);
value.minRank = Math.min(value.minRank, g.node(v).minRank);
value.maxRank = Math.max(value.maxRank, g.node(v).maxRank);
});
if ('rank' in value) uRanks.add(value.rank);
uRanks.keys().forEach(function(r) {
if (!(r in ranks)) ranks[r] = [];
ranks[r].push(u);
});
return uRanks;
}
dfs(null);
var layerGraphs = [];
ranks.forEach(function(us, rank) {
layerGraphs[rank] = g.filterNodes(nodesFromList(us));
});
return layerGraphs;
}
},{"cp-data":5,"graphlib":28}],16:[function(require,module,exports){
var crossCount = require('./crossCount'),
util = require('../util');
module.exports = initOrder;
/*
* Given a graph with a set of layered nodes (i.e. nodes that have a `rank`
* attribute) this function attaches an `order` attribute that uniquely
* arranges each node of each rank. If no constraint graph is provided the
* order of the nodes in each rank is entirely arbitrary.
*/
function initOrder(g, random) {
var layers = [];
g.eachNode(function(u, value) {
var layer = layers[value.rank];
if (g.children && g.children(u).length > 0) return;
if (!layer) {
layer = layers[value.rank] = [];
}
layer.push(u);
});
layers.forEach(function(layer) {
if (random) {
util.shuffle(layer);
}
layer.forEach(function(u, i) {
g.node(u).order = i;
});
});
var cc = crossCount(g);
g.graph().orderInitCC = cc;
g.graph().orderCC = Number.MAX_VALUE;
}
},{"../util":26,"./crossCount":14}],17:[function(require,module,exports){
var util = require('../util');
/*
Digraph = require('graphlib').Digraph,
topsort = require('graphlib').alg.topsort,
nodesFromList = require('graphlib').filter.nodesFromList;
*/
module.exports = sortLayer;
/*
function sortLayer(g, cg, weights) {
var result = sortLayerSubgraph(g, null, cg, weights);
result.list.forEach(function(u, i) {
g.node(u).order = i;
});
return result.constraintGraph;
}
*/
function sortLayer(g, cg, weights) {
var ordering = [];
var bs = {};
g.eachNode(function(u, value) {
ordering[value.order] = u;
var ws = weights[u];
if (ws.length) {
bs[u] = util.sum(ws) / ws.length;
}
});
var toSort = g.nodes().filter(function(u) { return bs[u] !== undefined; });
toSort.sort(function(x, y) {
return bs[x] - bs[y] || g.node(x).order - g.node(y).order;
});
for (var i = 0, j = 0, jl = toSort.length; j < jl; ++i) {
if (bs[ordering[i]] !== undefined) {
g.node(toSort[j++]).order = i;
}
}
}
// TOOD: re-enable constrained sorting once we have a strategy for handling
// undefined barycenters.
/*
function sortLayerSubgraph(g, sg, cg, weights) {
cg = cg ? cg.filterNodes(nodesFromList(g.children(sg))) : new Digraph();
var nodeData = {};
g.children(sg).forEach(function(u) {
if (g.children(u).length) {
nodeData[u] = sortLayerSubgraph(g, u, cg, weights);
nodeData[u].firstSG = u;
nodeData[u].lastSG = u;
} else {
var ws = weights[u];
nodeData[u] = {
degree: ws.length,
barycenter: ws.length > 0 ? util.sum(ws) / ws.length : 0,
list: [u]
};
}
});
resolveViolatedConstraints(g, cg, nodeData);
var keys = Object.keys(nodeData);
keys.sort(function(x, y) {
return nodeData[x].barycenter - nodeData[y].barycenter;
});
var result = keys.map(function(u) { return nodeData[u]; })
.reduce(function(lhs, rhs) { return mergeNodeData(g, lhs, rhs); });
return result;
}
/*
function mergeNodeData(g, lhs, rhs) {
var cg = mergeDigraphs(lhs.constraintGraph, rhs.constraintGraph);
if (lhs.lastSG !== undefined && rhs.firstSG !== undefined) {
if (cg === undefined) {
cg = new Digraph();
}
if (!cg.hasNode(lhs.lastSG)) { cg.addNode(lhs.lastSG); }
cg.addNode(rhs.firstSG);
cg.addEdge(null, lhs.lastSG, rhs.firstSG);
}
return {
degree: lhs.degree + rhs.degree,
barycenter: (lhs.barycenter * lhs.degree + rhs.barycenter * rhs.degree) /
(lhs.degree + rhs.degree),
list: lhs.list.concat(rhs.list),
firstSG: lhs.firstSG !== undefined ? lhs.firstSG : rhs.firstSG,
lastSG: rhs.lastSG !== undefined ? rhs.lastSG : lhs.lastSG,
constraintGraph: cg
};
}
function mergeDigraphs(lhs, rhs) {
if (lhs === undefined) return rhs;
if (rhs === undefined) return lhs;
lhs = lhs.copy();
rhs.nodes().forEach(function(u) { lhs.addNode(u); });
rhs.edges().forEach(function(e, u, v) { lhs.addEdge(null, u, v); });
return lhs;
}
function resolveViolatedConstraints(g, cg, nodeData) {
// Removes nodes `u` and `v` from `cg` and makes any edges incident on them
// incident on `w` instead.
function collapseNodes(u, v, w) {
// TODO original paper removes self loops, but it is not obvious when this would happen
cg.inEdges(u).forEach(function(e) {
cg.delEdge(e);
cg.addEdge(null, cg.source(e), w);
});
cg.outEdges(v).forEach(function(e) {
cg.delEdge(e);
cg.addEdge(null, w, cg.target(e));
});
cg.delNode(u);
cg.delNode(v);
}
var violated;
while ((violated = findViolatedConstraint(cg, nodeData)) !== undefined) {
var source = cg.source(violated),
target = cg.target(violated);
var v;
while ((v = cg.addNode(null)) && g.hasNode(v)) {
cg.delNode(v);
}
// Collapse barycenter and list
nodeData[v] = mergeNodeData(g, nodeData[source], nodeData[target]);
delete nodeData[source];
delete nodeData[target];
collapseNodes(source, target, v);
if (cg.incidentEdges(v).length === 0) { cg.delNode(v); }
}
}
function findViolatedConstraint(cg, nodeData) {
var us = topsort(cg);
for (var i = 0; i < us.length; ++i) {
var u = us[i];
var inEdges = cg.inEdges(u);
for (var j = 0; j < inEdges.length; ++j) {
var e = inEdges[j];
if (nodeData[cg.source(e)].barycenter >= nodeData[u].barycenter) {
return e;
}
}
}
}
*/
},{"../util":26}],18:[function(require,module,exports){
var util = require('./util');
/*
* The algorithms here are based on Brandes and Köpf, "Fast and Simple
* Horizontal Coordinate Assignment".
*/
module.exports = function() {
// External configuration
var config = {
nodeSep: 50,
edgeSep: 10,
universalSep: null,
rankSep: 30
};
var self = {};
self.nodeSep = util.propertyAccessor(self, config, 'nodeSep');
self.edgeSep = util.propertyAccessor(self, config, 'edgeSep');
// If not null this separation value is used for all nodes and edges
// regardless of their widths. `nodeSep` and `edgeSep` are ignored with this
// option.
self.universalSep = util.propertyAccessor(self, config, 'universalSep');
self.rankSep = util.propertyAccessor(self, config, 'rankSep');
self.debugLevel = util.propertyAccessor(self, config, 'debugLevel');
self.run = run;
return self;
function run(g) {
g = g.filterNodes(util.filterNonSubgraphs(g));
var layering = util.ordering(g);
var conflicts = findConflicts(g, layering);
var xss = {};
['u', 'd'].forEach(function(vertDir) {
if (vertDir === 'd') layering.reverse();
['l', 'r'].forEach(function(horizDir) {
if (horizDir === 'r') reverseInnerOrder(layering);
var dir = vertDir + horizDir;
var align = verticalAlignment(g, layering, conflicts, vertDir === 'u' ? 'predecessors' : 'successors');
xss[dir]= horizontalCompaction(g, layering, align.pos, align.root, align.align);
if (config.debugLevel >= 3)
debugPositioning(vertDir + horizDir, g, layering, xss[dir]);
if (horizDir === 'r') flipHorizontally(xss[dir]);
if (horizDir === 'r') reverseInnerOrder(layering);
});
if (vertDir === 'd') layering.reverse();
});
balance(g, layering, xss);
g.eachNode(function(v) {
var xs = [];
for (var alignment in xss) {
var alignmentX = xss[alignment][v];
posXDebug(alignment, g, v, alignmentX);
xs.push(alignmentX);
}
xs.sort(function(x, y) { return x - y; });
posX(g, v, (xs[1] + xs[2]) / 2);
});
// Align y coordinates with ranks
var y = 0, reverseY = g.graph().rankDir === 'BT' || g.graph().rankDir === 'RL';
layering.forEach(function(layer) {
var maxHeight = util.max(layer.map(function(u) { return height(g, u); }));
y += maxHeight / 2;
layer.forEach(function(u) {
posY(g, u, reverseY ? -y : y);
});
y += maxHeight / 2 + config.rankSep;
});
// Translate layout so that top left corner of bounding rectangle has
// coordinate (0, 0).
var minX = util.min(g.nodes().map(function(u) { return posX(g, u) - width(g, u) / 2; }));
var minY = util.min(g.nodes().map(function(u) { return posY(g, u) - height(g, u) / 2; }));
g.eachNode(function(u) {
posX(g, u, posX(g, u) - minX);
posY(g, u, posY(g, u) - minY);
});
}
/*
* Generate an ID that can be used to represent any undirected edge that is
* incident on `u` and `v`.
*/
function undirEdgeId(u, v) {
return u < v
? u.toString().length + ':' + u + '-' + v
: v.toString().length + ':' + v + '-' + u;
}
function findConflicts(g, layering) {
var conflicts = {}, // Set of conflicting edge ids
pos = {}, // Position of node in its layer
prevLayer,
currLayer,
k0, // Position of the last inner segment in the previous layer
l, // Current position in the current layer (for iteration up to `l1`)
k1; // Position of the next inner segment in the previous layer or
// the position of the last element in the previous layer
if (layering.length <= 2) return conflicts;
function updateConflicts(v) {
var k = pos[v];
if (k < k0 || k > k1) {
conflicts[undirEdgeId(currLayer[l], v)] = true;
}
}
layering[1].forEach(function(u, i) { pos[u] = i; });
for (var i = 1; i < layering.length - 1; ++i) {
prevLayer = layering[i];
currLayer = layering[i+1];
k0 = 0;
l = 0;
// Scan current layer for next node that is incident to an inner segement
// between layering[i+1] and layering[i].
for (var l1 = 0; l1 < currLayer.length; ++l1) {
var u = currLayer[l1]; // Next inner segment in the current layer or
// last node in the current layer
pos[u] = l1;
k1 = undefined;
if (g.node(u).dummy) {
var uPred = g.predecessors(u)[0];
// Note: In the case of self loops and sideways edges it is possible
// for a dummy not to have a predecessor.
if (uPred !== undefined && g.node(uPred).dummy)
k1 = pos[uPred];
}
if (k1 === undefined && l1 === currLayer.length - 1)
k1 = prevLayer.length - 1;
if (k1 !== undefined) {
for (; l <= l1; ++l) {
g.predecessors(currLayer[l]).forEach(updateConflicts);
}
k0 = k1;
}
}
}
return conflicts;
}
function verticalAlignment(g, layering, conflicts, relationship) {
var pos = {}, // Position for a node in its layer
root = {}, // Root of the block that the node participates in
align = {}; // Points to the next node in the block or, if the last
// element in the block, points to the first block's root
layering.forEach(function(layer) {
layer.forEach(function(u, i) {
root[u] = u;
align[u] = u;
pos[u] = i;
});
});
layering.forEach(function(layer) {
var prevIdx = -1;
layer.forEach(function(v) {
var related = g[relationship](v), // Adjacent nodes from the previous layer
mid; // The mid point in the related array
if (related.length > 0) {
related.sort(function(x, y) { return pos[x] - pos[y]; });
mid = (related.length - 1) / 2;
related.slice(Math.floor(mid), Math.ceil(mid) + 1).forEach(function(u) {
if (align[v] === v) {
if (!conflicts[undirEdgeId(u, v)] && prevIdx < pos[u]) {
align[u] = v;
align[v] = root[v] = root[u];
prevIdx = pos[u];
}
}
});
}
});
});
return { pos: pos, root: root, align: align };
}
// This function deviates from the standard BK algorithm in two ways. First
// it takes into account the size of the nodes. Second it includes a fix to
// the original algorithm that is described in Carstens, "Node and Label
// Placement in a Layered Layout Algorithm".
function horizontalCompaction(g, layering, pos, root, align) {
var sink = {}, // Mapping of node id -> sink node id for class
maybeShift = {}, // Mapping of sink node id -> { class node id, min shift }
shift = {}, // Mapping of sink node id -> shift
pred = {}, // Mapping of node id -> predecessor node (or null)
xs = {}; // Calculated X positions
layering.forEach(function(layer) {
layer.forEach(function(u, i) {
sink[u] = u;
maybeShift[u] = {};
if (i > 0)
pred[u] = layer[i - 1];
});
});
function updateShift(toShift, neighbor, delta) {
if (!(neighbor in maybeShift[toShift])) {
maybeShift[toShift][neighbor] = delta;
} else {
maybeShift[toShift][neighbor] = Math.min(maybeShift[toShift][neighbor], delta);
}
}
function placeBlock(v) {
if (!(v in xs)) {
xs[v] = 0;
var w = v;
do {
if (pos[w] > 0) {
var u = root[pred[w]];
placeBlock(u);
if (sink[v] === v) {
sink[v] = sink[u];
}
var delta = sep(g, pred[w]) + sep(g, w);
if (sink[v] !== sink[u]) {
updateShift(sink[u], sink[v], xs[v] - xs[u] - delta);
} else {
xs[v] = Math.max(xs[v], xs[u] + delta);
}
}
w = align[w];
} while (w !== v);
}
}
// Root coordinates relative to sink
util.values(root).forEach(function(v) {
placeBlock(v);
});
// Absolute coordinates
// There is an assumption here that we've resolved shifts for any classes
// that begin at an earlier layer. We guarantee this by visiting layers in
// order.
layering.forEach(function(layer) {
layer.forEach(function(v) {
xs[v] = xs[root[v]];
if (v === root[v] && v === sink[v]) {
var minShift = 0;
if (v in maybeShift && Object.keys(maybeShift[v]).length > 0) {
minShift = util.min(Object.keys(maybeShift[v])
.map(function(u) {
return maybeShift[v][u] + (u in shift ? shift[u] : 0);
}
));
}
shift[v] = minShift;
}
});
});
layering.forEach(function(layer) {
layer.forEach(function(v) {
xs[v] += shift[sink[root[v]]] || 0;
});
});
return xs;
}
function findMinCoord(g, layering, xs) {
return util.min(layering.map(function(layer) {
var u = layer[0];
return xs[u];
}));
}
function findMaxCoord(g, layering, xs) {
return util.max(layering.map(function(layer) {
var u = layer[layer.length - 1];
return xs[u];
}));
}
function balance(g, layering, xss) {
var min = {}, // Min coordinate for the alignment
max = {}, // Max coordinate for the alginment
smallestAlignment,
shift = {}; // Amount to shift a given alignment
function updateAlignment(v) {
xss[alignment][v] += shift[alignment];
}
var smallest = Number.POSITIVE_INFINITY;
for (var alignment in xss) {
var xs = xss[alignment];
min[alignment] = findMinCoord(g, layering, xs);
max[alignment] = findMaxCoord(g, layering, xs);
var w = max[alignment] - min[alignment];
if (w < smallest) {
smallest = w;
smallestAlignment = alignment;
}
}
// Determine how much to adjust positioning for each alignment
['u', 'd'].forEach(function(vertDir) {
['l', 'r'].forEach(function(horizDir) {
var alignment = vertDir + horizDir;
shift[alignment] = horizDir === 'l'
? min[smallestAlignment] - min[alignment]
: max[smallestAlignment] - max[alignment];
});
});
// Find average of medians for xss array
for (alignment in xss) {
g.eachNode(updateAlignment);
}
}
function flipHorizontally(xs) {
for (var u in xs) {
xs[u] = -xs[u];
}
}
function reverseInnerOrder(layering) {
layering.forEach(function(layer) {
layer.reverse();
});
}
function width(g, u) {
switch (g.graph().rankDir) {
case 'LR': return g.node(u).height;
case 'RL': return g.node(u).height;
default: return g.node(u).width;
}
}
function height(g, u) {
switch(g.graph().rankDir) {
case 'LR': return g.node(u).width;
case 'RL': return g.node(u).width;
default: return g.node(u).height;
}
}
function sep(g, u) {
if (config.universalSep !== null) {
return config.universalSep;
}
var w = width(g, u);
var s = g.node(u).dummy ? config.edgeSep : config.nodeSep;
return (w + s) / 2;
}
function posX(g, u, x) {
if (g.graph().rankDir === 'LR' || g.graph().rankDir === 'RL') {
if (arguments.length < 3) {
return g.node(u).y;
} else {
g.node(u).y = x;
}
} else {
if (arguments.length < 3) {
return g.node(u).x;
} else {
g.node(u).x = x;
}
}
}
function posXDebug(name, g, u, x) {
if (g.graph().rankDir === 'LR' || g.graph().rankDir === 'RL') {
if (arguments.length < 3) {
return g.node(u)[name];
} else {
g.node(u)[name] = x;
}
} else {
if (arguments.length < 3) {
return g.node(u)[name];
} else {
g.node(u)[name] = x;
}
}
}
function posY(g, u, y) {
if (g.graph().rankDir === 'LR' || g.graph().rankDir === 'RL') {
if (arguments.length < 3) {
return g.node(u).x;
} else {
g.node(u).x = y;
}
} else {
if (arguments.length < 3) {
return g.node(u).y;
} else {
g.node(u).y = y;
}
}
}
function debugPositioning(align, g, layering, xs) {
layering.forEach(function(l, li) {
var u, xU;
l.forEach(function(v) {
var xV = xs[v];
if (u) {
var s = sep(g, u) + sep(g, v);
if (xV - xU < s)
console.log('Position phase: sep violation. Align: ' + align + '. Layer: ' + li + '. ' +
'U: ' + u + ' V: ' + v + '. Actual sep: ' + (xV - xU) + ' Expected sep: ' + s);
}
u = v;
xU = xV;
});
});
}
};
},{"./util":26}],19:[function(require,module,exports){
var util = require('./util'),
acyclic = require('./rank/acyclic'),
initRank = require('./rank/initRank'),
feasibleTree = require('./rank/feasibleTree'),
constraints = require('./rank/constraints'),
simplex = require('./rank/simplex'),
components = require('graphlib').alg.components,
filter = require('graphlib').filter;
exports.run = run;
exports.restoreEdges = restoreEdges;
/*
* Heuristic function that assigns a rank to each node of the input graph with
* the intent of minimizing edge lengths, while respecting the `minLen`
* attribute of incident edges.
*
* Prerequisites:
*
* * Each edge in the input graph must have an assigned 'minLen' attribute
*/
function run(g, useSimplex) {
expandSelfLoops(g);
// If there are rank constraints on nodes, then build a new graph that
// encodes the constraints.
util.time('constraints.apply', constraints.apply)(g);
expandSidewaysEdges(g);
// Reverse edges to get an acyclic graph, we keep the graph in an acyclic
// state until the very end.
util.time('acyclic', acyclic)(g);
// Convert the graph into a flat graph for ranking
var flatGraph = g.filterNodes(util.filterNonSubgraphs(g));
// Assign an initial ranking using DFS.
initRank(flatGraph);
// For each component improve the assigned ranks.
components(flatGraph).forEach(function(cmpt) {
var subgraph = flatGraph.filterNodes(filter.nodesFromList(cmpt));
rankComponent(subgraph, useSimplex);
});
// Relax original constraints
util.time('constraints.relax', constraints.relax(g));
// When handling nodes with constrained ranks it is possible to end up with
// edges that point to previous ranks. Most of the subsequent algorithms assume
// that edges are pointing to successive ranks only. Here we reverse any "back
// edges" and mark them as such. The acyclic algorithm will reverse them as a
// post processing step.
util.time('reorientEdges', reorientEdges)(g);
}
function restoreEdges(g) {
acyclic.undo(g);
}
/*
* Expand self loops into three dummy nodes. One will sit above the incident
* node, one will be at the same level, and one below. The result looks like:
*
* /--<--x--->--\
* node y
* \--<--z--->--/
*
* Dummy nodes x, y, z give us the shape of a loop and node y is where we place
* the label.
*
* TODO: consolidate knowledge of dummy node construction.
* TODO: support minLen = 2
*/
function expandSelfLoops(g) {
g.eachEdge(function(e, u, v, a) {
if (u === v) {
var x = addDummyNode(g, e, u, v, a, 0, false),
y = addDummyNode(g, e, u, v, a, 1, true),
z = addDummyNode(g, e, u, v, a, 2, false);
g.addEdge(null, x, u, {minLen: 1, selfLoop: true});
g.addEdge(null, x, y, {minLen: 1, selfLoop: true});
g.addEdge(null, u, z, {minLen: 1, selfLoop: true});
g.addEdge(null, y, z, {minLen: 1, selfLoop: true});
g.delEdge(e);
}
});
}
function expandSidewaysEdges(g) {
g.eachEdge(function(e, u, v, a) {
if (u === v) {
var origEdge = a.originalEdge,
dummy = addDummyNode(g, origEdge.e, origEdge.u, origEdge.v, origEdge.value, 0, true);
g.addEdge(null, u, dummy, {minLen: 1});
g.addEdge(null, dummy, v, {minLen: 1});
g.delEdge(e);
}
});
}
function addDummyNode(g, e, u, v, a, index, isLabel) {
return g.addNode(null, {
width: isLabel ? a.width : 0,
height: isLabel ? a.height : 0,
edge: { id: e, source: u, target: v, attrs: a },
dummy: true,
index: index
});
}
function reorientEdges(g) {
g.eachEdge(function(e, u, v, value) {
if (g.node(u).rank > g.node(v).rank) {
g.delEdge(e);
value.reversed = true;
g.addEdge(e, v, u, value);
}
});
}
function rankComponent(subgraph, useSimplex) {
var spanningTree = feasibleTree(subgraph);
if (useSimplex) {
util.log(1, 'Using network simplex for ranking');
simplex(subgraph, spanningTree);
}
normalize(subgraph);
}
function normalize(g) {
var m = util.min(g.nodes().map(function(u) { return g.node(u).rank; }));
g.eachNode(function(u, node) { node.rank -= m; });
}
},{"./rank/acyclic":20,"./rank/constraints":21,"./rank/feasibleTree":22,"./rank/initRank":23,"./rank/simplex":25,"./util":26,"graphlib":28}],20:[function(require,module,exports){
var util = require('../util');
module.exports = acyclic;
module.exports.undo = undo;
/*
* This function takes a directed graph that may have cycles and reverses edges
* as appropriate to break these cycles. Each reversed edge is assigned a
* `reversed` attribute with the value `true`.
*
* There should be no self loops in the graph.
*/
function acyclic(g) {
var onStack = {},
visited = {},
reverseCount = 0;
function dfs(u) {
if (u in visited) return;
visited[u] = onStack[u] = true;
g.outEdges(u).forEach(function(e) {
var t = g.target(e),
value;
if (u === t) {
console.error('Warning: found self loop "' + e + '" for node "' + u + '"');
} else if (t in onStack) {
value = g.edge(e);
g.delEdge(e);
value.reversed = true;
++reverseCount;
g.addEdge(e, t, u, value);
} else {
dfs(t);
}
});
delete onStack[u];
}
g.eachNode(function(u) { dfs(u); });
util.log(2, 'Acyclic Phase: reversed ' + reverseCount + ' edge(s)');
return reverseCount;
}
/*
* Given a graph that has had the acyclic operation applied, this function
* undoes that operation. More specifically, any edge with the `reversed`
* attribute is again reversed to restore the original direction of the edge.
*/
function undo(g) {
g.eachEdge(function(e, s, t, a) {
if (a.reversed) {
delete a.reversed;
g.delEdge(e);
g.addEdge(e, t, s, a);
}
});
}
},{"../util":26}],21:[function(require,module,exports){
exports.apply = function(g) {
function dfs(sg) {
var rankSets = {};
g.children(sg).forEach(function(u) {
if (g.children(u).length) {
dfs(u);
return;
}
var value = g.node(u),
prefRank = value.prefRank;
if (prefRank !== undefined) {
if (!checkSupportedPrefRank(prefRank)) { return; }
if (!(prefRank in rankSets)) {
rankSets.prefRank = [u];
} else {
rankSets.prefRank.push(u);
}
var newU = rankSets[prefRank];
if (newU === undefined) {
newU = rankSets[prefRank] = g.addNode(null, { originalNodes: [] });
g.parent(newU, sg);
}
redirectInEdges(g, u, newU, prefRank === 'min');
redirectOutEdges(g, u, newU, prefRank === 'max');
// Save original node and remove it from reduced graph
g.node(newU).originalNodes.push({ u: u, value: value, parent: sg });
g.delNode(u);
}
});
addLightEdgesFromMinNode(g, sg, rankSets.min);
addLightEdgesToMaxNode(g, sg, rankSets.max);
}
dfs(null);
};
function checkSupportedPrefRank(prefRank) {
if (prefRank !== 'min' && prefRank !== 'max' && prefRank.indexOf('same_') !== 0) {
console.error('Unsupported rank type: ' + prefRank);
return false;
}
return true;
}
function redirectInEdges(g, u, newU, reverse) {
g.inEdges(u).forEach(function(e) {
var origValue = g.edge(e),
value;
if (origValue.originalEdge) {
value = origValue;
} else {
value = {
originalEdge: { e: e, u: g.source(e), v: g.target(e), value: origValue },
minLen: g.edge(e).minLen
};
}
// Do not reverse edges for self-loops.
if (origValue.selfLoop) {
reverse = false;
}
if (reverse) {
// Ensure that all edges to min are reversed
g.addEdge(null, newU, g.source(e), value);
value.reversed = true;
} else {
g.addEdge(null, g.source(e), newU, value);
}
});
}
function redirectOutEdges(g, u, newU, reverse) {
g.outEdges(u).forEach(function(e) {
var origValue = g.edge(e),
value;
if (origValue.originalEdge) {
value = origValue;
} else {
value = {
originalEdge: { e: e, u: g.source(e), v: g.target(e), value: origValue },
minLen: g.edge(e).minLen
};
}
// Do not reverse edges for self-loops.
if (origValue.selfLoop) {
reverse = false;
}
if (reverse) {
// Ensure that all edges from max are reversed
g.addEdge(null, g.target(e), newU, value);
value.reversed = true;
} else {
g.addEdge(null, newU, g.target(e), value);
}
});
}
function addLightEdgesFromMinNode(g, sg, minNode) {
if (minNode !== undefined) {
g.children(sg).forEach(function(u) {
// The dummy check ensures we don't add an edge if the node is involved
// in a self loop or sideways edge.
if (u !== minNode && !g.outEdges(minNode, u).length && !g.node(u).dummy) {
g.addEdge(null, minNode, u, { minLen: 0 });
}
});
}
}
function addLightEdgesToMaxNode(g, sg, maxNode) {
if (maxNode !== undefined) {
g.children(sg).forEach(function(u) {
// The dummy check ensures we don't add an edge if the node is involved
// in a self loop or sideways edge.
if (u !== maxNode && !g.outEdges(u, maxNode).length && !g.node(u).dummy) {
g.addEdge(null, u, maxNode, { minLen: 0 });
}
});
}
}
/*
* This function "relaxes" the constraints applied previously by the "apply"
* function. It expands any nodes that were collapsed and assigns the rank of
* the collapsed node to each of the expanded nodes. It also restores the
* original edges and removes any dummy edges pointing at the collapsed nodes.
*
* Note that the process of removing collapsed nodes also removes dummy edges
* automatically.
*/
exports.relax = function(g) {
// Save original edges
var originalEdges = [];
g.eachEdge(function(e, u, v, value) {
var originalEdge = value.originalEdge;
if (originalEdge) {
originalEdges.push(originalEdge);
}
});
// Expand collapsed nodes
g.eachNode(function(u, value) {
var originalNodes = value.originalNodes;
if (originalNodes) {
originalNodes.forEach(function(originalNode) {
originalNode.value.rank = value.rank;
g.addNode(originalNode.u, originalNode.value);
g.parent(originalNode.u, originalNode.parent);
});
g.delNode(u);
}
});
// Restore original edges
originalEdges.forEach(function(edge) {
g.addEdge(edge.e, edge.u, edge.v, edge.value);
});
};
},{}],22:[function(require,module,exports){
/* jshint -W079 */
var Set = require('cp-data').Set,
/* jshint +W079 */
Digraph = require('graphlib').Digraph,
util = require('../util');
module.exports = feasibleTree;
/*
* Given an acyclic graph with each node assigned a `rank` attribute, this
* function constructs and returns a spanning tree. This function may reduce
* the length of some edges from the initial rank assignment while maintaining
* the `minLen` specified by each edge.
*
* Prerequisites:
*
* * The input graph is acyclic
* * Each node in the input graph has an assigned `rank` attribute
* * Each edge in the input graph has an assigned `minLen` attribute
*
* Outputs:
*
* A feasible spanning tree for the input graph (i.e. a spanning tree that
* respects each graph edge's `minLen` attribute) represented as a Digraph with
* a `root` attribute on graph.
*
* Nodes have the same id and value as that in the input graph.
*
* Edges in the tree have arbitrarily assigned ids. The attributes for edges
* include `reversed`. `reversed` indicates that the edge is a
* back edge in the input graph.
*/
function feasibleTree(g) {
var remaining = new Set(g.nodes()),
tree = new Digraph();
if (remaining.size() === 1) {
var root = g.nodes()[0];
tree.addNode(root, {});
tree.graph({ root: root });
return tree;
}
function addTightEdges(v) {
var continueToScan = true;
g.predecessors(v).forEach(function(u) {
if (remaining.has(u) && !slack(g, u, v)) {
if (remaining.has(v)) {
tree.addNode(v, {});
remaining.remove(v);
tree.graph({ root: v });
}
tree.addNode(u, {});
tree.addEdge(null, u, v, { reversed: true });
remaining.remove(u);
addTightEdges(u);
continueToScan = false;
}
});
g.successors(v).forEach(function(w) {
if (remaining.has(w) && !slack(g, v, w)) {
if (remaining.has(v)) {
tree.addNode(v, {});
remaining.remove(v);
tree.graph({ root: v });
}
tree.addNode(w, {});
tree.addEdge(null, v, w, {});
remaining.remove(w);
addTightEdges(w);
continueToScan = false;
}
});
return continueToScan;
}
function createTightEdge() {
var minSlack = Number.MAX_VALUE;
remaining.keys().forEach(function(v) {
g.predecessors(v).forEach(function(u) {
if (!remaining.has(u)) {
var edgeSlack = slack(g, u, v);
if (Math.abs(edgeSlack) < Math.abs(minSlack)) {
minSlack = -edgeSlack;
}
}
});
g.successors(v).forEach(function(w) {
if (!remaining.has(w)) {
var edgeSlack = slack(g, v, w);
if (Math.abs(edgeSlack) < Math.abs(minSlack)) {
minSlack = edgeSlack;
}
}
});
});
tree.eachNode(function(u) { g.node(u).rank -= minSlack; });
}
while (remaining.size()) {
var nodesToSearch = !tree.order() ? remaining.keys() : tree.nodes();
for (var i = 0, il = nodesToSearch.length;
i < il && addTightEdges(nodesToSearch[i]);
++i);
if (remaining.size()) {
createTightEdge();
}
}
return tree;
}
function slack(g, u, v) {
var rankDiff = g.node(v).rank - g.node(u).rank;
var maxMinLen = util.max(g.outEdges(u, v)
.map(function(e) { return g.edge(e).minLen; }));
return rankDiff - maxMinLen;
}
},{"../util":26,"cp-data":5,"graphlib":28}],23:[function(require,module,exports){
var util = require('../util'),
topsort = require('graphlib').alg.topsort;
module.exports = initRank;
/*
* Assigns a `rank` attribute to each node in the input graph and ensures that
* this rank respects the `minLen` attribute of incident edges.
*
* Prerequisites:
*
* * The input graph must be acyclic
* * Each edge in the input graph must have an assigned 'minLen' attribute
*/
function initRank(g) {
var sorted = topsort(g);
sorted.forEach(function(u) {
var inEdges = g.inEdges(u);
if (inEdges.length === 0) {
g.node(u).rank = 0;
return;
}
var minLens = inEdges.map(function(e) {
return g.node(g.source(e)).rank + g.edge(e).minLen;
});
g.node(u).rank = util.max(minLens);
});
}
},{"../util":26,"graphlib":28}],24:[function(require,module,exports){
module.exports = {
slack: slack
};
/*
* A helper to calculate the slack between two nodes (`u` and `v`) given a
* `minLen` constraint. The slack represents how much the distance between `u`
* and `v` could shrink while maintaining the `minLen` constraint. If the value
* is negative then the constraint is currently violated.
*
This function requires that `u` and `v` are in `graph` and they both have a
`rank` attribute.
*/
function slack(graph, u, v, minLen) {
return Math.abs(graph.node(u).rank - graph.node(v).rank) - minLen;
}
},{}],25:[function(require,module,exports){
var util = require('../util'),
rankUtil = require('./rankUtil');
module.exports = simplex;
function simplex(graph, spanningTree) {
// The network simplex algorithm repeatedly replaces edges of
// the spanning tree with negative cut values until no such
// edge exists.
initCutValues(graph, spanningTree);
while (true) {
var e = leaveEdge(spanningTree);
if (e === null) break;
var f = enterEdge(graph, spanningTree, e);
exchange(graph, spanningTree, e, f);
}
}
/*
* Set the cut values of edges in the spanning tree by a depth-first
* postorder traversal. The cut value corresponds to the cost, in
* terms of a ranking's edge length sum, of lengthening an edge.
* Negative cut values typically indicate edges that would yield a
* smaller edge length sum if they were lengthened.
*/
function initCutValues(graph, spanningTree) {
computeLowLim(spanningTree);
spanningTree.eachEdge(function(id, u, v, treeValue) {
treeValue.cutValue = 0;
});
// Propagate cut values up the tree.
function dfs(n) {
var children = spanningTree.successors(n);
for (var c in children) {
var child = children[c];
dfs(child);
}
if (n !== spanningTree.graph().root) {
setCutValue(graph, spanningTree, n);
}
}
dfs(spanningTree.graph().root);
}
/*
* Perform a DFS postorder traversal, labeling each node v with
* its traversal order 'lim(v)' and the minimum traversal number
* of any of its descendants 'low(v)'. This provides an efficient
* way to test whether u is an ancestor of v since
* low(u) <= lim(v) <= lim(u) if and only if u is an ancestor.
*/
function computeLowLim(tree) {
var postOrderNum = 0;
function dfs(n) {
var children = tree.successors(n);
var low = postOrderNum;
for (var c in children) {
var child = children[c];
dfs(child);
low = Math.min(low, tree.node(child).low);
}
tree.node(n).low = low;
tree.node(n).lim = postOrderNum++;
}
dfs(tree.graph().root);
}
/*
* To compute the cut value of the edge parent -> child, we consider
* it and any other graph edges to or from the child.
* parent
* |
* child
* / \
* u v
*/
function setCutValue(graph, tree, child) {
var parentEdge = tree.inEdges(child)[0];
// List of child's children in the spanning tree.
var grandchildren = [];
var grandchildEdges = tree.outEdges(child);
for (var gce in grandchildEdges) {
grandchildren.push(tree.target(grandchildEdges[gce]));
}
var cutValue = 0;
// TODO: Replace unit increment/decrement with edge weights.
var E = 0; // Edges from child to grandchild's subtree.
var F = 0; // Edges to child from grandchild's subtree.
var G = 0; // Edges from child to nodes outside of child's subtree.
var H = 0; // Edges from nodes outside of child's subtree to child.
// Consider all graph edges from child.
var outEdges = graph.outEdges(child);
var gc;
for (var oe in outEdges) {
var succ = graph.target(outEdges[oe]);
for (gc in grandchildren) {
if (inSubtree(tree, succ, grandchildren[gc])) {
E++;
}
}
if (!inSubtree(tree, succ, child)) {
G++;
}
}
// Consider all graph edges to child.
var inEdges = graph.inEdges(child);
for (var ie in inEdges) {
var pred = graph.source(inEdges[ie]);
for (gc in grandchildren) {
if (inSubtree(tree, pred, grandchildren[gc])) {
F++;
}
}
if (!inSubtree(tree, pred, child)) {
H++;
}
}
// Contributions depend on the alignment of the parent -> child edge
// and the child -> u or v edges.
var grandchildCutSum = 0;
for (gc in grandchildren) {
var cv = tree.edge(grandchildEdges[gc]).cutValue;
if (!tree.edge(grandchildEdges[gc]).reversed) {
grandchildCutSum += cv;
} else {
grandchildCutSum -= cv;
}
}
if (!tree.edge(parentEdge).reversed) {
cutValue += grandchildCutSum - E + F - G + H;
} else {
cutValue -= grandchildCutSum - E + F - G + H;
}
tree.edge(parentEdge).cutValue = cutValue;
}
/*
* Return whether n is a node in the subtree with the given
* root.
*/
function inSubtree(tree, n, root) {
return (tree.node(root).low <= tree.node(n).lim &&
tree.node(n).lim <= tree.node(root).lim);
}
/*
* Return an edge from the tree with a negative cut value, or null if there
* is none.
*/
function leaveEdge(tree) {
var edges = tree.edges();
for (var n in edges) {
var e = edges[n];
var treeValue = tree.edge(e);
if (treeValue.cutValue < 0) {
return e;
}
}
return null;
}
/*
* The edge e should be an edge in the tree, with an underlying edge
* in the graph, with a negative cut value. Of the two nodes incident
* on the edge, take the lower one. enterEdge returns an edge with
* minimum slack going from outside of that node's subtree to inside
* of that node's subtree.
*/
function enterEdge(graph, tree, e) {
var source = tree.source(e);
var target = tree.target(e);
var lower = tree.node(target).lim < tree.node(source).lim ? target : source;
// Is the tree edge aligned with the graph edge?
var aligned = !tree.edge(e).reversed;
var minSlack = Number.POSITIVE_INFINITY;
var minSlackEdge;
if (aligned) {
graph.eachEdge(function(id, u, v, value) {
if (id !== e && inSubtree(tree, u, lower) && !inSubtree(tree, v, lower)) {
var slack = rankUtil.slack(graph, u, v, value.minLen);
if (slack < minSlack) {
minSlack = slack;
minSlackEdge = id;
}
}
});
} else {
graph.eachEdge(function(id, u, v, value) {
if (id !== e && !inSubtree(tree, u, lower) && inSubtree(tree, v, lower)) {
var slack = rankUtil.slack(graph, u, v, value.minLen);
if (slack < minSlack) {
minSlack = slack;
minSlackEdge = id;
}
}
});
}
if (minSlackEdge === undefined) {
var outside = [];
var inside = [];
graph.eachNode(function(id) {
if (!inSubtree(tree, id, lower)) {
outside.push(id);
} else {
inside.push(id);
}
});
throw new Error('No edge found from outside of tree to inside');
}
return minSlackEdge;
}
/*
* Replace edge e with edge f in the tree, recalculating the tree root,
* the nodes' low and lim properties and the edges' cut values.
*/
function exchange(graph, tree, e, f) {
tree.delEdge(e);
var source = graph.source(f);
var target = graph.target(f);
// Redirect edges so that target is the root of its subtree.
function redirect(v) {
var edges = tree.inEdges(v);
for (var i in edges) {
var e = edges[i];
var u = tree.source(e);
var value = tree.edge(e);
redirect(u);
tree.delEdge(e);
value.reversed = !value.reversed;
tree.addEdge(e, v, u, value);
}
}
redirect(target);
var root = source;
var edges = tree.inEdges(root);
while (edges.length > 0) {
root = tree.source(edges[0]);
edges = tree.inEdges(root);
}
tree.graph().root = root;
tree.addEdge(null, source, target, {cutValue: 0});
initCutValues(graph, tree);
adjustRanks(graph, tree);
}
/*
* Reset the ranks of all nodes based on the current spanning tree.
* The rank of the tree's root remains unchanged, while all other
* nodes are set to the sum of minimum length constraints along
* the path from the root.
*/
function adjustRanks(graph, tree) {
function dfs(p) {
var children = tree.successors(p);
children.forEach(function(c) {
var minLen = minimumLength(graph, p, c);
graph.node(c).rank = graph.node(p).rank + minLen;
dfs(c);
});
}
dfs(tree.graph().root);
}
/*
* If u and v are connected by some edges in the graph, return the
* minimum length of those edges, as a positive number if v succeeds
* u and as a negative number if v precedes u.
*/
function minimumLength(graph, u, v) {
var outEdges = graph.outEdges(u, v);
if (outEdges.length > 0) {
return util.max(outEdges.map(function(e) {
return graph.edge(e).minLen;
}));
}
var inEdges = graph.inEdges(u, v);
if (inEdges.length > 0) {
return -util.max(inEdges.map(function(e) {
return graph.edge(e).minLen;
}));
}
}
},{"../util":26,"./rankUtil":24}],26:[function(require,module,exports){
/*
* Returns the smallest value in the array.
*/
exports.min = function(values) {
return Math.min.apply(Math, values);
};
/*
* Returns the largest value in the array.
*/
exports.max = function(values) {
return Math.max.apply(Math, values);
};
/*
* Returns `true` only if `f(x)` is `true` for all `x` in `xs`. Otherwise
* returns `false`. This function will return immediately if it finds a
* case where `f(x)` does not hold.
*/
exports.all = function(xs, f) {
for (var i = 0; i < xs.length; ++i) {
if (!f(xs[i])) {
return false;
}
}
return true;
};
/*
* Accumulates the sum of elements in the given array using the `+` operator.
*/
exports.sum = function(values) {
return values.reduce(function(acc, x) { return acc + x; }, 0);
};
/*
* Returns an array of all values in the given object.
*/
exports.values = function(obj) {
return Object.keys(obj).map(function(k) { return obj[k]; });
};
exports.shuffle = function(array) {
for (i = array.length - 1; i > 0; --i) {
var j = Math.floor(Math.random() * (i + 1));
var aj = array[j];
array[j] = array[i];
array[i] = aj;
}
};
exports.propertyAccessor = function(self, config, field, setHook) {
return function(x) {
if (!arguments.length) return config[field];
config[field] = x;
if (setHook) setHook(x);
return self;
};
};
/*
* Given a layered, directed graph with `rank` and `order` node attributes,
* this function returns an array of ordered ranks. Each rank contains an array
* of the ids of the nodes in that rank in the order specified by the `order`
* attribute.
*/
exports.ordering = function(g) {
var ordering = [];
g.eachNode(function(u, value) {
var rank = ordering[value.rank] || (ordering[value.rank] = []);
rank[value.order] = u;
});
return ordering;
};
/*
* A filter that can be used with `filterNodes` to get a graph that only
* includes nodes that do not contain others nodes.
*/
exports.filterNonSubgraphs = function(g) {
return function(u) {
return g.children(u).length === 0;
};
};
/*
* Returns a new function that wraps `func` with a timer. The wrapper logs the
* time it takes to execute the function.
*
* The timer will be enabled provided `log.level >= 1`.
*/
function time(name, func) {
return function() {
var start = new Date().getTime();
try {
return func.apply(null, arguments);
} finally {
log(1, name + ' time: ' + (new Date().getTime() - start) + 'ms');
}
};
}
time.enabled = false;
exports.time = time;
/*
* A global logger with the specification `log(level, message, ...)` that
* will log a message to the console if `log.level >= level`.
*/
function log(level) {
if (log.level >= level) {
console.log.apply(console, Array.prototype.slice.call(arguments, 1));
}
}
log.level = 0;
exports.log = log;
},{}],27:[function(require,module,exports){
module.exports = '0.4.5';
},{}],28:[function(require,module,exports){
exports.Graph = require("./lib/Graph");
exports.Digraph = require("./lib/Digraph");
exports.CGraph = require("./lib/CGraph");
exports.CDigraph = require("./lib/CDigraph");
require("./lib/graph-converters");
exports.alg = {
isAcyclic: require("./lib/alg/isAcyclic"),
components: require("./lib/alg/components"),
dijkstra: require("./lib/alg/dijkstra"),
dijkstraAll: require("./lib/alg/dijkstraAll"),
findCycles: require("./lib/alg/findCycles"),
floydWarshall: require("./lib/alg/floydWarshall"),
postorder: require("./lib/alg/postorder"),
preorder: require("./lib/alg/preorder"),
prim: require("./lib/alg/prim"),
tarjan: require("./lib/alg/tarjan"),
topsort: require("./lib/alg/topsort")
};
exports.converter = {
json: require("./lib/converter/json.js")
};
var filter = require("./lib/filter");
exports.filter = {
all: filter.all,
nodesFromList: filter.nodesFromList
};
exports.version = require("./lib/version");
},{"./lib/CDigraph":30,"./lib/CGraph":31,"./lib/Digraph":32,"./lib/Graph":33,"./lib/alg/components":34,"./lib/alg/dijkstra":35,"./lib/alg/dijkstraAll":36,"./lib/alg/findCycles":37,"./lib/alg/floydWarshall":38,"./lib/alg/isAcyclic":39,"./lib/alg/postorder":40,"./lib/alg/preorder":41,"./lib/alg/prim":42,"./lib/alg/tarjan":43,"./lib/alg/topsort":44,"./lib/converter/json.js":46,"./lib/filter":47,"./lib/graph-converters":48,"./lib/version":50}],29:[function(require,module,exports){
/* jshint -W079 */
var Set = require("cp-data").Set;
/* jshint +W079 */
module.exports = BaseGraph;
function BaseGraph() {
// The value assigned to the graph itself.
this._value = undefined;
// Map of node id -> { id, value }
this._nodes = {};
// Map of edge id -> { id, u, v, value }
this._edges = {};
// Used to generate a unique id in the graph
this._nextId = 0;
}
// Number of nodes
BaseGraph.prototype.order = function() {
return Object.keys(this._nodes).length;
};
// Number of edges
BaseGraph.prototype.size = function() {
return Object.keys(this._edges).length;
};
// Accessor for graph level value
BaseGraph.prototype.graph = function(value) {
if (arguments.length === 0) {
return this._value;
}
this._value = value;
};
BaseGraph.prototype.hasNode = function(u) {
return u in this._nodes;
};
BaseGraph.prototype.node = function(u, value) {
var node = this._strictGetNode(u);
if (arguments.length === 1) {
return node.value;
}
node.value = value;
};
BaseGraph.prototype.nodes = function() {
var nodes = [];
this.eachNode(function(id) { nodes.push(id); });
return nodes;
};
BaseGraph.prototype.eachNode = function(func) {
for (var k in this._nodes) {
var node = this._nodes[k];
func(node.id, node.value);
}
};
BaseGraph.prototype.hasEdge = function(e) {
return e in this._edges;
};
BaseGraph.prototype.edge = function(e, value) {
var edge = this._strictGetEdge(e);
if (arguments.length === 1) {
return edge.value;
}
edge.value = value;
};
BaseGraph.prototype.edges = function() {
var es = [];
this.eachEdge(function(id) { es.push(id); });
return es;
};
BaseGraph.prototype.eachEdge = function(func) {
for (var k in this._edges) {
var edge = this._edges[k];
func(edge.id, edge.u, edge.v, edge.value);
}
};
BaseGraph.prototype.incidentNodes = function(e) {
var edge = this._strictGetEdge(e);
return [edge.u, edge.v];
};
BaseGraph.prototype.addNode = function(u, value) {
if (u === undefined || u === null) {
do {
u = "_" + (++this._nextId);
} while (this.hasNode(u));
} else if (this.hasNode(u)) {
throw new Error("Graph already has node '" + u + "'");
}
this._nodes[u] = { id: u, value: value };
return u;
};
BaseGraph.prototype.delNode = function(u) {
this._strictGetNode(u);
this.incidentEdges(u).forEach(function(e) { this.delEdge(e); }, this);
delete this._nodes[u];
};
// inMap and outMap are opposite sides of an incidence map. For example, for
// Graph these would both come from the _incidentEdges map, while for Digraph
// they would come from _inEdges and _outEdges.
BaseGraph.prototype._addEdge = function(e, u, v, value, inMap, outMap) {
this._strictGetNode(u);
this._strictGetNode(v);
if (e === undefined || e === null) {
do {
e = "_" + (++this._nextId);
} while (this.hasEdge(e));
}
else if (this.hasEdge(e)) {
throw new Error("Graph already has edge '" + e + "'");
}
this._edges[e] = { id: e, u: u, v: v, value: value };
addEdgeToMap(inMap[v], u, e);
addEdgeToMap(outMap[u], v, e);
return e;
};
// See note for _addEdge regarding inMap and outMap.
BaseGraph.prototype._delEdge = function(e, inMap, outMap) {
var edge = this._strictGetEdge(e);
delEdgeFromMap(inMap[edge.v], edge.u, e);
delEdgeFromMap(outMap[edge.u], edge.v, e);
delete this._edges[e];
};
BaseGraph.prototype.copy = function() {
var copy = new this.constructor();
copy.graph(this.graph());
this.eachNode(function(u, value) { copy.addNode(u, value); });
this.eachEdge(function(e, u, v, value) { copy.addEdge(e, u, v, value); });
copy._nextId = this._nextId;
return copy;
};
BaseGraph.prototype.filterNodes = function(filter) {
var copy = new this.constructor();
copy.graph(this.graph());
this.eachNode(function(u, value) {
if (filter(u)) {
copy.addNode(u, value);
}
});
this.eachEdge(function(e, u, v, value) {
if (copy.hasNode(u) && copy.hasNode(v)) {
copy.addEdge(e, u, v, value);
}
});
return copy;
};
BaseGraph.prototype._strictGetNode = function(u) {
var node = this._nodes[u];
if (node === undefined) {
throw new Error("Node '" + u + "' is not in graph");
}
return node;
};
BaseGraph.prototype._strictGetEdge = function(e) {
var edge = this._edges[e];
if (edge === undefined) {
throw new Error("Edge '" + e + "' is not in graph");
}
return edge;
};
function addEdgeToMap(map, v, e) {
(map[v] || (map[v] = new Set())).add(e);
}
function delEdgeFromMap(map, v, e) {
var vEntry = map[v];
vEntry.remove(e);
if (vEntry.size() === 0) {
delete map[v];
}
}
},{"cp-data":5}],30:[function(require,module,exports){
var Digraph = require("./Digraph"),
compoundify = require("./compoundify");
var CDigraph = compoundify(Digraph);
module.exports = CDigraph;
CDigraph.fromDigraph = function(src) {
var g = new CDigraph(),
graphValue = src.graph();
if (graphValue !== undefined) {
g.graph(graphValue);
}
src.eachNode(function(u, value) {
if (value === undefined) {
g.addNode(u);
} else {
g.addNode(u, value);
}
});
src.eachEdge(function(e, u, v, value) {
if (value === undefined) {
g.addEdge(null, u, v);
} else {
g.addEdge(null, u, v, value);
}
});
return g;
};
CDigraph.prototype.toString = function() {
return "CDigraph " + JSON.stringify(this, null, 2);
};
},{"./Digraph":32,"./compoundify":45}],31:[function(require,module,exports){
var Graph = require("./Graph"),
compoundify = require("./compoundify");
var CGraph = compoundify(Graph);
module.exports = CGraph;
CGraph.fromGraph = function(src) {
var g = new CGraph(),
graphValue = src.graph();
if (graphValue !== undefined) {
g.graph(graphValue);
}
src.eachNode(function(u, value) {
if (value === undefined) {
g.addNode(u);
} else {
g.addNode(u, value);
}
});
src.eachEdge(function(e, u, v, value) {
if (value === undefined) {
g.addEdge(null, u, v);
} else {
g.addEdge(null, u, v, value);
}
});
return g;
};
CGraph.prototype.toString = function() {
return "CGraph " + JSON.stringify(this, null, 2);
};
},{"./Graph":33,"./compoundify":45}],32:[function(require,module,exports){
/*
* This file is organized with in the following order:
*
* Exports
* Graph constructors
* Graph queries (e.g. nodes(), edges()
* Graph mutators
* Helper functions
*/
var util = require("./util"),
BaseGraph = require("./BaseGraph"),
/* jshint -W079 */
Set = require("cp-data").Set;
/* jshint +W079 */
module.exports = Digraph;
/*
* Constructor to create a new directed multi-graph.
*/
function Digraph() {
BaseGraph.call(this);
/*! Map of sourceId -> {targetId -> Set of edge ids} */
this._inEdges = {};
/*! Map of targetId -> {sourceId -> Set of edge ids} */
this._outEdges = {};
}
Digraph.prototype = new BaseGraph();
Digraph.prototype.constructor = Digraph;
/*
* Always returns `true`.
*/
Digraph.prototype.isDirected = function() {
return true;
};
/*
* Returns all successors of the node with the id `u`. That is, all nodes
* that have the node `u` as their source are returned.
*
* If no node `u` exists in the graph this function throws an Error.
*
* @param {String} u a node id
*/
Digraph.prototype.successors = function(u) {
this._strictGetNode(u);
return Object.keys(this._outEdges[u])
.map(function(v) { return this._nodes[v].id; }, this);
};
/*
* Returns all predecessors of the node with the id `u`. That is, all nodes
* that have the node `u` as their target are returned.
*
* If no node `u` exists in the graph this function throws an Error.
*
* @param {String} u a node id
*/
Digraph.prototype.predecessors = function(u) {
this._strictGetNode(u);
return Object.keys(this._inEdges[u])
.map(function(v) { return this._nodes[v].id; }, this);
};
/*
* Returns all nodes that are adjacent to the node with the id `u`. In other
* words, this function returns the set of all successors and predecessors of
* node `u`.
*
* @param {String} u a node id
*/
Digraph.prototype.neighbors = function(u) {
return Set.union([this.successors(u), this.predecessors(u)]).keys();
};
/*
* Returns all nodes in the graph that have no in-edges.
*/
Digraph.prototype.sources = function() {
var self = this;
return this._filterNodes(function(u) {
// This could have better space characteristics if we had an inDegree function.
return self.inEdges(u).length === 0;
});
};
/*
* Returns all nodes in the graph that have no out-edges.
*/
Digraph.prototype.sinks = function() {
var self = this;
return this._filterNodes(function(u) {
// This could have better space characteristics if we have an outDegree function.
return self.outEdges(u).length === 0;
});
};
/*
* Returns the source node incident on the edge identified by the id `e`. If no
* such edge exists in the graph this function throws an Error.
*
* @param {String} e an edge id
*/
Digraph.prototype.source = function(e) {
return this._strictGetEdge(e).u;
};
/*
* Returns the target node incident on the edge identified by the id `e`. If no
* such edge exists in the graph this function throws an Error.
*
* @param {String} e an edge id
*/
Digraph.prototype.target = function(e) {
return this._strictGetEdge(e).v;
};
/*
* Returns an array of ids for all edges in the graph that have the node
* `target` as their target. If the node `target` is not in the graph this
* function raises an Error.
*
* Optionally a `source` node can also be specified. This causes the results
* to be filtered such that only edges from `source` to `target` are included.
* If the node `source` is specified but is not in the graph then this function
* raises an Error.
*
* @param {String} target the target node id
* @param {String} [source] an optional source node id
*/
Digraph.prototype.inEdges = function(target, source) {
this._strictGetNode(target);
var results = Set.union(util.values(this._inEdges[target])).keys();
if (arguments.length > 1) {
this._strictGetNode(source);
results = results.filter(function(e) { return this.source(e) === source; }, this);
}
return results;
};
/*
* Returns an array of ids for all edges in the graph that have the node
* `source` as their source. If the node `source` is not in the graph this
* function raises an Error.
*
* Optionally a `target` node may also be specified. This causes the results
* to be filtered such that only edges from `source` to `target` are included.
* If the node `target` is specified but is not in the graph then this function
* raises an Error.
*
* @param {String} source the source node id
* @param {String} [target] an optional target node id
*/
Digraph.prototype.outEdges = function(source, target) {
this._strictGetNode(source);
var results = Set.union(util.values(this._outEdges[source])).keys();
if (arguments.length > 1) {
this._strictGetNode(target);
results = results.filter(function(e) { return this.target(e) === target; }, this);
}
return results;
};
/*
* Returns an array of ids for all edges in the graph that have the `u` as
* their source or their target. If the node `u` is not in the graph this
* function raises an Error.
*
* Optionally a `v` node may also be specified. This causes the results to be
* filtered such that only edges between `u` and `v` - in either direction -
* are included. IF the node `v` is specified but not in the graph then this
* function raises an Error.
*
* @param {String} u the node for which to find incident edges
* @param {String} [v] option node that must be adjacent to `u`
*/
Digraph.prototype.incidentEdges = function(u, v) {
if (arguments.length > 1) {
return Set.union([this.outEdges(u, v), this.outEdges(v, u)]).keys();
} else {
return Set.union([this.inEdges(u), this.outEdges(u)]).keys();
}
};
/*
* Returns a string representation of this graph.
*/
Digraph.prototype.toString = function() {
return "Digraph " + JSON.stringify(this, null, 2);
};
/*
* Adds a new node with the id `u` to the graph and assigns it the value
* `value`. If a node with the id is already a part of the graph this function
* throws an Error.
*
* @param {String} u a node id
* @param {Object} [value] an optional value to attach to the node
*/
Digraph.prototype.addNode = function(u, value) {
u = BaseGraph.prototype.addNode.call(this, u, value);
this._inEdges[u] = {};
this._outEdges[u] = {};
return u;
};
/*
* Removes a node from the graph that has the id `u`. Any edges incident on the
* node are also removed. If the graph does not contain a node with the id this
* function will throw an Error.
*
* @param {String} u a node id
*/
Digraph.prototype.delNode = function(u) {
BaseGraph.prototype.delNode.call(this, u);
delete this._inEdges[u];
delete this._outEdges[u];
};
/*
* Adds a new edge to the graph with the id `e` from a node with the id `source`
* to a node with an id `target` and assigns it the value `value`. This graph
* allows more than one edge from `source` to `target` as long as the id `e`
* is unique in the set of edges. If `e` is `null` the graph will assign a
* unique identifier to the edge.
*
* If `source` or `target` are not present in the graph this function will
* throw an Error.
*
* @param {String} [e] an edge id
* @param {String} source the source node id
* @param {String} target the target node id
* @param {Object} [value] an optional value to attach to the edge
*/
Digraph.prototype.addEdge = function(e, source, target, value) {
return BaseGraph.prototype._addEdge.call(this, e, source, target, value,
this._inEdges, this._outEdges);
};
/*
* Removes an edge in the graph with the id `e`. If no edge in the graph has
* the id `e` this function will throw an Error.
*
* @param {String} e an edge id
*/
Digraph.prototype.delEdge = function(e) {
BaseGraph.prototype._delEdge.call(this, e, this._inEdges, this._outEdges);
};
// Unlike BaseGraph.filterNodes, this helper just returns nodes that
// satisfy a predicate.
Digraph.prototype._filterNodes = function(pred) {
var filtered = [];
this.eachNode(function(u) {
if (pred(u)) {
filtered.push(u);
}
});
return filtered;
};
},{"./BaseGraph":29,"./util":49,"cp-data":5}],33:[function(require,module,exports){
/*
* This file is organized with in the following order:
*
* Exports
* Graph constructors
* Graph queries (e.g. nodes(), edges()
* Graph mutators
* Helper functions
*/
var util = require("./util"),
BaseGraph = require("./BaseGraph"),
/* jshint -W079 */
Set = require("cp-data").Set;
/* jshint +W079 */
module.exports = Graph;
/*
* Constructor to create a new undirected multi-graph.
*/
function Graph() {
BaseGraph.call(this);
/*! Map of nodeId -> { otherNodeId -> Set of edge ids } */
this._incidentEdges = {};
}
Graph.prototype = new BaseGraph();
Graph.prototype.constructor = Graph;
/*
* Always returns `false`.
*/
Graph.prototype.isDirected = function() {
return false;
};
/*
* Returns all nodes that are adjacent to the node with the id `u`.
*
* @param {String} u a node id
*/
Graph.prototype.neighbors = function(u) {
this._strictGetNode(u);
return Object.keys(this._incidentEdges[u])
.map(function(v) { return this._nodes[v].id; }, this);
};
/*
* Returns an array of ids for all edges in the graph that are incident on `u`.
* If the node `u` is not in the graph this function raises an Error.
*
* Optionally a `v` node may also be specified. This causes the results to be
* filtered such that only edges between `u` and `v` are included. If the node
* `v` is specified but not in the graph then this function raises an Error.
*
* @param {String} u the node for which to find incident edges
* @param {String} [v] option node that must be adjacent to `u`
*/
Graph.prototype.incidentEdges = function(u, v) {
this._strictGetNode(u);
if (arguments.length > 1) {
this._strictGetNode(v);
return v in this._incidentEdges[u] ? this._incidentEdges[u][v].keys() : [];
} else {
return Set.union(util.values(this._incidentEdges[u])).keys();
}
};
/*
* Returns a string representation of this graph.
*/
Graph.prototype.toString = function() {
return "Graph " + JSON.stringify(this, null, 2);
};
/*
* Adds a new node with the id `u` to the graph and assigns it the value
* `value`. If a node with the id is already a part of the graph this function
* throws an Error.
*
* @param {String} u a node id
* @param {Object} [value] an optional value to attach to the node
*/
Graph.prototype.addNode = function(u, value) {
u = BaseGraph.prototype.addNode.call(this, u, value);
this._incidentEdges[u] = {};
return u;
};
/*
* Removes a node from the graph that has the id `u`. Any edges incident on the
* node are also removed. If the graph does not contain a node with the id this
* function will throw an Error.
*
* @param {String} u a node id
*/
Graph.prototype.delNode = function(u) {
BaseGraph.prototype.delNode.call(this, u);
delete this._incidentEdges[u];
};
/*
* Adds a new edge to the graph with the id `e` between a node with the id `u`
* and a node with an id `v` and assigns it the value `value`. This graph
* allows more than one edge between `u` and `v` as long as the id `e`
* is unique in the set of edges. If `e` is `null` the graph will assign a
* unique identifier to the edge.
*
* If `u` or `v` are not present in the graph this function will throw an
* Error.
*
* @param {String} [e] an edge id
* @param {String} u the node id of one of the adjacent nodes
* @param {String} v the node id of the other adjacent node
* @param {Object} [value] an optional value to attach to the edge
*/
Graph.prototype.addEdge = function(e, u, v, value) {
return BaseGraph.prototype._addEdge.call(this, e, u, v, value,
this._incidentEdges, this._incidentEdges);
};
/*
* Removes an edge in the graph with the id `e`. If no edge in the graph has
* the id `e` this function will throw an Error.
*
* @param {String} e an edge id
*/
Graph.prototype.delEdge = function(e) {
BaseGraph.prototype._delEdge.call(this, e, this._incidentEdges, this._incidentEdges);
};
},{"./BaseGraph":29,"./util":49,"cp-data":5}],34:[function(require,module,exports){
/* jshint -W079 */
var Set = require("cp-data").Set;
/* jshint +W079 */
module.exports = components;
/**
* Finds all [connected components][] in a graph and returns an array of these
* components. Each component is itself an array that contains the ids of nodes
* in the component.
*
* This function only works with undirected Graphs.
*
*
* @param {Graph} g the graph to search for components
*/
function components(g) {
var results = [];
var visited = new Set();
function dfs(v, component) {
if (!visited.has(v)) {
visited.add(v);
component.push(v);
g.neighbors(v).forEach(function(w) {
dfs(w, component);
});
}
}
g.nodes().forEach(function(v) {
var component = [];
dfs(v, component);
if (component.length > 0) {
results.push(component);
}
});
return results;
}
},{"cp-data":5}],35:[function(require,module,exports){
var PriorityQueue = require("cp-data").PriorityQueue;
module.exports = dijkstra;
/**
* This function is an implementation of [Dijkstra's algorithm][] which finds
* the shortest path from **source** to all other nodes in **g**. This
* function returns a map of `u -> { distance, predecessor }`. The distance
* property holds the sum of the weights from **source** to `u` along the
* shortest path or `Number.POSITIVE_INFINITY` if there is no path from
* **source**. The predecessor property can be used to walk the individual
* elements of the path from **source** to **u** in reverse order.
*
* This function takes an optional `weightFunc(e)` which returns the
* weight of the edge `e`. If no weightFunc is supplied then each edge is
* assumed to have a weight of 1. This function throws an Error if any of
* the traversed edges have a negative edge weight.
*
* This function takes an optional `incidentFunc(u)` which returns the ids of
* all edges incident to the node `u` for the purposes of shortest path
* traversal. By default this function uses the `g.outEdges` for Digraphs and
* `g.incidentEdges` for Graphs.
*
* This function takes `O((|E| + |V|) * log |V|)` time.
*
*
* @param {Graph} g the graph to search for shortest paths from **source**
* @param {Object} source the source from which to start the search
* @param {Function} [weightFunc] optional weight function
* @param {Function} [incidentFunc] optional incident function
*/
function dijkstra(g, source, weightFunc, incidentFunc) {
var results = {},
pq = new PriorityQueue();
function updateNeighbors(e) {
var incidentNodes = g.incidentNodes(e),
v = incidentNodes[0] !== u ? incidentNodes[0] : incidentNodes[1],
vEntry = results[v],
weight = weightFunc(e),
distance = uEntry.distance + weight;
if (weight < 0) {
throw new Error("dijkstra does not allow negative edge weights. Bad edge: " + e + " Weight: " + weight);
}
if (distance < vEntry.distance) {
vEntry.distance = distance;
vEntry.predecessor = u;
pq.decrease(v, distance);
}
}
weightFunc = weightFunc || function() { return 1; };
incidentFunc = incidentFunc || (g.isDirected()
? function(u) { return g.outEdges(u); }
: function(u) { return g.incidentEdges(u); });
g.eachNode(function(u) {
var distance = u === source ? 0 : Number.POSITIVE_INFINITY;
results[u] = { distance: distance };
pq.add(u, distance);
});
var u, uEntry;
while (pq.size() > 0) {
u = pq.removeMin();
uEntry = results[u];
if (uEntry.distance === Number.POSITIVE_INFINITY) {
break;
}
incidentFunc(u).forEach(updateNeighbors);
}
return results;
}
},{"cp-data":5}],36:[function(require,module,exports){
var dijkstra = require("./dijkstra");
module.exports = dijkstraAll;
/**
* This function finds the shortest path from each node to every other
* reachable node in the graph. It is similar to [alg.dijkstra][], but
* instead of returning a single-source array, it returns a mapping of
* of `source -> alg.dijksta(g, source, weightFunc, incidentFunc)`.
*
* This function takes an optional `weightFunc(e)` which returns the
* weight of the edge `e`. If no weightFunc is supplied then each edge is
* assumed to have a weight of 1. This function throws an Error if any of
* the traversed edges have a negative edge weight.
*
* This function takes an optional `incidentFunc(u)` which returns the ids of
* all edges incident to the node `u` for the purposes of shortest path
* traversal. By default this function uses the `outEdges` function on the
* supplied graph.
*
* This function takes `O(|V| * (|E| + |V|) * log |V|)` time.
*
* [alg.dijkstra]: dijkstra.js.html#dijkstra
*
* @param {Graph} g the graph to search for shortest paths from **source**
* @param {Function} [weightFunc] optional weight function
* @param {Function} [incidentFunc] optional incident function
*/
function dijkstraAll(g, weightFunc, incidentFunc) {
var results = {};
g.eachNode(function(u) {
results[u] = dijkstra(g, u, weightFunc, incidentFunc);
});
return results;
}
},{"./dijkstra":35}],37:[function(require,module,exports){
var tarjan = require("./tarjan");
module.exports = findCycles;
/*
* Given a Digraph **g** this function returns all nodes that are part of a
* cycle. Since there may be more than one cycle in a graph this function
* returns an array of these cycles, where each cycle is itself represented
* by an array of ids for each node involved in that cycle.
*
* [alg.isAcyclic][] is more efficient if you only need to determine whether
* a graph has a cycle or not.
*
* [alg.isAcyclic]: isAcyclic.js.html#isAcyclic
*
* @param {Digraph} g the graph to search for cycles.
*/
function findCycles(g) {
return tarjan(g).filter(function(cmpt) { return cmpt.length > 1; });
}
},{"./tarjan":43}],38:[function(require,module,exports){
module.exports = floydWarshall;
/**
* This function is an implementation of the [Floyd-Warshall algorithm][],
* which finds the shortest path from each node to every other reachable node
* in the graph. It is similar to [alg.dijkstraAll][], but it handles negative
* edge weights and is more efficient for some types of graphs. This function
* returns a map of `source -> { target -> { distance, predecessor }`. The
* distance property holds the sum of the weights from `source` to `target`
* along the shortest path of `Number.POSITIVE_INFINITY` if there is no path
* from `source`. The predecessor property can be used to walk the individual
* elements of the path from `source` to `target` in reverse order.
*
* This function takes an optional `weightFunc(e)` which returns the
* weight of the edge `e`. If no weightFunc is supplied then each edge is
* assumed to have a weight of 1.
*
* This function takes an optional `incidentFunc(u)` which returns the ids of
* all edges incident to the node `u` for the purposes of shortest path
* traversal. By default this function uses the `outEdges` function on the
* supplied graph.
*
* This algorithm takes O(|V|^3) time.
*
* [alg.dijkstraAll]: dijkstraAll.js.html#dijkstraAll
*
* @param {Graph} g the graph to search for shortest paths from **source**
* @param {Function} [weightFunc] optional weight function
* @param {Function} [incidentFunc] optional incident function
*/
function floydWarshall(g, weightFunc, incidentFunc) {
var results = {},
nodes = g.nodes();
weightFunc = weightFunc || function() { return 1; };
incidentFunc = incidentFunc || (g.isDirected()
? function(u) { return g.outEdges(u); }
: function(u) { return g.incidentEdges(u); });
nodes.forEach(function(u) {
results[u] = {};
results[u][u] = { distance: 0 };
nodes.forEach(function(v) {
if (u !== v) {
results[u][v] = { distance: Number.POSITIVE_INFINITY };
}
});
incidentFunc(u).forEach(function(e) {
var incidentNodes = g.incidentNodes(e),
v = incidentNodes[0] !== u ? incidentNodes[0] : incidentNodes[1],
d = weightFunc(e);
if (d < results[u][v].distance) {
results[u][v] = { distance: d, predecessor: u };
}
});
});
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;
}
},{}],39:[function(require,module,exports){
var topsort = require("./topsort");
module.exports = isAcyclic;
/*
* Given a Digraph **g** this function returns `true` if the graph has no
* cycles and returns `false` if it does. This algorithm returns as soon as it
* detects the first cycle.
*
* Use [alg.findCycles][] if you need the actual list of cycles in a graph.
*
* [alg.findCycles]: findCycles.js.html#findCycles
*
* @param {Digraph} g the graph to test for cycles
*/
function isAcyclic(g) {
try {
topsort(g);
} catch (e) {
if (e instanceof topsort.CycleException) return false;
throw e;
}
return true;
}
},{"./topsort":44}],40:[function(require,module,exports){
/* jshint -W079 */
var Set = require("cp-data").Set;
/* jshint +W079 */
module.exports = postorder;
// Postorder traversal of g, calling f for each visited node. Assumes the graph
// is a tree.
function postorder(g, root, f) {
var visited = new Set();
if (g.isDirected()) {
throw new Error("This function only works for undirected graphs");
}
function dfs(u, prev) {
if (visited.has(u)) {
throw new Error("The input graph is not a tree: " + g);
}
visited.add(u);
g.neighbors(u).forEach(function(v) {
if (v !== prev) dfs(v, u);
});
f(u);
}
dfs(root);
}
},{"cp-data":5}],41:[function(require,module,exports){
/* jshint -W079 */
var Set = require("cp-data").Set;
/* jshint +W079 */
module.exports = preorder;
// Preorder traversal of g, calling f for each visited node. Assumes the graph
// is a tree.
function preorder(g, root, f) {
var visited = new Set();
if (g.isDirected()) {
throw new Error("This function only works for undirected graphs");
}
function dfs(u, prev) {
if (visited.has(u)) {
throw new Error("The input graph is not a tree: " + g);
}
visited.add(u);
f(u);
g.neighbors(u).forEach(function(v) {
if (v !== prev) dfs(v, u);
});
}
dfs(root);
}
},{"cp-data":5}],42:[function(require,module,exports){
var Graph = require("../Graph"),
PriorityQueue = require("cp-data").PriorityQueue;
module.exports = prim;
/**
* [Prim's algorithm][] takes a connected undirected graph and generates a
* [minimum spanning tree][]. This function returns the minimum spanning
* tree as an undirected graph. This algorithm is derived from the description
* in "Introduction to Algorithms", Third Edition, Cormen, et al., Pg 634.
*
* This function takes a `weightFunc(e)` which returns the weight of the edge
* `e`. It throws an Error if the graph is not connected.
*
* This function takes `O(|E| log |V|)` time.
*
*
* @param {Graph} g the graph used to generate the minimum spanning tree
* @param {Function} weightFunc the weight function to use
*/
function prim(g, weightFunc) {
var result = new Graph(),
parents = {},
pq = new PriorityQueue(),
u;
function updateNeighbors(e) {
var incidentNodes = g.incidentNodes(e),
v = incidentNodes[0] !== u ? incidentNodes[0] : incidentNodes[1],
pri = pq.priority(v);
if (pri !== undefined) {
var edgeWeight = weightFunc(e);
if (edgeWeight < pri) {
parents[v] = u;
pq.decrease(v, edgeWeight);
}
}
}
if (g.order() === 0) {
return result;
}
g.eachNode(function(u) {
pq.add(u, Number.POSITIVE_INFINITY);
result.addNode(u);
});
// Start from an arbitrary node
pq.decrease(g.nodes()[0], 0);
var init = false;
while (pq.size() > 0) {
u = pq.removeMin();
if (u in parents) {
result.addEdge(null, u, parents[u]);
} else if (init) {
throw new Error("Input graph is not connected: " + g);
} else {
init = true;
}
g.incidentEdges(u).forEach(updateNeighbors);
}
return result;
}
},{"../Graph":33,"cp-data":5}],43:[function(require,module,exports){
module.exports = tarjan;
/**
* This function is an implementation of [Tarjan's algorithm][] which finds
* all [strongly connected components][] in the directed graph **g**. Each
* strongly connected component is composed of nodes that can reach all other
* nodes in the component via directed edges. A strongly connected component
* can consist of a single node if that node cannot both reach and be reached
* by any other specific node in the graph. Components of more than one node
* are guaranteed to have at least one cycle.
*
* This function returns an array of components. Each component is itself an
* array that contains the ids of all nodes in the component.
*
*
* @param {Digraph} g the graph to search for strongly connected components
*/
function tarjan(g) {
if (!g.isDirected()) {
throw new Error("tarjan can only be applied to a directed graph. Bad input: " + g);
}
var index = 0,
stack = [],
visited = {}, // node id -> { onStack, lowlink, index }
results = [];
function dfs(u) {
var entry = visited[u] = {
onStack: true,
lowlink: index,
index: index++
};
stack.push(u);
g.successors(u).forEach(function(v) {
if (!(v in visited)) {
dfs(v);
entry.lowlink = Math.min(entry.lowlink, visited[v].lowlink);
} else if (visited[v].onStack) {
entry.lowlink = Math.min(entry.lowlink, visited[v].index);
}
});
if (entry.lowlink === entry.index) {
var cmpt = [],
v;
do {
v = stack.pop();
visited[v].onStack = false;
cmpt.push(v);
} while (u !== v);
results.push(cmpt);
}
}
g.nodes().forEach(function(u) {
if (!(u in visited)) {
dfs(u);
}
});
return results;
}
},{}],44:[function(require,module,exports){
module.exports = topsort;
topsort.CycleException = CycleException;
/*
* Given a graph **g**, this function returns an ordered list of nodes such
* that for each edge `u -> v`, `u` appears before `v` in the list. If the
* graph has a cycle it is impossible to generate such a list and
* **CycleException** is thrown.
*
* for more details about how this algorithm works.
*
* @param {Digraph} g the graph to sort
*/
function topsort(g) {
if (!g.isDirected()) {
throw new Error("topsort can only be applied to a directed graph. Bad input: " + g);
}
var visited = {};
var stack = {};
var results = [];
function visit(node) {
if (node in stack) {
throw new CycleException();
}
if (!(node in visited)) {
stack[node] = true;
visited[node] = true;
g.predecessors(node).forEach(function(pred) {
visit(pred);
});
delete stack[node];
results.push(node);
}
}
var sinks = g.sinks();
if (g.order() !== 0 && sinks.length === 0) {
throw new CycleException();
}
g.sinks().forEach(function(sink) {
visit(sink);
});
return results;
}
function CycleException() {}
CycleException.prototype.toString = function() {
return "Graph has at least one cycle";
};
},{}],45:[function(require,module,exports){
// This file provides a helper function that mixes-in Dot behavior to an
// existing graph prototype.
/* jshint -W079 */
var Set = require("cp-data").Set;
/* jshint +W079 */
module.exports = compoundify;
// Extends the given SuperConstructor with the ability for nodes to contain
// other nodes. A special node id `null` is used to indicate the root graph.
function compoundify(SuperConstructor) {
function Constructor() {
SuperConstructor.call(this);
// Map of object id -> parent id (or null for root graph)
this._parents = {};
// Map of id (or null) -> children set
this._children = {};
this._children[null] = new Set();
}
Constructor.prototype = new SuperConstructor();
Constructor.prototype.constructor = Constructor;
Constructor.prototype.parent = function(u, parent) {
this._strictGetNode(u);
if (arguments.length < 2) {
return this._parents[u];
}
if (u === parent) {
throw new Error("Cannot make " + u + " a parent of itself");
}
if (parent !== null) {
this._strictGetNode(parent);
}
this._children[this._parents[u]].remove(u);
this._parents[u] = parent;
this._children[parent].add(u);
};
Constructor.prototype.children = function(u) {
if (u !== null) {
this._strictGetNode(u);
}
return this._children[u].keys();
};
Constructor.prototype.addNode = function(u, value) {
u = SuperConstructor.prototype.addNode.call(this, u, value);
this._parents[u] = null;
this._children[u] = new Set();
this._children[null].add(u);
return u;
};
Constructor.prototype.delNode = function(u) {
// Promote all children to the parent of the subgraph
var parent = this.parent(u);
this._children[u].keys().forEach(function(child) {
this.parent(child, parent);
}, this);
this._children[parent].remove(u);
delete this._parents[u];
delete this._children[u];
return SuperConstructor.prototype.delNode.call(this, u);
};
Constructor.prototype.copy = function() {
var copy = SuperConstructor.prototype.copy.call(this);
this.nodes().forEach(function(u) {
copy.parent(u, this.parent(u));
}, this);
return copy;
};
Constructor.prototype.filterNodes = function(filter) {
var self = this,
copy = SuperConstructor.prototype.filterNodes.call(this, filter);
var parents = {};
function findParent(u) {
var parent = self.parent(u);
if (parent === null || copy.hasNode(parent)) {
parents[u] = parent;
return parent;
} else if (parent in parents) {
return parents[parent];
} else {
return findParent(parent);
}
}
copy.eachNode(function(u) { copy.parent(u, findParent(u)); });
return copy;
};
return Constructor;
}
},{"cp-data":5}],46:[function(require,module,exports){
var Graph = require("../Graph"),
Digraph = require("../Digraph"),
CGraph = require("../CGraph"),
CDigraph = require("../CDigraph");
exports.decode = function(nodes, edges, Ctor) {
Ctor = Ctor || Digraph;
if (typeOf(nodes) !== "Array") {
throw new Error("nodes is not an Array");
}
if (typeOf(edges) !== "Array") {
throw new Error("edges is not an Array");
}
if (typeof Ctor === "string") {
switch(Ctor) {
case "graph": Ctor = Graph; break;
case "digraph": Ctor = Digraph; break;
case "cgraph": Ctor = CGraph; break;
case "cdigraph": Ctor = CDigraph; break;
default: throw new Error("Unrecognized graph type: " + Ctor);
}
}
var graph = new Ctor();
nodes.forEach(function(u) {
graph.addNode(u.id, u.value);
});
// If the graph is compound, set up children...
if (graph.parent) {
nodes.forEach(function(u) {
if (u.children) {
u.children.forEach(function(v) {
graph.parent(v, u.id);
});
}
});
}
edges.forEach(function(e) {
graph.addEdge(e.id, e.u, e.v, e.value);
});
return graph;
};
exports.encode = function(graph) {
var nodes = [];
var edges = [];
graph.eachNode(function(u, value) {
var node = {id: u, value: value};
if (graph.children) {
var children = graph.children(u);
if (children.length) {
node.children = children;
}
}
nodes.push(node);
});
graph.eachEdge(function(e, u, v, value) {
edges.push({id: e, u: u, v: v, value: value});
});
var type;
if (graph instanceof CDigraph) {
type = "cdigraph";
} else if (graph instanceof CGraph) {
type = "cgraph";
} else if (graph instanceof Digraph) {
type = "digraph";
} else if (graph instanceof Graph) {
type = "graph";
} else {
throw new Error("Couldn't determine type of graph: " + graph);
}
return { nodes: nodes, edges: edges, type: type };
};
function typeOf(obj) {
return Object.prototype.toString.call(obj).slice(8, -1);
}
},{"../CDigraph":30,"../CGraph":31,"../Digraph":32,"../Graph":33}],47:[function(require,module,exports){
/* jshint -W079 */
var Set = require("cp-data").Set;
/* jshint +W079 */
exports.all = function() {
return function() { return true; };
};
exports.nodesFromList = function(nodes) {
var set = new Set(nodes);
return function(u) {
return set.has(u);
};
};
},{"cp-data":5}],48:[function(require,module,exports){
var Graph = require("./Graph"),
Digraph = require("./Digraph");
// Side-effect based changes are lousy, but node doesn't seem to resolve the
// requires cycle.
/**
* Returns a new directed graph using the nodes and edges from this graph. The
* new graph will have the same nodes, but will have twice the number of edges:
* each edge is split into two edges with opposite directions. Edge ids,
* consequently, are not preserved by this transformation.
*/
Graph.prototype.toDigraph =
Graph.prototype.asDirected = function() {
var g = new Digraph();
this.eachNode(function(u, value) { g.addNode(u, value); });
this.eachEdge(function(e, u, v, value) {
g.addEdge(null, u, v, value);
g.addEdge(null, v, u, value);
});
return g;
};
/**
* Returns a new undirected graph using the nodes and edges from this graph.
* The new graph will have the same nodes, but the edges will be made
* undirected. Edge ids are preserved in this transformation.
*/
Digraph.prototype.toGraph =
Digraph.prototype.asUndirected = function() {
var g = new Graph();
this.eachNode(function(u, value) { g.addNode(u, value); });
this.eachEdge(function(e, u, v, value) {
g.addEdge(e, u, v, value);
});
return g;
};
},{"./Digraph":32,"./Graph":33}],49:[function(require,module,exports){
// Returns an array of all values for properties of **o**.
exports.values = function(o) {
var ks = Object.keys(o),
len = ks.length,
result = new Array(len),
i;
for (i = 0; i < len; ++i) {
result[i] = o[ks[i]];
}
return result;
};
},{}],50:[function(require,module,exports){
module.exports = '0.7.4';
},{}]},{},[1])
;