/*! JointJS v0.9.7 - JavaScript diagramming library 2016-04-20
|
|
|
This Source Code Form is subject to the terms of the Mozilla Public
|
License, v. 2.0. If a copy of the MPL was not distributed with this
|
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
*/
|
if (typeof exports === 'object') {
|
|
var graphlib = require('graphlib');
|
var dagre = require('dagre');
|
}
|
|
// In the browser, these variables are set to undefined because of JavaScript hoisting.
|
// In that case, should grab them from the window object.
|
graphlib = graphlib || (typeof window !== 'undefined' && window.graphlib);
|
dagre = dagre || (typeof window !== 'undefined' && window.dagre);
|
|
joint.layout.DirectedGraph = {
|
|
layout: function(graphOrCells, opt) {
|
|
var graph;
|
|
if (graphOrCells instanceof joint.dia.Graph) {
|
graph = graphOrCells;
|
} else {
|
graph = (new joint.dia.Graph()).resetCells(graphOrCells);
|
}
|
|
// This is not needed anymore.
|
graphOrCells = null;
|
|
opt = _.defaults(opt || {}, {
|
resizeClusters: true,
|
clusterPadding: 10
|
});
|
|
// create a graphlib.Graph that represents the joint.dia.Graph
|
var glGraph = graph.toGraphLib({
|
directed: true,
|
// We are about to use edge naming feature.
|
multigraph: true,
|
// We are able to layout graphs with embeds.
|
compound: true,
|
setNodeLabel: function(element) {
|
return {
|
width: element.get('size').width,
|
height: element.get('size').height,
|
rank: element.get('rank')
|
};
|
},
|
setEdgeLabel: function(link) {
|
return {
|
minLen: link.get('minLen') || 1
|
};
|
},
|
setEdgeName: function(link) {
|
// Graphlib edges have no ids. We use edge name property
|
// to store and retrieve ids instead.
|
return link.id;
|
}
|
});
|
|
var glLabel = {};
|
|
// Dagre layout accepts options as lower case.
|
// Direction for rank nodes. Can be TB, BT, LR, or RL
|
if (opt.rankDir) glLabel.rankdir = opt.rankDir;
|
// Alignment for rank nodes. Can be UL, UR, DL, or DR
|
if (opt.align) glLabel.align = opt.align;
|
// Number of pixels that separate nodes horizontally in the layout.
|
if (opt.nodeSep) glLabel.nodesep = opt.nodeSep;
|
// Number of pixels that separate edges horizontally in the layout.
|
if (opt.edgeSep) glLabel.edgesep = opt.edgeSep;
|
// Number of pixels between each rank in the layout.
|
if (opt.rankSep) glLabel.ranksep = opt.rankSep;
|
// Number of pixels to use as a margin around the left and right of the graph.
|
if (opt.marginX) glLabel.marginx = opt.marginX;
|
// Number of pixels to use as a margin around the top and bottom of the graph.
|
if (opt.marginY) glLabel.marginy = opt.marginY;
|
|
// Set the option object for the graph label.
|
glGraph.setGraph(glLabel);
|
|
// Executes the layout.
|
dagre.layout(glGraph, { debugTiming: !!opt.debugTiming });
|
|
// Wrap all graph changes into a batch.
|
graph.startBatch('layout');
|
|
// Update the graph.
|
graph.fromGraphLib(glGraph, {
|
importNode: function(v, gl) {
|
|
var element = this.getCell(v);
|
var glNode = gl.node(v);
|
|
if (opt.setPosition) {
|
opt.setPosition(element, glNode);
|
} else {
|
element.set('position', {
|
x: glNode.x - glNode.width / 2,
|
y: glNode.y - glNode.height / 2
|
});
|
}
|
},
|
importEdge: function(edgeObj, gl) {
|
|
var link = this.getCell(edgeObj.name);
|
var glEdge = gl.edge(edgeObj);
|
var points = glEdge.points || [];
|
|
if (opt.setLinkVertices) {
|
if (opt.setVertices) {
|
opt.setVertices(link, points);
|
} else {
|
// Remove the first and last point from points array.
|
// Those are source/target element connection points
|
// ie. they lies on the edge of connected elements.
|
link.set('vertices', points.slice(1, points.length - 1));
|
}
|
}
|
}
|
});
|
|
if (opt.resizeClusters) {
|
// Resize and reposition cluster elements (parents of other elements)
|
// to fit their children.
|
// 1. filter clusters only
|
// 2. map id on cells
|
// 3. sort cells by their depth (the deepest first)
|
// 4. resize cell to fit their direct children only.
|
_.chain(glGraph.nodes())
|
.filter(function(v) { return glGraph.children(v).length > 0; })
|
.map(graph.getCell, graph)
|
.sortBy(function(cluster) { return -cluster.getAncestors().length; })
|
.invoke('fitEmbeds', { padding: opt.clusterPadding })
|
.value();
|
}
|
|
graph.stopBatch('layout');
|
|
// Return an object with height and width of the graph.
|
return glGraph.graph();
|
},
|
|
fromGraphLib: function(glGraph, opt) {
|
|
opt = opt || {};
|
|
var importNode = opt.importNode || _.noop;
|
var importEdge = opt.importEdge || _.noop;
|
var graph = this instanceof joint.dia.Graph ? this : new joint.dia.Graph;
|
|
// Import all nodes.
|
glGraph.nodes().forEach(function(node) {
|
importNode.call(graph, node, glGraph, graph, opt);
|
});
|
|
// Import all edges.
|
glGraph.edges().forEach(function(edge) {
|
importEdge.call(graph, edge, glGraph, graph, opt);
|
});
|
|
return graph;
|
},
|
|
// Create new graphlib graph from existing JointJS graph.
|
toGraphLib: function(graph, opt) {
|
|
opt = opt || {};
|
|
var glGraphType = _.pick(opt, 'directed', 'compound', 'multigraph');
|
var glGraph = new graphlib.Graph(glGraphType);
|
var setNodeLabel = opt.setNodeLabel || _.noop;
|
var setEdgeLabel = opt.setEdgeLabel || _.noop;
|
var setEdgeName = opt.setEdgeName || _.noop;
|
|
graph.get('cells').each(function(cell) {
|
|
if (cell.isLink()) {
|
|
var source = cell.get('source');
|
var target = cell.get('target');
|
|
// Links that end at a point are ignored.
|
if (!source.id || !target.id) return;
|
|
// Note that if we are creating a multigraph we can name the edges. If
|
// we try to name edges on a non-multigraph an exception is thrown.
|
glGraph.setEdge(source.id, target.id, setEdgeLabel(cell), setEdgeName(cell));
|
|
} else {
|
|
glGraph.setNode(cell.id, setNodeLabel(cell));
|
|
// For the compound graphs we have to take embeds into account.
|
if (glGraph.isCompound() && cell.has('parent')) {
|
glGraph.setParent(cell.id, cell.get('parent'));
|
}
|
}
|
});
|
|
return glGraph;
|
}
|
};
|
|
joint.dia.Graph.prototype.toGraphLib = function(opt) {
|
|
return joint.layout.DirectedGraph.toGraphLib(this, opt);
|
};
|
|
joint.dia.Graph.prototype.fromGraphLib = function(glGraph, opt) {
|
|
return joint.layout.DirectedGraph.fromGraphLib.call(this, glGraph, opt);
|
};
|