@progress/kendo-ui
Version:
This package is part of the [Kendo UI for jQuery](http://www.telerik.com/kendo-ui) suite.
3,810 lines • 151 kB
JavaScript
module.exports =
/******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId])
/******/ return installedModules[moduleId].exports;
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ exports: {},
/******/ id: moduleId,
/******/ loaded: false
/******/ };
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/ // Flag the module as loaded
/******/ module.loaded = true;
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/ // Load entry module and return exports
/******/ return __webpack_require__(0);
/******/ })
/************************************************************************/
/******/ ({
/***/ 0:
/***/ (function(module, exports, __webpack_require__) {
module.exports = __webpack_require__(876);
/***/ }),
/***/ 3:
/***/ (function(module, exports) {
module.exports = function() { throw new Error("define cannot be used indirect"); };
/***/ }),
/***/ 876:
/***/ (function(module, exports, __webpack_require__) {
var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;(function(f, define){
!(__WEBPACK_AMD_DEFINE_ARRAY__ = [ __webpack_require__(877) ], __WEBPACK_AMD_DEFINE_FACTORY__ = (f), __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? (__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
})(function(){
(function ($, undefined) {
var kendo = window.kendo,
diagram = kendo.dataviz.diagram,
Graph = diagram.Graph,
Node = diagram.Node,
Link = diagram.Link,
deepExtend = kendo.deepExtend,
Size = diagram.Size,
Rect = diagram.Rect,
Dictionary = diagram.Dictionary,
Set = diagram.Set,
HyperTree = diagram.Graph,
Utils = diagram.Utils,
Point = diagram.Point,
EPSILON = 1e-06,
DEG_TO_RAD = Math.PI / 180,
contains = Utils.contains,
grep = $.grep;
/**
* Base class for layout algorithms.
* @type {*}
*/
var LayoutBase = kendo.Class.extend({
defaultOptions: {
type: "Tree",
subtype: "Down",
roots: null,
animate: false,
//-------------------------------------------------------------------
/**
* Force-directed option: whether the motion of the nodes should be limited by the boundaries of the diagram surface.
*/
limitToView: false,
/**
* Force-directed option: the amount of friction applied to the motion of the nodes.
*/
friction: 0.9,
/**
* Force-directed option: the optimal distance between nodes (minimum energy).
*/
nodeDistance: 50,
/**
* Force-directed option: the number of time things are being calculated.
*/
iterations: 300,
//-------------------------------------------------------------------
/**
* Tree option: the separation in one direction (depends on the subtype what direction this is).
*/
horizontalSeparation: 90,
/**
* Tree option: the separation in the complementary direction (depends on the subtype what direction this is).
*/
verticalSeparation: 50,
//-------------------------------------------------------------------
/**
* Tip-over tree option: children-to-parent vertical distance.
*/
underneathVerticalTopOffset: 15,
/**
* Tip-over tree option: children-to-parent horizontal distance.
*/
underneathHorizontalOffset: 15,
/**
* Tip-over tree option: leaf-to-next-branch vertical distance.
*/
underneathVerticalSeparation: 15,
//-------------------------------------------------------------------
/**
* Settings object to organize the different components of the diagram in a grid layout structure
*/
grid: {
/**
* The width of the grid in which components are arranged. Beyond this width a component will be on the next row.
*/
width: 1500,
/**
* The left offset of the grid.
*/
offsetX: 50,
/**
* The top offset of the grid.
*/
offsetY: 50,
/**
* The horizontal padding within a cell of the grid where a single component resides.
*/
componentSpacingX: 20,
/**
* The vertical padding within a cell of the grid where a single component resides.
*/
componentSpacingY: 20
},
//-------------------------------------------------------------------
/**
* Layered option: the separation height/width between the layers.
*/
layerSeparation: 50,
/**
* Layered option: how many rounds of shifting and fine-tuning.
*/
layeredIterations: 2,
/**
* Tree-radial option: the angle at which the layout starts.
*/
startRadialAngle: 0,
/**
* Tree-radial option: the angle at which the layout starts.
*/
endRadialAngle: 360,
/**
* Tree-radial option: the separation between levels.
*/
radialSeparation: 150,
/**
* Tree-radial option: the separation between the root and the first level.
*/
radialFirstLevelSeparation: 200,
/**
* Tree-radial option: whether a virtual roots bing the components in one radial layout.
*/
keepComponentsInOneRadialLayout: false,
//-------------------------------------------------------------------
// TODO: ensure to change this to false when containers are around
ignoreContainers: true,
layoutContainerChildren: false,
ignoreInvisible: true,
animateTransitions: false
},
init: function () {
},
/**
* Organizes the components in a grid.
* Returns the final set of nodes (not the Graph).
* @param components
*/
gridLayoutComponents: function (components) {
if (!components) {
throw "No components supplied.";
}
// calculate and cache the bounds of the components
Utils.forEach(components, function (c) {
c.calcBounds();
});
// order by decreasing width
components.sort(function (a, b) {
return b.bounds.width - a.bounds.width;
});
var maxWidth = this.options.grid.width,
offsetX = this.options.grid.componentSpacingX,
offsetY = this.options.grid.componentSpacingY,
height = 0,
startX = this.options.grid.offsetX,
startY = this.options.grid.offsetY,
x = startX,
y = startY,
i,
resultLinkSet = [],
resultNodeSet = [];
while (components.length > 0) {
if (x >= maxWidth) {
// start a new row
x = startX;
y += height + offsetY;
// reset the row height
height = 0;
}
var component = components.pop();
this.moveToOffset(component, new Point(x, y));
for (i = 0; i < component.nodes.length; i++) {
resultNodeSet.push(component.nodes[i]); // to be returned in the end
}
for (i = 0; i < component.links.length; i++) {
resultLinkSet.push(component.links[i]);
}
var boundingRect = component.bounds;
var currentHeight = boundingRect.height;
if (currentHeight <= 0 || isNaN(currentHeight)) {
currentHeight = 0;
}
var currentWidth = boundingRect.width;
if (currentWidth <= 0 || isNaN(currentWidth)) {
currentWidth = 0;
}
if (currentHeight >= height) {
height = currentHeight;
}
x += currentWidth + offsetX;
}
return {
nodes: resultNodeSet,
links: resultLinkSet
};
},
moveToOffset: function (component, p) {
var i, j,
bounds = component.bounds,
deltax = p.x - bounds.x,
deltay = p.y - bounds.y;
for (i = 0; i < component.nodes.length; i++) {
var node = component.nodes[i];
var nodeBounds = node.bounds();
if (nodeBounds.width === 0 && nodeBounds.height === 0 && nodeBounds.x === 0 && nodeBounds.y === 0) {
nodeBounds = new Rect(0, 0, 0, 0);
}
nodeBounds.x += deltax;
nodeBounds.y += deltay;
node.bounds(nodeBounds);
}
for (i = 0; i < component.links.length; i++) {
var link = component.links[i];
if (link.points) {
var newpoints = [];
var points = link.points;
for (j = 0; j < points.length; j++) {
var pt = points[j];
pt.x += deltax;
pt.y += deltay;
newpoints.push(pt);
}
link.points = newpoints;
}
}
this.currentHorizontalOffset += bounds.width + this.options.grid.offsetX;
return new Point(deltax, deltay);
},
transferOptions: function (options) {
// Size options lead to stackoverflow and need special handling
this.options = kendo.deepExtend({}, this.defaultOptions);
if (Utils.isUndefined(options)) {
return;
}
this.options = kendo.deepExtend(this.options, options || {});
}
});
/**
* The data bucket a hypertree holds in its nodes. *
* @type {*}
*/
/* var ContainerGraph = kendo.Class.extend({
init: function (diagram) {
this.diagram = diagram;
this.graph = new Graph(diagram);
this.container = null;
this.containerNode = null;
}
});*/
/**
* Adapter between the diagram control and the graph representation. It converts shape and connections to nodes and edges taking into the containers and their collapsef state,
* the visibility of items and more. If the layoutContainerChildren is true a hypertree is constructed which holds the hierarchy of containers and many conditions are analyzed
* to investigate how the effective graph structure looks like and how the layout has to be performed.
* @type {*}
*/
var DiagramToHyperTreeAdapter = kendo.Class.extend({
init: function (diagram) {
/**
* The mapping to/from the original nodes.
* @type {Dictionary}
*/
this.nodeMap = new Dictionary();
/**
* Gets the mapping of a shape to a container in case the shape sits in a collapsed container.
* @type {Dictionary}
*/
this.shapeMap = new Dictionary();
/**
* The nodes being mapped.
* @type {Dictionary}
*/
this.nodes = [];
/**
* The connections being mapped.
* @type {Dictionary}
*/
this.edges = [];
// the mapping from an edge to all the connections it represents, this can be both because of multiple connections between
// two shapes or because a container holds multiple connections to another shape or container.
this.edgeMap = new Dictionary();
/**
* The resulting set of Nodes when the analysis has finished.
* @type {Array}
*/
this.finalNodes = [];
/**
* The resulting set of Links when the analysis has finished.
* @type {Array}
*/
this.finalLinks = [];
/**
* The items being omitted because of multigraph edges.
* @type {Array}
*/
this.ignoredConnections = [];
/**
* The items being omitted because of containers, visibility and other factors.
* @type {Array}
*/
this.ignoredShapes = [];
/**
* The map from a node to the partition/hypernode in which it sits. This hyperMap is null if 'options.layoutContainerChildren' is false.
* @type {Dictionary}
*/
this.hyperMap = new Dictionary();
/**
* The hypertree contains the hierarchy defined by the containers.
* It's in essence a Graph of Graphs with a tree structure defined by the hierarchy of containers.
* @type {HyperTree}
*/
this.hyperTree = new Graph();
/**
* The resulting graph after conversion. Note that this does not supply the information contained in the
* ignored connection and shape collections.
* @type {null}
*/
this.finalGraph = null;
this.diagram = diagram;
},
/**
* The hyperTree is used when the 'options.layoutContainerChildren' is true. It contains the hierarchy of containers whereby each node is a ContainerGraph.
* This type of node has a Container reference to the container which holds the Graph items. There are three possible situations during the conversion process:
* - Ignore the containers: the container are non-existent and only normal shapes are mapped. If a shape has a connection to a container it will be ignored as well
* since there is no node mapped for the container.
* - Do not ignore the containers and leave the content of the containers untouched: the top-level elements are being mapped and the children within a container are not altered.
* - Do not ignore the containers and organize the content of the containers as well: the hypertree is constructed and there is a partitioning of all nodes and connections into the hypertree.
* The only reason a connection or node is not being mapped might be due to the visibility, which includes the visibility change through a collapsed parent container.
* @param options
*/
convert: function (options) {
if (Utils.isUndefined(this.diagram)) {
throw "No diagram to convert.";
}
this.options = kendo.deepExtend({
ignoreInvisible: true,
ignoreContainers: true,
layoutContainerChildren: false
},
options || {}
);
this.clear();
// create the nodes which participate effectively in the graph analysis
this._renormalizeShapes();
// recreate the incoming and outgoing collections of each and every node
this._renormalizeConnections();
// export the resulting graph
this.finalNodes = new Dictionary(this.nodes);
this.finalLinks = new Dictionary(this.edges);
this.finalGraph = new Graph();
this.finalNodes.forEach(function (n) {
this.finalGraph.addNode(n);
}, this);
this.finalLinks.forEach(function (l) {
this.finalGraph.addExistingLink(l);
}, this);
return this.finalGraph;
},
/**
* Maps the specified connection to an edge of the graph deduced from the given diagram.
* @param connection
* @returns {*}
*/
mapConnection: function (connection) {
return this.edgeMap.get(connection.id);
},
/**
* Maps the specified shape to a node of the graph deduced from the given diagram.
* @param shape
* @returns {*}
*/
mapShape: function (shape) {
return this.nodeMap.get(shape.id);
},
/**
* Gets the edge, if any, between the given nodes.
* @param a
* @param b
*/
getEdge: function (a, b) {
return Utils.first(a.links, function (link) {
return link.getComplement(a) === b;
});
},
/**
* Clears all the collections used by the conversion process.
*/
clear: function () {
this.finalGraph = null;
this.hyperTree = (!this.options.ignoreContainers && this.options.layoutContainerChildren) ? new HyperTree() : null;
this.hyperMap = (!this.options.ignoreContainers && this.options.layoutContainerChildren) ? new Dictionary() : null;
this.nodeMap = new Dictionary();
this.shapeMap = new Dictionary();
this.nodes = [];
this.edges = [];
this.edgeMap = new Dictionary();
this.ignoredConnections = [];
this.ignoredShapes = [];
this.finalNodes = [];
this.finalLinks = [];
},
/**
* The path from a given ContainerGraph to the root (container).
* @param containerGraph
* @returns {Array}
*/
listToRoot: function (containerGraph) {
var list = [];
var s = containerGraph.container;
if (!s) {
return list;
}
list.push(s);
while (s.parentContainer) {
s = s.parentContainer;
list.push(s);
}
list.reverse();
return list;
},
firstNonIgnorableContainer: function (shape) {
if (shape.isContainer && !this._isIgnorableItem(shape)) {
return shape;
}
return !shape.parentContainer ? null : this.firstNonIgnorableContainer(shape.parentContainer);
},
isContainerConnection: function (a, b) {
if (a.isContainer && this.isDescendantOf(a, b)) {
return true;
}
return b.isContainer && this.isDescendantOf(b, a);
},
/**
* Returns true if the given shape is a direct child or a nested container child of the given container.
* If the given container and shape are the same this will return false since a shape cannot be its own child.
* @param scope
* @param a
* @returns {boolean}
*/
isDescendantOf: function (scope, a) {
if (!scope.isContainer) {
throw "Expecting a container.";
}
if (scope === a) {
return false;
}
if (contains(scope.children, a)) {
return true;
}
var containers = [];
for (var i = 0, len = scope.children.length; i < len; i++) {
var c = scope.children[i];
if (c.isContainer && this.isDescendantOf(c, a)) {
containers.push(c);
}
}
return containers.length > 0;
},
isIgnorableItem: function (shape) {
if (this.options.ignoreInvisible) {
if (shape.isCollapsed && this._isVisible(shape)) {
return false;
}
if (!shape.isCollapsed && this._isVisible(shape)) {
return false;
}
return true;
}
else {
return shape.isCollapsed && !this._isTop(shape);
}
},
/**
* Determines whether the shape is or needs to be mapped to another shape. This occurs essentially when the shape sits in
* a collapsed container hierarchy and an external connection needs a node endpoint. This node then corresponds to the mapped shape and is
* necessarily a container in the parent hierarchy of the shape.
* @param shape
*/
isShapeMapped: function (shape) {
return shape.isCollapsed && !this._isVisible(shape) && !this._isTop(shape);
},
leastCommonAncestor: function (a, b) {
if (!a) {
throw "Parameter should not be null.";
}
if (!b) {
throw "Parameter should not be null.";
}
if (!this.hyperTree) {
throw "No hypertree available.";
}
var al = this.listToRoot(a);
var bl = this.listToRoot(b);
var found = null;
if (Utils.isEmpty(al) || Utils.isEmpty(bl)) {
return this.hyperTree.root.data;
}
var xa = al[0];
var xb = bl[0];
var i = 0;
while (xa === xb) {
found = al[i];
i++;
if (i >= al.length || i >= bl.length) {
break;
}
xa = al[i];
xb = bl[i];
}
if (!found) {
return this.hyperTree.root.data;
}
else {
return grep(this.hyperTree.nodes, function (n) {
return n.data.container === found;
});
}
},
/**
* Determines whether the specified item is a top-level shape or container.
* @param item
* @returns {boolean}
* @private
*/
_isTop: function (item) {
return !item.parentContainer;
},
/**
* Determines iteratively (by walking up the container stack) whether the specified shape is visible.
* This does NOT tell whether the item is not visible due to an explicit Visibility change or due to a collapse state.
* @param shape
* @returns {*}
* @private
*/
_isVisible: function (shape) {
if (!shape.visible()) {
return false;
}
return !shape.parentContainer ? shape.visible() : this._isVisible(shape.parentContainer);
},
_isCollapsed: function (shape) {
if (shape.isContainer && shape.isCollapsed) {
return true;
}
return shape.parentContainer && this._isCollapsed(shape.parentContainer);
},
/**
* First part of the graph creation; analyzing the shapes and containers and deciding whether they should be mapped to a Node.
* @private
*/
_renormalizeShapes: function () {
// add the nodes, the adjacency structure will be reconstructed later on
if (this.options.ignoreContainers) {
for (var i = 0, len = this.diagram.shapes.length; i < len; i++) {
var shape = this.diagram.shapes[i];
// if not visible (and ignoring the invisible ones) or a container we skip
if ((this.options.ignoreInvisible && !this._isVisible(shape)) || shape.isContainer) {
this.ignoredShapes.push(shape);
continue;
}
var node = new Node(shape.id, shape);
node.isVirtual = false;
// the mapping will always contain singletons and the hyperTree will be null
this.nodeMap.add(shape.id, node);
this.nodes.push(node);
}
}
else {
throw "Containers are not supported yet, but stay tuned.";
}
},
/**
* Second part of the graph creation; analyzing the connections and deciding whether they should be mapped to an edge.
* @private
*/
_renormalizeConnections: function () {
if (this.diagram.connections.length === 0) {
return;
}
for (var i = 0, len = this.diagram.connections.length; i < len; i++) {
var conn = this.diagram.connections[i];
if (this.isIgnorableItem(conn)) {
this.ignoredConnections.push(conn);
continue;
}
var source = !conn.sourceConnector ? null : conn.sourceConnector.shape;
var sink = !conn.targetConnector ? null : conn.targetConnector.shape;
// no layout for floating connections
if (!source || !sink) {
this.ignoredConnections.push(conn);
continue;
}
if (contains(this.ignoredShapes, source) && !this.shapeMap.containsKey(source)) {
this.ignoredConnections.push(conn);
continue;
}
if (contains(this.ignoredShapes, sink) && !this.shapeMap.containsKey(sink)) {
this.ignoredConnections.push(conn);
continue;
}
// if the endpoint sits in a collapsed container we need the container rather than the shape itself
if (this.shapeMap.containsKey(source)) {
source = this.shapeMap[source];
}
if (this.shapeMap.containsKey(sink)) {
sink = this.shapeMap[sink];
}
var sourceNode = this.mapShape(source);
var sinkNode = this.mapShape(sink);
if ((sourceNode === sinkNode) || this.areConnectedAlready(sourceNode, sinkNode)) {
this.ignoredConnections.push(conn);
continue;
}
if (sourceNode === null || sinkNode === null) {
throw "A shape was not mapped to a node.";
}
if (this.options.ignoreContainers) {
// much like a floating connection here since at least one end is attached to a container
if (sourceNode.isVirtual || sinkNode.isVirtual) {
this.ignoredConnections.push(conn);
continue;
}
var newEdge = new Link(sourceNode, sinkNode, conn.id, conn);
this.edgeMap.add(conn.id, newEdge);
this.edges.push(newEdge);
}
else {
throw "Containers are not supported yet, but stay tuned.";
}
}
},
areConnectedAlready: function (n, m) {
return Utils.any(this.edges, function (l) {
return l.source === n && l.target === m || l.source === m && l.target === n;
});
}
/**
* Depth-first traversal of the given container.
* @param container
* @param action
* @param includeStart
* @private
*/
/* _visitContainer: function (container, action, includeStart) {
*//*if (container == null) throw new ArgumentNullException("container");
if (action == null) throw new ArgumentNullException("action");
if (includeStart) action(container);
if (container.children.isEmpty()) return;
foreach(
var item
in
container.children.OfType < IShape > ()
)
{
var childContainer = item
as
IContainerShape;
if (childContainer != null) this.VisitContainer(childContainer, action);
else action(item);
}*//*
}*/
});
/**
* The classic spring-embedder (aka force-directed, Fruchterman-Rheingold, barycentric) algorithm.
* http://en.wikipedia.org/wiki/Force-directed_graph_drawing
* - Chapter 12 of Tamassia et al. "Handbook of graph drawing and visualization".
* - Kobourov on preprint arXiv; http://arxiv.org/pdf/1201.3011.pdf
* - Fruchterman and Rheingold in SOFTWARE-PRACTICE AND EXPERIENCE, VOL. 21(1 1), 1129-1164 (NOVEMBER 1991)
* @type {*}
*/
var SpringLayout = LayoutBase.extend({
init: function (diagram) {
var that = this;
LayoutBase.fn.init.call(that);
if (Utils.isUndefined(diagram)) {
throw "Diagram is not specified.";
}
this.diagram = diagram;
},
layout: function (options) {
this.transferOptions(options);
var adapter = new DiagramToHyperTreeAdapter(this.diagram);
var graph = adapter.convert(options);
if (graph.isEmpty()) {
return;
}
// split into connected components
var components = graph.getConnectedComponents();
if (Utils.isEmpty(components)) {
return;
}
for (var i = 0; i < components.length; i++) {
var component = components[i];
this.layoutGraph(component, options);
}
var finalNodeSet = this.gridLayoutComponents(components);
return new diagram.LayoutState(this.diagram, finalNodeSet);
},
layoutGraph: function (graph, options) {
if (Utils.isDefined(options)) {
this.transferOptions(options);
}
this.graph = graph;
var initialTemperature = this.options.nodeDistance * 9;
this.temperature = initialTemperature;
var guessBounds = this._expectedBounds();
this.width = guessBounds.width;
this.height = guessBounds.height;
for (var step = 0; step < this.options.iterations; step++) {
this.refineStage = step >= this.options.iterations * 5 / 6;
this.tick();
// exponential cooldown
this.temperature = this.refineStage ?
initialTemperature / 30 :
initialTemperature * (1 - step / (2 * this.options.iterations ));
}
},
/**
* Single iteration of the simulation.
*/
tick: function () {
var i;
// collect the repulsive forces on each node
for (i = 0; i < this.graph.nodes.length; i++) {
this._repulsion(this.graph.nodes[i]);
}
// collect the attractive forces on each node
for (i = 0; i < this.graph.links.length; i++) {
this._attraction(this.graph.links[i]);
}
// update the positions
for (i = 0; i < this.graph.nodes.length; i++) {
var node = this.graph.nodes[i];
var offset = Math.sqrt(node.dx * node.dx + node.dy * node.dy);
if (offset === 0) {
return;
}
node.x += Math.min(offset, this.temperature) * node.dx / offset;
node.y += Math.min(offset, this.temperature) * node.dy / offset;
if (this.options.limitToView) {
node.x = Math.min(this.width, Math.max(node.width / 2, node.x));
node.y = Math.min(this.height, Math.max(node.height / 2, node.y));
}
}
},
/**
* Shakes the node away from its current position to escape the deadlock.
* @param node A Node.
* @private
*/
_shake: function (node) {
// just a simple polar neighborhood
var rho = Math.random() * this.options.nodeDistance / 4;
var alpha = Math.random() * 2 * Math.PI;
node.x += rho * Math.cos(alpha);
node.y -= rho * Math.sin(alpha);
},
/**
* The typical Coulomb-Newton force law F=k/r^2
* @remark This only works in dimensions less than three.
* @param d
* @param n A Node.
* @param m Another Node.
* @returns {number}
* @private
*/
_InverseSquareForce: function (d, n, m) {
var force;
if (!this.refineStage) {
force = Math.pow(d, 2) / Math.pow(this.options.nodeDistance, 2);
}
else {
var deltax = n.x - m.x;
var deltay = n.y - m.y;
var wn = n.width / 2;
var hn = n.height / 2;
var wm = m.width / 2;
var hm = m.height / 2;
force = (Math.pow(deltax, 2) / Math.pow(wn + wm + this.options.nodeDistance, 2)) + (Math.pow(deltay, 2) / Math.pow(hn + hm + this.options.nodeDistance, 2));
}
return force * 4 / 3;
},
/**
* The typical Hooke force law F=kr^2
* @param d
* @param n
* @param m
* @returns {number}
* @private
*/
_SquareForce: function (d, n, m) {
return 1 / this._InverseSquareForce(d, n, m);
},
_repulsion: function (n) {
n.dx = 0;
n.dy = 0;
Utils.forEach(this.graph.nodes, function (m) {
if (m === n) {
return;
}
while (n.x === m.x && n.y === m.y) {
this._shake(m);
}
var vx = n.x - m.x;
var vy = n.y - m.y;
var distance = Math.sqrt(vx * vx + vy * vy);
var r = this._SquareForce(distance, n, m) * 2;
n.dx += (vx / distance) * r;
n.dy += (vy / distance) * r;
}, this);
},
_attraction: function (link) {
var t = link.target;
var s = link.source;
if (s === t) {
// loops induce endless shakes
return;
}
while (s.x === t.x && s.y === t.y) {
this._shake(t);
}
var vx = s.x - t.x;
var vy = s.y - t.y;
var distance = Math.sqrt(vx * vx + vy * vy);
var a = this._InverseSquareForce(distance, s, t) * 5;
var dx = (vx / distance) * a;
var dy = (vy / distance) * a;
t.dx += dx;
t.dy += dy;
s.dx -= dx;
s.dy -= dy;
},
/**
* Calculates the expected bounds after layout.
* @returns {*}
* @private
*/
_expectedBounds: function () {
var size, N = this.graph.nodes.length, /*golden ration optimal?*/ ratio = 1.5, multiplier = 4;
if (N === 0) {
return size;
}
size = Utils.fold(this.graph.nodes, function (s, node) {
var area = node.width * node.height;
if (area > 0) {
s += Math.sqrt(area);
return s;
}
return 0;
}, 0, this);
var av = size / N;
var squareSize = av * Math.ceil(Math.sqrt(N));
var width = squareSize * Math.sqrt(ratio);
var height = squareSize / Math.sqrt(ratio);
return { width: width * multiplier, height: height * multiplier };
}
});
var TreeLayoutProcessor = kendo.Class.extend({
init: function (options) {
this.center = null;
this.options = options;
},
layout: function (treeGraph, root) {
this.graph = treeGraph;
if (!this.graph.nodes || this.graph.nodes.length === 0) {
return;
}
if (!contains(this.graph.nodes, root)) {
throw "The given root is not in the graph.";
}
this.center = root;
this.graph.cacheRelationships();
/* var nonull = this.graph.nodes.where(function (n) {
return n.associatedShape != null;
});*/
// transfer the rects
/*nonull.forEach(function (n) {
n.Location = n.associatedShape.Position;
n.NodeSize = n.associatedShape.ActualBounds.ToSize();
}
);*/
// caching the children
/* nonull.forEach(function (n) {
n.children = n.getChildren();
});*/
this.layoutSwitch();
// apply the layout to the actual visuals
// nonull.ForEach(n => n.associatedShape.Position = n.Location);
},
layoutLeft: function (left) {
this.setChildrenDirection(this.center, "Left", false);
this.setChildrenLayout(this.center, "Default", false);
var h = 0, w = 0, y, i, node;
for (i = 0; i < left.length; i++) {
node = left[i];
node.TreeDirection = "Left";
var s = this.measure(node, Size.Empty);
w = Math.max(w, s.Width);
h += s.height + this.options.verticalSeparation;
}
h -= this.options.verticalSeparation;
var x = this.center.x - this.options.horizontalSeparation;
y = this.center.y + ((this.center.height - h) / 2);
for (i = 0; i < left.length; i++) {
node = left[i];
var p = new Point(x - node.Size.width, y);
this.arrange(node, p);
y += node.Size.height + this.options.verticalSeparation;
}
},
layoutRight: function (right) {
this.setChildrenDirection(this.center, "Right", false);
this.setChildrenLayout(this.center, "Default", false);
var h = 0, w = 0, y, i, node;
for (i = 0; i < right.length; i++) {
node = right[i];
node.TreeDirection = "Right";
var s = this.measure(node, Size.Empty);
w = Math.max(w, s.Width);
h += s.height + this.options.verticalSeparation;
}
h -= this.options.verticalSeparation;
var x = this.center.x + this.options.horizontalSeparation + this.center.width;
y = this.center.y + ((this.center.height - h) / 2);
for (i = 0; i < right.length; i++) {
node = right[i];
var p = new Point(x, y);
this.arrange(node, p);
y += node.Size.height + this.options.verticalSeparation;
}
},
layoutUp: function (up) {
this.setChildrenDirection(this.center, "Up", false);
this.setChildrenLayout(this.center, "Default", false);
var w = 0, y, node, i;
for (i = 0; i < up.length; i++) {
node = up[i];
node.TreeDirection = "Up";
var s = this.measure(node, Size.Empty);
w += s.width + this.options.horizontalSeparation;
}
w -= this.options.horizontalSeparation;
var x = this.center.x + (this.center.width / 2) - (w / 2);
// y = this.center.y -verticalSeparation -this.center.height/2 - h;
for (i = 0; i < up.length; i++) {
node = up[i];
y = this.center.y - this.options.verticalSeparation - node.Size.height;
var p = new Point(x, y);
this.arrange(node, p);
x += node.Size.width + this.options.horizontalSeparation;
}
},
layoutDown: function (down) {
var node, i;
this.setChildrenDirection(this.center, "Down", false);
this.setChildrenLayout(this.center, "Default", false);
var w = 0, y;
for (i = 0; i < down.length; i++) {
node = down[i];
node.treeDirection = "Down";
var s = this.measure(node, Size.Empty);
w += s.width + this.options.horizontalSeparation;
}
w -= this.options.horizontalSeparation;
var x = this.center.x + (this.center.width / 2) - (w / 2);
y = this.center.y + this.options.verticalSeparation + this.center.height;
for (i = 0; i < down.length; i++) {
node = down[i];
var p = new Point(x, y);
this.arrange(node, p);
x += node.Size.width + this.options.horizontalSeparation;
}
},
layoutRadialTree: function () {
// var rmax = children.Aggregate(0D, (current, node) => Math.max(node.SectorAngle, current));
this.setChildrenDirection(this.center, "Radial", false);
this.setChildrenLayout(this.center, "Default", false);
this.previousRoot = null;
var startAngle = this.options.startRadialAngle * DEG_TO_RAD;
var endAngle = this.options.endRadialAngle * DEG_TO_RAD;
if (endAngle <= startAngle) {
throw "Final angle should not be less than the start angle.";
}
this.maxDepth = 0;
this.origin = new Point(this.center.x, this.center.y);
this.calculateAngularWidth(this.center, 0);
// perform the layout
if (this.maxDepth > 0) {
this.radialLayout(this.center, this.options.radialFirstLevelSeparation, startAngle, endAngle);
}
// update properties of the root node
this.center.Angle = endAngle - startAngle;
},
tipOverTree: function (down, startFromLevel) {
if (Utils.isUndefined(startFromLevel)) {
startFromLevel = 0;
}
this.setChildrenDirection(this.center, "Down", false);
this.setChildrenLayout(this.center, "Default", false);
this.setChildrenLayout(this.center, "Underneath", false, startFromLevel);
var w = 0, y, node, i;
for (i = 0; i < down.length; i++) {
node = down[i];
// if (node.IsSpecial) continue;
node.TreeDirection = "Down";
var s = this.measure(node, Size.Empty);
w += s.width + this.options.horizontalSeparation;
}
w -= this.options.horizontalSeparation;
// putting the root in the center with respect to the whole diagram is not a nice result, let's put it with respect to the first level only
w -= down[down.length - 1].width;
w += down[down.length - 1].associatedShape.bounds().width;
var x = this.center.x + (this.center.width / 2) - (w / 2);
y = this.center.y + this.options.verticalSeparation + this.center.height;
for (i = 0; i < down.length; i++) {
node = down[i];
// if (node.IsSpecial) continue;
var p = new Point(x, y);
this.arrange(node, p);
x += node.Size.width + this.options.horizontalSeparation;
}
/*//let's place the special node, assuming there is only one
if (down.Count(n => n.IsSpecial) > 0)
{
var special = (from n in down where n.IsSpecial select n).First();
if (special.Children.Count > 0)
throw new DiagramException("The 'special' element should not have children.");
special.Data.Location = new Point(Center.Data.Location.X + Center.AssociatedShape.BoundingRectangle.Width + this.options.HorizontalSeparation, Center.Data.Location.Y);
}*/
},
calculateAngularWidth: function (n, d) {
if (d > this.maxDepth) {
this.maxDepth = d;
}
var aw = 0, w = 1000, h = 1000, diameter = d === 0 ? 0 : Math.sqrt((w * w) + (h * h)) / d;
if (n.children.length > 0) {
// eventually with n.IsExpanded
for (var i = 0, len = n.children.length; i < len; i++) {
var child = n.children[i];
aw += this.calculateAngularWidth(child, d + 1);
}
aw = Math.max(diameter, aw);
}
else {
aw = diameter;
}
n.sectorAngle = aw;
return aw;
},
sortChildren: function (n) {
var basevalue = 0, i;
// update basevalue angle for node ordering
if (n.parents.length > 1) {
throw "Node is not part of a tree.";
}
var p = n.parents[0];
if (p) {
var pl = new Point(p.x, p.y);
var nl = new Point(n.x, n.y);
basevalue = this.normalizeAngle(Math.atan2(pl.y - nl.y, pl.x - nl.x));
}
var count = n.children.length;
if (count === 0) {
return null;
}
var angle = [];
var idx = [];
for (i = 0; i < count; ++i) {
var c = n.children[i];
var l = new Point(c.x, c.y);
idx[i] = i;
angle[i] = this.normalizeAngle(-basevalue + Math.atan2(l.y - l.y, l.x - l.x));
}
Utils.bisort(angle, idx);
var col = []; // list of nodes
var children = n.children;
for (i = 0; i < count; ++i) {
col.push(children[idx[i]]);
}
return col;
},
normalizeAngle: function (angle) {
while (angle > Math.PI * 2) {
angle -= 2 * Math.PI;
}
while (angle < 0) {
angle += Math.PI * 2;
}
return angle;
},
radialLayout: function (node, radius, startAngle, endAngle) {
var deltaTheta = endAngle - startAngle;
var deltaThetaHalf = deltaTheta / 2.0;
var parentSector = node.sectorAngle;
var fraction = 0;
var sorted = this.sortChildren(node);
for (var i = 0, len = sorted.length; i < len; i++) {
var childNode = sorted[i];
var cp = childNode;
var childAngleFraction = cp.sectorAngle / parentSector;
if (childNode.children.length > 0) {
this.radialLayout(childNode,
radius + this.options.radialSeparation,
startAngle + (fraction * deltaTheta),
startAngle + ((fraction + childAngleFraction) * deltaTheta));
}
this.setPolarLocation(childNode, radius, startAngle + (fraction * deltaTheta) + (childAngleFraction * deltaThetaHalf));
cp.angle = childAngleFraction * deltaTheta;
fraction += childAngleFraction;
}
},
setPolarLocation: function (node, radius, angle) {
node.x = this.origin.x + (radius * Math.cos(angle));
node.y = this.origin.y + (radius * Math.sin(angle));
node.BoundingRectangle = new Rect(node.x, node.y, node.width, node.height);
},
/**
* Sets the children direction recursively.
* @param node
* @param direction
* @param includeStart
*/
setChildrenDirection: function (node, direction, includeStart) {
var rootDirection = node.treeDirection;
this.graph.depthFirstTraversal(node, function (n) {
n.treeDirection = direction;
});
if (!includeStart) {
node.treeDirection = rootDirection;
}
},
/**
* Sets the children layout recursively.
* @param node
* @param layout
* @param includeStart
* @param startFromLevel
*/
setChildrenLayout: function (node, layout, includeStart, startFromLevel) {
if (Utils.isUndefined(startFromLevel)) {
startFromLevel = 0;
}
var rootLayout = node.childrenLayout;
if (startFromLevel > 0) {
// assign levels to the Node.Level property
this.graph.assignLevels(node);
// assign the layout on the condition that the level is at least the 'startFromLevel'
this.graph.depthFirstTraversal(
node, function (s) {
if (s.level >= startFromLevel + 1) {
s.childrenLayout = layout;
}
}
);
}
else {
this.graph.depthFirstTraversal(node, function (s) {
s.childrenLayout = layout;
});
// if the start should not be affected we put the state back
if (!includeStart) {
node.childrenLayout = rootLayout;
}
}
},
/**
* Returns the actual size of the node. The given size is the allowed space wherein the node can lay out itself.
* @param node
* @param givenSize
* @returns {Size}
*/
measure: function (node, givenSize) {
var w = 0, h = 0, s;
var result = new Size(0, 0);
if (!node) {
throw "";
}
var b = node.associatedShape.bounds();
var shapeWidth = b.width;
var shapeHeight = b.height;
if (node.parents.length !== 1) {
throw "Node not in a spanning tree.";
}
var parent = node.parents[0];
if (node.treeDirection === "Undefined") {
node.treeDirection = parent.treeDirection;
}
if (Utils.isEmpty(node.children)) {
result = new Size(
Math.abs(shapeWidth) < EPSILON ? 50 : shapeWidth,
Math.abs(shapeHeight) < EPSILON ? 25 : shapeHeight);
}
else if (node.children.length === 1) {
switch (node.treeDirection) {
case "Radial":
s = this.measure(node.children[0], givenSize); // child size
w = shapeWidth + (this.options.radialSeparation * Math.cos(node.AngleToParent)) + s.width;
h = shapeHeight + Math.abs(this.options.radialSeparation * Math.sin(node.AngleToParent)) + s.height;
break;
case "Left":
case "Right":
switch (node.childrenLayout) {
case "TopAlignedWithParent":
break;
case "BottomAlignedWithParent":
break;
case "Underneath":
s = this.measure(node.children[0], givenSize);
w = shapeWidth + s.width + this.options.underneathHorizontalOffset;
h = shapeHeight + this.options.underneathVerticalTopOffset + s.height;
break;
case "Default":
s = this.measure(node.children[0], givenSize);
w = shapeWidth + this.options.horizontalSeparation + s.width;
h = Math.max(shapeHeight, s.height);
break;
default:
throw "Unhandled TreeDirection in the Radial layout measuring.";
}
break;
case "Up":
case "Down":
switch (node.childrenLayout) {
case "TopAlignedWithParent":
case "BottomAlignedWithParent":
break;
case "Underneath":
s = this.measure(node.children[0], givenSize);
w = Math.max(shapeWidth, s.width + this.options.underneathHorizontalOffset);
h = shapeHeight + this.options.underneathVerticalTopOffset + s.height;
break;
case "Default":
s = this.measure(node.children[0], givenSize);
h = shapeHeight + this.options.verticalSeparation + s.height;
w = Math.max(shapeWidth, s.width);
break;
default:
throw "Unhandled TreeDirection in the Down layout measuring.";
}
break;
default:
throw "Unhandled TreeDirection in the layout measuring.";
}
result = new Size(w, h);
}
else {
var i, childNode;
switch (node.treeDirection) {
case "Left":
case "Right":
switch (node.childrenLayout) {
case "TopAlignedWithParent":
case "BottomAlignedWithParent":
break;
case "Underneath":
w = shapeWidth;
h = shapeHeight + this.options.underneathVerticalTopOffset;
for (i = 0; i < node.children.length; i++) {
childNode = node.children[i];
s = this.measure(childNode, givenSize);
w = Math.max(w, s.width + this.options.underneathHorizontalOffset);
h += s.height + this.options.underneathVerticalSeparation;
}
h -= this.options.underneathVerticalSeparation;
break;
case "Default":
w = shapeWidth;
h = 0;
for (i = 0; i < node.children.length; i++) {
childNode = node.children[i];
s = this.measure(childNode, givenSize);
w = Math.max(w, shapeWidth + this.options.horizontalSeparation + s.width);
h += s.height + this.options.verticalSeparation;
}
h -= this.options.verticalSeparation;
break;
default:
throw "Unhandled TreeDirection in the Right layout measuring.";
}
break;
case "Up":
case "Down":
switch (node.childrenLayout) {
case "TopAlignedWithParent":
case "BottomAlignedWithParent":
break;
case "Underneath":
w = shapeWidth;
h = shapeHeight + this.options.underneathVerticalTopOffset;
for (i = 0; i < node.children.length; i++) {
childNode = node.children[i];
s = this.measure(childNode, givenSize);
w = Math.max(w, s.width + this.options.underneathHorizontalOffset);
h += s.height + this.options.underneathVerticalSeparation;
}
h -= this.options.underneathVerticalSeparation;
break;
case "Default":
w = 0;
h = 0;
for (i = 0; i < node.children.length; i++) {
childNode = node.children[i];
s = this.measure(childNode, givenSize);
w += s.width + this.options.horizontalSeparation;
h = Math.max(h, s.height + this.options.verticalSeparation + shapeHeight);
}
w -= this.options.horizontalSeparation;
break;
default:
throw "Unhandled TreeDirection in the Down layout measuring.";
}
break;
default:
throw "Unhandled TreeDirection in the layout measuring.";
}
result = new Size(w, h);
}
node.SectorAngle = Math.sqrt((w * w / 4) + (h * h / 4));
node.Size = result;
return result;
},
arrange: function (n, p) {
var i, pp, child, node, childrenwidth, b = n.associatedShape.bounds();
var shapeWidth = b.width;
var shapeHeight = b.height;
if (Utils.isEmpty(n.children)) {
n.x = p.x;
n.y = p.y;
n.BoundingRectangle = new Rect(p.x, p.y, shapeWidth, shapeHeight);
}
else {
var x, y;
var selfLocation;
switch (n.treeDirection) {
case "Left":
switch (n.childrenLayout) {
case "TopAlignedWithParent":
case "BottomAlignedWithParent":
break;
case "Underneath":
selfLocation = p;
n.x = selfLocation.x;
n.y = selfLocation.y;
n.BoundingRectangle = new Rect(n.x, n.y, n.width, n.height);
y = p.y + shapeHeight + this.options.underneathVerticalTopOffset;
for (i = 0; i < node.children.length; i++) {
node = node.children[i];
x = selfLocation.x - node.associatedShape.width - this.options.underneathHorizontalOffset;
pp = new Point(x, y);
this.arrange(node, pp);
y += node.Size.height + this.options.underneathVerticalSeparation;
}
break;
case "Default":
selfLocation = new Point(p.x + n.Size.width - shapeWidth, p.y + ((n.Size.height - shapeHeight) / 2));
n.x = selfLocation.x;
n.y = selfLocation.y;
n.BoundingRectangle = new Rect(n.x, n.y, n.width, n.height);
x = selfLocation.x - this.options.horizontalSeparation; // alignment of children
y = p.y;
for (i = 0; i < n.children.length; i++) {
node = n.children[i];
pp = new Point(x - node.Size.width, y);
this.arrange(node, pp);
y += node.Size.height + this.options.verticalSeparation;
}
break;
default:
throw "Unsupported TreeDirection";
}
break;
case "Right":
switch (n.childrenLayout) {
case "TopAlignedWithParent":
case "BottomAlignedWithParent":
break;
case "Underneath":
selfLocation = p;
n.x = selfLocation.x;
n.y = selfLocation.y;
n.BoundingRectangle = new Rect(n.x, n.y, n.width, n.height);
x = p.x + shapeWidth + this.options.underneathHorizontalOffset;
// alignment of children left-underneath the parent
y = p.y + shapeHeight + this.options.underneathVerticalTopOffset;
for (i = 0; i < n.children.length; i++) {
node = n.children[i];
pp = new Point(x, y);
this.arrange(node, pp);
y += node.Size.height + this.options.underneathVerticalSeparation;
}
break;
case "Default":
selfLocation = new Point(p.x, p.y + ((n.Size.height - shapeHeight) / 2));
n.x = selfLocation.x;
n.y = selfLocation.y;
n.BoundingRectangle = new Rect(n.x, n.y, n.width, n.height);
x = p.x + shapeWidth + this.options.horizontalSeparation; // alignment of children
y = p.y;
for (i = 0; i < n.children.length; i++) {
node = n.children[i];
pp = new Point(x, y);
this.arrange(node, pp);
y += node.Size.height + this.options.verticalSeparation;
}
break;
default:
throw "Unsupported TreeDirection";
}
break;
case "Up":
selfLocation = new Point(p.x + ((n.Size.width - shapeWidth) / 2), p.y + n.Size.height - shapeHeight);
n.x = selfLocation.x;
n.y = selfLocation.y;
n.BoundingRectangle = new Rect(n.x, n.y, n.width, n.height);
if (Math.abs(selfLocation.x - p.x) < EPSILON) {
childrenwidth = 0;
// means there is an aberration due to the oversized Element with respect to the children
for (i = 0; i < n.children.length; i++) {
child = n.children[i];
childrenwidth += child.Size.width + this.options.horizontalSeparation;
}
childrenwidth -= this.options.horizontalSeparation;
x = p.x + ((shapeWidth - childrenwidth) / 2);
}
else {
x = p.x;
}
for (i = 0; i < n.children.length; i++) {
node = n.children[i];
y = selfLocation.y - this.options.verticalSeparation - node.Size.height;
pp = new Point(x, y);
this.arrange(node, pp);
x += node.Size.width + this.options.horizontalSeparation;
}
break;
case "Down":
switch (n.childrenLayout) {
case "TopAlignedWithParent":
case "BottomAlignedWithParent":
break;
case "Underneath":
selfLocation = p;
n.x = selfLocation.x;
n.y = selfLocation.y;
n.BoundingRectangle = new Rect(n.x, n.y, n.width, n.height);
x = p.x + this.options.underneathHorizontalOffset; // alignment of children left-underneath the parent
y = p.y + shapeHeight + this.options.underneathVerticalTopOffset;
for (i = 0; i < n.children.length; i++) {
node = n.children[i];
pp = new Point(x, y);
this.arrange(node, pp);
y += node.Size.height + this.options.underneathVerticalSeparation;
}
break;
case "Default":
selfLocation = new Point(p.x + ((n.Size.width - shapeWidth) / 2), p.y);
n.x = selfLocation.x;
n.y = selfLocation.y;
n.BoundingRectangle = new Rect(n.x, n.y, n.width, n.height);
if (Math.abs(selfLocation.x - p.x) < EPSILON) {
childrenwidth = 0;
// means there is an aberration due to the oversized Element with respect to the children
for (i = 0; i < n.children.length; i++) {
child = n.children[i];
childrenwidth += child.Size.width + this.options.horizontalSeparation;
}
childrenwidth -= this.options.horizontalSeparation;
x = p.x + ((shapeWidth - childrenwidth) / 2);
}
else {
x = p.x;
}
for (i = 0; i < n.children.length; i++) {
node = n.children[i];
y = selfLocation.y + this.options.verticalSeparation + shapeHeight;
pp = new Point(x, y);
this.arrange(node, pp);
x += node.Size.width + this.options.horizontalSeparation;
}
break;
default:
throw "Unsupported TreeDirection";
}
break;
case "None":
break;
default:
throw "Unsupported TreeDirection";
}
}
},
layoutSwitch: function () {
if (!this.center) {
return;
}
if (Utils.isEmpty(this.center.children)) {
return;
}
var type = this.options.subtype;
if (Utils.isUndefined(type)) {
type = "Down";
}
var single, male, female, leftcount;
var children = this.center.children;
switch (type.toLowerCase()) {
case "radial":
case "radialtree":
this.layoutRadialTree();
break;
case "mindmaphorizontal":
case "mindmap":
single = this.center.children;
if (this.center.children.length === 1) {
this.layoutRight(single);
}
else {
// odd number will give one more at the right
leftcount = children.length / 2;
male = grep(this.center.children, function (n) {
return Utils.indexOf(children, n) < leftcount;
});
female = grep(this.center.children, function (n) {
return Utils.indexOf(children, n) >= leftcount;
});
this.layoutLeft(male);
this.layoutRight(female);
}
break;
case "mindmapvertical":
single = this.center.children;
if (this.center.children.length === 1) {
this.layoutDown(single);
}
else {
// odd number will give one more at the right
leftcount = children.length / 2;
male = grep(this.center.children, function (n) {
return Utils.indexOf(children, n) < leftcount;
});
female = grep(this.center.children, function (n) {
return Utils.indexOf(children, n) >= leftcount;
});
this.layoutUp(male);
this.layoutDown(female);
}
break;
case "right":
this.layoutRight(this.center.children);
break;
case "left":
this.layoutLeft(this.center.children);
break;
case "up":
case "bottom":
this.layoutUp(this.center.children);
break;
case "down":
case "top":
this.layoutDown(this.center.children);
break;
case "tipover":
case "tipovertree":
if (this.options.tipOverTreeStartLevel < 0) {
throw "The tip-over level should be a positive integer.";
}
this.tipOverTree(this.center.children, this.options.tipOverTreeStartLevel);
break;
case "undefined":
case "none":
break;
}
}
});
/**
* The various tree layout algorithms.
* @type {*}
*/
var TreeLayout = LayoutBase.extend({
init: function (diagram) {
var that = this;
LayoutBase.fn.init.call(that);
if (Utils.isUndefined(diagram)) {
throw "No diagram specified.";
}
this.diagram = diagram;
},
/**
* Arranges the diagram in a tree-layout with the specified options and tree subtype.
*/
layout: function (options) {
this.transferOptions(options);
// transform the diagram into a Graph
var adapter = new DiagramToHyperTreeAdapter(this.diagram);
/**
* The Graph reduction from the given diagram.
* @type {*}
*/
this.graph = adapter.convert();
var finalNodeSet = this.layoutComponents();
// note that the graph contains the original data and
// the components are another instance of nodes referring to the same set of shapes
return new diagram.LayoutState(this.diagram, finalNodeSet);
},
layoutComponents: function () {
if (this.graph.isEmpty()) {
return;
}
// split into connected components
var components = this.graph.getConnectedComponents();
if (Utils.isEmpty(components)) {
return;
}
var layout = new TreeLayoutProcessor(this.options);
var trees = [];
// find a spanning tree for each component
for (var i = 0; i < components.length; i++) {
var component = components[i];
var treeGraph = this.getTree(component);
if (!treeGraph) {
throw "Failed to find a spanning tree for the component.";
}
var root = treeGraph.root;
var tree = treeGraph.tree;
layout.layout(tree, root);
trees.push(tree);
}
return this.gridLayoutComponents(trees);
},
/**
* Gets a spanning tree (and root) for the given graph.
* Ensure that the given graph is connected!
* @param graph
* @returns {*} A literal object consisting of the found root and the spanning tree.
*/
getTree: function (graph) {
var root = null;
if (this.options.roots && this.options.roots.length > 0) {
for (var i = 0, len = graph.nodes.length; i < len; i++) {
var node = graph.nodes[i];
for (var j = 0; j < this.options.roots.length; j++) {
var givenRootShape = this.options.roots[j];
if (givenRootShape === node.associatedShape) {
root = node;
break;
}
}
}
}
if (!root) {
// finds the most probable root on the basis of the longest path in the component
root = graph.root();
// should not happen really
if (!root) {
throw "Unable to find a root for the tree.";
}
}
return this.getTreeForRoot(graph, root);
},
getTreeForRoot: function (graph, root) {
var tree = graph.getSpanningTree(root);
if (Utils.isUndefined(tree) || tree.isEmpty()) {
return null;
}
return {
tree: tree,
root: tree.root
};
}
});
/**
* The Sugiyama aka layered layout algorithm.
* @type {*}
*/
var LayeredLayout = LayoutBase.extend({
init: function (diagram) {
var that = this;
LayoutBase.fn.init.call(that);
if (Utils.isUndefined(diagram)) {
throw "Diagram is not specified.";
}
this.diagram = diagram;
},
layout: function (options) {
this.transferOptions(options);
var adapter = new DiagramToHyperTreeAdapter(this.diagram);
var graph = adapter.convert(options);
if (graph.isEmpty()) {
return;
}
// split into connected components
var components = graph.getConnectedComponents();
if (Utils.isEmpty(components)) {
return;
}
for (var i = 0; i < components.length; i++) {
var component = components[i];
this.layoutGraph(component, options);
}
var finalNodeSet = this.gridLayoutComponents(components);
return new diagram.LayoutState(this.diagram, finalNodeSet);
},
/**
* Initializes the runtime data properties of the layout.
* @private
*/
_initRuntimeProperties: function () {
for (var k = 0; k < this.graph.nodes.length; k++) {
var node = this.graph.nodes[k];
node.layer = -1;
node.downstreamLinkCount = 0;
node.upstreamLinkCount = 0;
node.isVirtual = false;
node.uBaryCenter = 0.0;
node.dBaryCenter = 0.0;
node.upstreamPriority = 0;
node.downstreamPriority = 0;
node.gridPosition = 0;
}
},
_prepare: function (graph) {
var current = [], i, l, link;
// defines a mapping of a node to the layer index
var layerMap = new Dictionary();
var layerCount = 0;
var targetLayer, next, target;
Utils.forEach(graph.nodes, function (node) {
if (node.incoming.length === 0) {
layerMap.set(node, 0);
current.push(node);
}
});
while (current.length > 0) {
next = current.shift();
for (i = 0; i < next.outgoing.length; i++) {
link = next.outgoing[i];
target = link.target;
if (layerMap.containsKey(target)) {
targetLayer = Math.max(layerMap.get(next) + 1, layerMap.get(target));
} else {
targetLayer = layerMap.get(next) + 1;
}
layerMap.set(target, targetLayer);
if (targetLayer > layerCount) {
layerCount = targetLayer;
}
if (!contains(current, target)) {
current.push(target);
}
}
}
var sortedNodes = layerMap.keys();
sortedNodes.sort(function (o1, o2) {
var o1layer = layerMap.get(o1);
var o2layer = layerMap.get(o2);
return Utils.sign(o2layer - o1layer);
});
for (var n = 0; n < sortedNodes.length; ++n) {
var node = sortedNodes[n];
var minLayer = Number.MAX_VALUE;
if (node.outgoing.length === 0) {
continue;
}
for (l = 0; l < node.outgoing.length; ++l) {
link = node.outgoing[l];
minLayer = Math.min(minLayer, layerMap.get(link.target));
}
if (minLayer > 1) {
layerMap.set(node, minLayer - 1);
}
}
this.layers = [];
var layer;
for (i = 0; i < layerCount + 1; i++) {
layer = [];
layer.linksTo = {};
this.layers.push(layer);
}
layerMap.forEach(function (node, layer) {
node.layer = layer;
this.layers[layer].push(node);
}, this);
// set initial grid positions
for (l = 0; l < this.layers.length; l++) {
layer = this.layers[l];
for (i = 0; i < layer.length; i++) {
layer[i].gridPosition = i;
}
}
},
/**
* Performs the layout of a single component.
*/
layoutGraph: function (graph, options) {
if (Utils.isUndefined(graph)) {
throw "No graph given or graph analysis of the diagram failed.";
}
if (Utils.isDefined(options)) {
this.transferOptions(options);
}
this.graph = graph;
// sets unique indices on the nodes
graph.setItemIndices();
// ensures no cycles present for this layout
var reversedEdges = graph.makeAcyclic();
// define the runtime props being used by the layout algorithm
this._initRuntimeProperties();
this._prepare(graph, options);
this._dummify();
this._optimizeCrossings();
this._swapPairs();
this.arrangeNodes();
this._moveThingsAround();
this._dedummify();
// re-reverse the links which were switched earlier
Utils.forEach(reversedEdges, function (e) {
if (e.points) {
e.points.reverse();
}
});
},
setMinDist: function (m, n, minDist) {
var l = m.layer;
var i = m.layerIndex;
this.minDistances[l][i] = minDist;
},
getMinDist: function (m, n) {
var dist = 0,
i1 = m.layerIndex,
i2 = n.layerIndex,
l = m.layer,
min = Math.min(i1, i2),
max = Math.max(i1, i2);
// use Sum()?
for (var k = min; k < max; ++k) {
dist += this.minDistances[l][k];
}
return dist;
},
placeLeftToRight: function (leftClasses) {
var leftPos = new Dictionary(), n, node;
for (var c = 0; c < this.layers.length; ++c) {
var classNodes = leftClasses[c];
if (!classNodes) {
continue;
}
for (n = 0; n < classNodes.length; n++) {
node = classNodes[n];
if (!leftPos.containsKey(node)) {
this.placeLeft(node, leftPos, c);
}
}
// adjust class
var d = Number.POSITIVE_INFINITY;
for (n = 0; n < classNodes.length; n++) {
node = classNodes[n];
var rightSibling = this.rightSibling(node);
if (rightSibling && this.nodeLeftClass.get(rightSibling) !== c) {
d = Math.min(d, leftPos.get(rightSibling) - leftPos.get(node) - this.getMinDist(node, rightSibling));
}
}
if (d === Number.POSITIVE_INFINITY) {
var D = [];
for (n = 0; n < classNodes.length; n++) {
node = classNodes[n];
var neighbors = [];
Utils.addRange(neighbors, this.upNodes.get(node));
Utils.addRange(neighbors, this.downNodes.get(node));
for (var e = 0; e < neighbors.length; e++) {
var neighbor = neighbors[e];
if (this.nodeLeftClass.get(neighbor) < c) {
D.push(leftPos.get(neighbor) - leftPos.get(node));
}
}
}
D.sort();
if (D.length === 0) {
d = 0;
}
else if (D.length % 2 === 1) {
d = D[this.intDiv(D.length, 2)];
}
else {
d = (D[this.intDiv(D.length, 2) - 1] + D[this.intDiv(D.length, 2)]) / 2;
}
}
for (n = 0; n < classNodes.length; n++) {
node = classNodes[n];
leftPos.set(node, leftPos.get(node) + d);
}
}
return leftPos;
},
placeRightToLeft: function (rightClasses) {
var rightPos = new Dictionary(), n, node;
for (var c = 0; c < this.layers.length; ++c) {
var classNodes = rightClasses[c];
if (!classNodes) {
continue;
}
for (n = 0; n < classNodes.length; n++) {
node = classNodes[n];
if (!rightPos.containsKey(node)) {
this.placeRight(node, rightPos, c);
}
}
// adjust class
var d = Number.NEGATIVE_INFINITY;
for (n = 0; n < classNodes.length; n++) {
node = classNodes[n];
var leftSibling = this.leftSibling(node);
if (leftSibling && this.nodeRightClass.get(leftSibling) !== c) {
d = Math.max(d, rightPos.get(leftSibling) - rightPos.get(node) + this.getMinDist(leftSibling, node));
}
}
if (d === Number.NEGATIVE_INFINITY) {
var D = [];
for (n = 0; n < classNodes.length; n++) {
node = classNodes[n];
var neighbors = [];
Utils.addRange(neighbors, this.upNodes.get(node));
Utils.addRange(neighbors, this.downNodes.get(node));
for (var e = 0; e < neighbors.length; e++) {
var neighbor = neighbors[e];
if (this.nodeRightClass.get(neighbor) < c) {
D.push(rightPos.get(node) - rightPos.get(neighbor));
}
}
}
D.sort();
if (D.length === 0) {
d = 0;
}
else if (D.length % 2 === 1) {
d = D[this.intDiv(D.length, 2)];
}
else {
d = (D[this.intDiv(D.length, 2) - 1] + D[this.intDiv(D.length, 2)]) / 2;
}
}
for (n = 0; n < classNodes.length; n++) {
node = classNodes[n];
rightPos.set(node, rightPos.get(node) + d);
}
}
return rightPos;
},
_getLeftWing: function () {
var leftWing = { value: null };
var result = this.computeClasses(leftWing, 1);
this.nodeLeftClass = leftWing.value;
return result;
},
_getRightWing: function () {
var rightWing = { value: null };
var result = this.computeClasses(rightWing, -1);
this.nodeRightClass = rightWing.value;
return result;
},
computeClasses: function (wingPair, d) {
var currentWing = 0,
wing = wingPair.value = new Dictionary();
for (var l = 0; l < this.layers.length; ++l) {
currentWing = l;
var layer = this.layers[l];
for (var n = d === 1 ? 0 : layer.length - 1; 0 <= n && n < layer.length; n += d) {
var node = layer[n];
if (!wing.containsKey(node)) {
wing.set(node, currentWing);
if (node.isVirtual) {
var ndsinl = this._nodesInLink(node);
for (var kk = 0; kk < ndsinl.length; kk++) {
var vnode = ndsinl[kk];
wing.set(vnode, currentWing);
}
}
}
else {
currentWing = wing.get(node);
}
}
}
var wings = [];
for (var i = 0; i < this.layers.length; i++) {
wings.push(null);
}
wing.forEach(function (node, classIndex) {
if (wings[classIndex] === null) {
wings[classIndex] = [];
}
wings[classIndex].push(node);
});
return wings;
},
_isVerticalLayout: function () {
return this.options.subtype.toLowerCase() === "up" || this.options.subtype.toLowerCase() === "down" || this.options.subtype.toLowerCase() === "vertical";
},
_isHorizontalLayout: function () {
return this.options.subtype.toLowerCase() === "right" || this.options.subtype.toLowerCase() === "left" || this.options.subtype.toLowerCase() === "horizontal";
},
_isIncreasingLayout: function () {
// meaning that the visiting of the layers goes in the natural order of increasing layer index
return this.options.subtype.toLowerCase() === "right" || this.options.subtype.toLowerCase() === "down";
},
_moveThingsAround: function () {
var i, l, node, layer, n, w;
// sort the layers by their grid position
for (l = 0; l < this.layers.length; ++l) {
layer = this.layers[l];
layer.sort(this._gridPositionComparer);
}
this.minDistances = [];
for (l = 0; l < this.layers.length; ++l) {
layer = this.layers[l];
this.minDistances[l] = [];
for (n = 0; n < layer.length; ++n) {
node = layer[n];
node.layerIndex = n;
this.minDistances[l][n] = this.options.nodeDistance;
if (n < layer.length - 1) {
if (this._isVerticalLayout()) {
this.minDistances[l][n] += (node.width + layer[n + 1].width) / 2;
}
else {
this.minDistances[l][n] += (node.height + layer[n + 1].height) / 2;
}
}
}
}
this.downNodes = new Dictionary();
this.upNodes = new Dictionary();
Utils.forEach(this.graph.nodes, function (node) {
this.downNodes.set(node, []);
this.upNodes.set(node, []);
}, this);
Utils.forEach(this.graph.links, function (link) {
var origin = link.source;
var dest = link.target;
var down = null, up = null;
if (origin.layer > dest.layer) {
down = link.source;
up = link.target;
}
else {
up = link.source;
down = link.target;
}
this.downNodes.get(up).push(down);
this.upNodes.get(down).push(up);
}, this);
this.downNodes.forEachValue(function (list) {
list.sort(this._gridPositionComparer);
}, this);
this.upNodes.forEachValue(function (list) {
list.sort(this._gridPositionComparer);
}, this);
for (l = 0; l < this.layers.length - 1; ++l) {
layer = this.layers[l];
for (w = 0; w < layer.length - 1; w++) {
var currentNode = layer[w];
if (!currentNode.isVirtual) {
continue;
}
var currDown = this.downNodes.get(currentNode)[0];
if (!currDown.isVirtual) {
continue;
}
for (n = w + 1; n < layer.length; ++n) {
node = layer[n];
if (!node.isVirtual) {
continue;
}
var downNode = this.downNodes.get(node)[0];
if (!downNode.isVirtual) {
continue;
}
if (currDown.gridPosition > downNode.gridPosition) {
var pos = currDown.gridPosition;
currDown.gridPosition = downNode.gridPosition;
downNode.gridPosition = pos;
var i1 = currDown.layerIndex;
var i2 = downNode.layerIndex;
this.layers[l + 1][i1] = downNode;
this.layers[l + 1][i2] = currDown;
currDown.layerIndex = i2;
downNode.layerIndex = i1;
}
}
}
}
var leftClasses = this._getLeftWing();
var rightClasses = this._getRightWing();
var leftPos = this.placeLeftToRight(leftClasses);
var rightPos = this.placeRightToLeft(rightClasses);
var x = new Dictionary();
Utils.forEach(this.graph.nodes, function (node) {
x.set(node, (leftPos.get(node) + rightPos.get(node)) / 2);
});
var order = new Dictionary();
var placed = new Dictionary();
for (l = 0; l < this.layers.length; ++l) {
layer = this.layers[l];
var sequenceStart = -1, sequenceEnd = -1;
for (n = 0; n < layer.length; ++n) {
node = layer[n];
order.set(node, 0);
placed.set(node, false);
if (node.isVirtual) {
if (sequenceStart === -1) {
sequenceStart = n;
}
else if (sequenceStart === n - 1) {
sequenceStart = n;
}
else {
sequenceEnd = n;
order.set(layer[sequenceStart], 0);
if (x.get(node) - x.get(layer[sequenceStart]) === this.getMinDist(layer[sequenceStart], node)) {
placed.set(layer[sequenceStart], true);
}
else {
placed.set(layer[sequenceStart], false);
}
sequenceStart = n;
}
}
}
}
var directions = [1, -1];
Utils.forEach(directions, function (d) {
var start = d === 1 ? 0 : this.layers.length - 1;
for (var l = start; 0 <= l && l < this.layers.length; l += d) {
var layer = this.layers[l];
var virtualStartIndex = this._firstVirtualNode(layer);
var virtualStart = null;
var sequence = null;
if (virtualStartIndex !== -1) {
virtualStart = layer[virtualStartIndex];
sequence = [];
for (i = 0; i < virtualStartIndex; i++) {
sequence.push(layer[i]);
}
}
else {
virtualStart = null;
sequence = layer;
}
if (sequence.length > 0) {
this._sequencer(x, null, virtualStart, d, sequence);
for (i = 0; i < sequence.length - 1; ++i) {
this.setMinDist(sequence[i], sequence[i + 1], x.get(sequence[i + 1]) - x.get(sequence[i]));
}
if (virtualStart) {
this.setMinDist(sequence[sequence.length - 1], virtualStart, x.get(virtualStart) - x.get(sequence[sequence.length - 1]));
}
}
while (virtualStart) {
var virtualEnd = this.nextVirtualNode(layer, virtualStart);
if (!virtualEnd) {
virtualStartIndex = virtualStart.layerIndex;
sequence = [];
for (i = virtualStartIndex + 1; i < layer.length; i++) {
sequence.push(layer[i]);
}
if (sequence.length > 0) {
this._sequencer(x, virtualStart, null, d, sequence);
for (i = 0; i < sequence.length - 1; ++i) {
this.setMinDist(sequence[i], sequence[i + 1], x.get(sequence[i + 1]) - x.get(sequence[i]));
}
this.setMinDist(virtualStart, sequence[0], x.get(sequence[0]) - x.get(virtualStart));
}
}
else if (order.get(virtualStart) === d) {
virtualStartIndex = virtualStart.layerIndex;
var virtualEndIndex = virtualEnd.layerIndex;
sequence = [];
for (i = virtualStartIndex + 1; i < virtualEndIndex; i++) {
sequence.push(layer[i]);
}
if (sequence.length > 0) {
this._sequencer(x, virtualStart, virtualEnd, d, sequence);
}
placed.set(virtualStart, true);
}
virtualStart = virtualEnd;
}
this.adjustDirections(l, d, order, placed);
}
}, this);
var fromLayerIndex = this._isIncreasingLayout() ? 0 : this.layers.length - 1;
var reachedFinalLayerIndex = function (k, ctx) {
if (ctx._isIncreasingLayout()) {
return k < ctx.layers.length;
}
else {
return k >= 0;
}
};
var layerIncrement = this._isIncreasingLayout() ? +1 : -1, offset = 0;
/**
* Calcs the max height of the given layer.
*/
function maximumHeight(layer, ctx) {
var height = Number.MIN_VALUE;
for (var n = 0; n < layer.length; ++n) {
var node = layer[n];
if (ctx._isVerticalLayout()) {
height = Math.max(height, node.height);
}
else {
height = Math.max(height, node.width);
}
}
return height;
}
for (i = fromLayerIndex; reachedFinalLayerIndex(i, this); i += layerIncrement) {
layer = this.layers[i];
var height = maximumHeight(layer, this);
for (n = 0; n < layer.length; ++n) {
node = layer[n];
if (this._isVerticalLayout()) {
node.x = x.get(node);
node.y = offset + height / 2;
}
else {
node.x = offset + height / 2;
node.y = x.get(node);
}
}
offset += this.options.layerSeparation + height;
}
},
adjustDirections: function (l, d, order, placed) {
if (l + d < 0 || l + d >= this.layers.length) {
return;
}
var prevBridge = null, prevBridgeTarget = null;
var layer = this.layers[l + d];
for (var n = 0; n < layer.length; ++n) {
var nextBridge = layer[n];
if (nextBridge.isVirtual) {
var nextBridgeTarget = this.getNeighborOnLayer(nextBridge, l);
if (nextBridgeTarget.isVirtual) {
if (prevBridge) {
var p = placed.get(prevBridgeTarget);
var clayer = this.layers[l];
var i1 = prevBridgeTarget.layerIndex;
var i2 = nextBridgeTarget.layerIndex;
for (var i = i1 + 1; i < i2; ++i) {
if (clayer[i].isVirtual) {
p = p && placed.get(clayer[i]);
}
}
if (p) {
order.set(prevBridge, d);
var j1 = prevBridge.layerIndex;
var j2 = nextBridge.layerIndex;
for (var j = j1 + 1; j < j2; ++j) {
if (layer[j].isVirtual) {
order.set(layer[j], d);
}
}
}
}
prevBridge = nextBridge;
prevBridgeTarget = nextBridgeTarget;
}
}
}
},
getNeighborOnLayer: function (node, l) {
var neighbor = this.upNodes.get(node)[0];
if (neighbor.layer === l) {
return neighbor;
}
neighbor = this.downNodes.get(node)[0];
if (neighbor.layer === l) {
return neighbor;
}
return null;
},
_sequencer: function (x, virtualStart, virtualEnd, dir, sequence) {
if (sequence.length === 1) {
this._sequenceSingle(x, virtualStart, virtualEnd, dir, sequence[0]);
}
if (sequence.length > 1) {
var r = sequence.length, t = this.intDiv(r, 2);
this._sequencer(x, virtualStart, virtualEnd, dir, sequence.slice(0, t));
this._sequencer(x, virtualStart, virtualEnd, dir, sequence.slice(t));
this.combineSequences(x, virtualStart, virtualEnd, dir, sequence);
}
},
_sequenceSingle: function (x, virtualStart, virtualEnd, dir, node) {
var neighbors = dir === -1 ? this.downNodes.get(node) : this.upNodes.get(node);
var n = neighbors.length;
if (n !== 0) {
if (n % 2 === 1) {
x.set(node, x.get(neighbors[this.intDiv(n, 2)]));
}
else {
x.set(node, (x.get(neighbors[this.intDiv(n, 2) - 1]) + x.get(neighbors[this.intDiv(n, 2)])) / 2);
}
if (virtualStart) {
x.set(node, Math.max(x.get(node), x.get(virtualStart) + this.getMinDist(virtualStart, node)));
}
if (virtualEnd) {
x.set(node, Math.min(x.get(node), x.get(virtualEnd) - this.getMinDist(node, virtualEnd)));
}
}
},
combineSequences: function (x, virtualStart, virtualEnd, dir, sequence) {
var r = sequence.length, t = this.intDiv(r, 2);
// collect left changes
var leftHeap = [], i, c, n, neighbors, neighbor, pair;
for (i = 0; i < t; ++i) {
c = 0;
neighbors = dir === -1 ? this.downNodes.get(sequence[i]) : this.upNodes.get(sequence[i]);
for (n = 0; n < neighbors.length; ++n) {
neighbor = neighbors[n];
if (x.get(neighbor) >= x.get(sequence[i])) {
c++;
}
else {
c--;
leftHeap.push({ k: x.get(neighbor) + this.getMinDist(sequence[i], sequence[t - 1]), v: 2 });
}
}
leftHeap.push({ k: x.get(sequence[i]) + this.getMinDist(sequence[i], sequence[t - 1]), v: c });
}
if (virtualStart) {
leftHeap.push({ k: x.get(virtualStart) + this.getMinDist(virtualStart, sequence[t - 1]), v: Number.MAX_VALUE });
}
leftHeap.sort(this._positionDescendingComparer);
// collect right changes
var rightHeap = [];
for (i = t; i < r; ++i) {
c = 0;
neighbors = dir === -1 ? this.downNodes.get(sequence[i]) : this.upNodes.get(sequence[i]);
for (n = 0; n < neighbors.length; ++n) {
neighbor = neighbors[n];
if (x.get(neighbor) <= x.get(sequence[i])) {
c++;
}
else {
c--;
rightHeap.push({ k: x.get(neighbor) - this.getMinDist(sequence[i], sequence[t]), v: 2 });
}
}
rightHeap.push({ k: x.get(sequence[i]) - this.getMinDist(sequence[i], sequence[t]), v: c });
}
if (virtualEnd) {
rightHeap.push({ k: x.get(virtualEnd) - this.getMinDist(virtualEnd, sequence[t]), v: Number.MAX_VALUE });
}
rightHeap.sort(this._positionAscendingComparer);
var leftRes = 0, rightRes = 0;
var m = this.getMinDist(sequence[t - 1], sequence[t]);
while (x.get(sequence[t]) - x.get(sequence[t - 1]) < m) {
if (leftRes < rightRes) {
if (leftHeap.length === 0) {
x.set(sequence[t - 1], x.get(sequence[t]) - m);
break;
}
else {
pair = leftHeap.shift();
leftRes = leftRes + pair.v;
x.set(sequence[t - 1], pair.k);
x.set(sequence[t - 1], Math.max(x.get(sequence[t - 1]), x.get(sequence[t]) - m));
}
}
else {
if (rightHeap.length === 0) {
x.set(sequence[t], x.get(sequence[t - 1]) + m);
break;
}
else {
pair = rightHeap.shift();
rightRes = rightRes + pair.v;
x.set(sequence[t], pair.k);
x.set(sequence[t], Math.min(x.get(sequence[t]), x.get(sequence[t - 1]) + m));
}
}
}
for (i = t - 2; i >= 0; i--) {
x.set(sequence[i], Math.min(x.get(sequence[i]), x.get(sequence[t - 1]) - this.getMinDist(sequence[i], sequence[t - 1])));
}
for (i = t + 1; i < r; i++) {
x.set(sequence[i], Math.max(x.get(sequence[i]), x.get(sequence[t]) + this.getMinDist(sequence[i], sequence[t])));
}
},
placeLeft: function (node, leftPos, leftClass) {
var pos = Number.NEGATIVE_INFINITY;
Utils.forEach(this._getComposite(node), function (v) {
var leftSibling = this.leftSibling(v);
if (leftSibling && this.nodeLeftClass.get(leftSibling) === this.nodeLeftClass.get(v)) {
if (!leftPos.containsKey(leftSibling)) {
this.placeLeft(leftSibling, leftPos, leftClass);
}
pos = Math.max(pos, leftPos.get(leftSibling) + this.getMinDist(leftSibling, v));
}
}, this);
if (pos === Number.NEGATIVE_INFINITY) {
pos = 0;
}
Utils.forEach(this._getComposite(node), function (v) {
leftPos.set(v, pos);
});
},
placeRight: function (node, rightPos, rightClass) {
var pos = Number.POSITIVE_INFINITY;
Utils.forEach(this._getComposite(node), function (v) {
var rightSibling = this.rightSibling(v);
if (rightSibling && this.nodeRightClass.get(rightSibling) === this.nodeRightClass.get(v)) {
if (!rightPos.containsKey(rightSibling)) {
this.placeRight(rightSibling, rightPos, rightClass);
}
pos = Math.min(pos, rightPos.get(rightSibling) - this.getMinDist(v, rightSibling));
}
}, this);
if (pos === Number.POSITIVE_INFINITY) {
pos = 0;
}
Utils.forEach(this._getComposite(node), function (v) {
rightPos.set(v, pos);
});
},
leftSibling: function (node) {
var layer = this.layers[node.layer],
layerIndex = node.layerIndex;
return layerIndex === 0 ? null : layer[layerIndex - 1];
},
rightSibling: function (node) {
var layer = this.layers[node.layer];
var layerIndex = node.layerIndex;
return layerIndex === layer.length - 1 ? null : layer[layerIndex + 1];
},
_getComposite: function (node) {
return node.isVirtual ? this._nodesInLink(node) : [node];
},
arrangeNodes: function () {
var i, l, ni, layer, node;
// Initialize node's base priority
for (l = 0; l < this.layers.length; l++) {
layer = this.layers[l];
for (ni = 0; ni < layer.length; ni++) {
node = layer[ni];
node.upstreamPriority = node.upstreamLinkCount;
node.downstreamPriority = node.downstreamLinkCount;
}
}
// Layout is invoked after MinimizeCrossings
// so we may assume node's barycenters are initially correct
var maxLayoutIterations = 2;
for (var it = 0; it < maxLayoutIterations; it++) {
for (i = this.layers.length - 1; i >= 1; i--) {
this.layoutLayer(false, i);
}
for (i = 0; i < this.layers.length - 1; i++) {
this.layoutLayer(true, i);
}
}
// Offset the whole structure so that there are no gridPositions < 0
var gridPos = Number.MAX_VALUE;
for (l = 0; l < this.layers.length; l++) {
layer = this.layers[l];
for (ni = 0; ni < layer.length; ni++) {
node = layer[ni];
gridPos = Math.min(gridPos, node.gridPosition);
}
}
if (gridPos < 0) {
for (l = 0; l < this.layers.length; l++) {
layer = this.layers[l];
for (ni = 0; ni < layer.length; ni++) {
node = layer[ni];
node.gridPosition = node.gridPosition - gridPos;
}
}
}
},
/// <summary>
/// Layout of a single layer.
/// </summary>
/// <param name="layerIndex">The layer to organize.</param>
/// <param name="movingDownwards">If set to <c>true</c> we move down in the layer stack.</param>
/// <seealso cref="OptimizeCrossings()"/>
layoutLayer: function (down, layer) {
var iconsidered;
var considered;
if (down) {
considered = this.layers[iconsidered = layer + 1];
}
else {
considered = this.layers[iconsidered = layer - 1];
}
// list containing the nodes in the considered layer sorted by priority
var sorted = [];
for (var n = 0; n < considered.length; n++) {
sorted.push(considered[n]);
}
sorted.sort(function (n1, n2) {
var n1Priority = (n1.upstreamPriority + n1.downstreamPriority) / 2;
var n2Priority = (n2.upstreamPriority + n2.downstreamPriority) / 2;
if (Math.abs(n1Priority - n2Priority) < 0.0001) {
return 0;
}
if (n1Priority < n2Priority) {
return 1;
}
return -1;
});
// each node strives for its barycenter; high priority nodes start first
Utils.forEach(sorted, function (node) {
var nodeGridPos = node.gridPosition;
var nodeBaryCenter = this.calcBaryCenter(node);
var nodePriority = (node.upstreamPriority + node.downstreamPriority) / 2;
if (Math.abs(nodeGridPos - nodeBaryCenter) < 0.0001) {
// This node is exactly at its barycenter -> perfect
return;
}
if (Math.abs(nodeGridPos - nodeBaryCenter) < 0.25 + 0.0001) {
// This node is close enough to the barycenter -> should work
return;
}
if (nodeGridPos < nodeBaryCenter) {
// Try to move the node to the right in an
// attempt to reach its barycenter
while (nodeGridPos < nodeBaryCenter) {
if (!this.moveRight(node, considered, nodePriority)) {
break;
}
nodeGridPos = node.gridPosition;
}
}
else {
// Try to move the node to the left in an
// attempt to reach its barycenter
while (nodeGridPos > nodeBaryCenter) {
if (!this.moveLeft(node, considered, nodePriority)) {
break;
}
nodeGridPos = node.gridPosition;
}
}
}, this);
// after the layer has been rearranged we need to recalculate the barycenters
// of the nodes in the surrounding layers
if (iconsidered > 0) {
this.calcDownData(iconsidered - 1);
}
if (iconsidered < this.layers.length - 1) {
this.calcUpData(iconsidered + 1);
}
},
/// <summary>
/// Moves the node to the right and returns <c>true</c> if this was possible.
/// </summary>
/// <param name="node">The node.</param>
/// <param name="layer">The layer.</param>
/// <returns>Returns <c>true</c> if the shift was possible, otherwise <c>false</c>.</returns>
moveRight: function (node, layer, priority) {
var index = Utils.indexOf(layer, node);
if (index === layer.length - 1) {
// this is the last node in the layer, so we can move to the right without troubles
node.gridPosition = node.gridPosition + 0.5;
return true;
}
var rightNode = layer[index + 1];
var rightNodePriority = (rightNode.upstreamPriority + rightNode.downstreamPriority) / 2;
// check if there is space between the right and the current node
if (rightNode.gridPosition > node.gridPosition + 1) {
node.gridPosition = node.gridPosition + 0.5;
return true;
}
// we have reached a node with higher priority; no movement is allowed
if (rightNodePriority > priority ||
Math.abs(rightNodePriority - priority) < 0.0001) {
return false;
}
// the right node has lower priority - try to move it
if (this.moveRight(rightNode, layer, priority)) {
node.gridPosition = node.gridPosition + 0.5;
return true;
}
return false;
},
/// <summary>
/// Moves the node to the left and returns <c>true</c> if this was possible.
/// </summary>
/// <param name="node">The node.</param>
/// <param name="layer">The layer.</param>
/// <returns>Returns <c>true</c> if the shift was possible, otherwise <c>false</c>.</returns>
moveLeft: function (node, layer, priority) {
var index = Utils.indexOf(layer, node);
if (index === 0) {
// this is the last node in the layer, so we can move to the left without troubles
node.gridPosition = node.gridPosition - 0.5;
return true;
}
var leftNode = layer[index - 1];
var leftNodePriority = (leftNode.upstreamPriority + leftNode.downstreamPriority) / 2;
// check if there is space between the left and the current node
if (leftNode.gridPosition < node.gridPosition - 1) {
node.gridPosition = node.gridPosition - 0.5;
return true;
}
// we have reached a node with higher priority; no movement is allowed
if (leftNodePriority > priority ||
Math.abs(leftNodePriority - priority) < 0.0001) {
return false;
}
// The left node has lower priority - try to move it
if (this.moveLeft(leftNode, layer, priority)) {
node.gridPosition = node.gridPosition - 0.5;
return true;
}
return false;
},
mapVirtualNode: function (node, link) {
this.nodeToLinkMap.set(node, link);
if (!this.linkToNodeMap.containsKey(link)) {
this.linkToNodeMap.set(link, []);
}
this.linkToNodeMap.get(link).push(node);
},
_nodesInLink: function (node) {
return this.linkToNodeMap.get(this.nodeToLinkMap.get(node));
},
/// <summary>
/// Inserts dummy nodes to break long links.
/// </summary>
_dummify: function () {
this.linkToNodeMap = new Dictionary();
this.nodeToLinkMap = new Dictionary();
var layer, pos, newNode, node, r, newLink, i, l, links = this.graph.links.slice(0);
var layers = this.layers;
var addLinkBetweenLayers = function(upLayer, downLayer, link) {
layers[upLayer].linksTo[downLayer] = layers[upLayer].linksTo[downLayer] || [];
layers[upLayer].linksTo[downLayer].push(link);
};
for (l = 0; l < links.length; l++) {
var link = links[l];
var o = link.source;
var d = link.target;
var oLayer = o.layer;
var dLayer = d.layer;
var oPos = o.gridPosition;
var dPos = d.gridPosition;
var step = (dPos - oPos) / Math.abs(dLayer - oLayer);
var p = o;
if (oLayer - dLayer > 1) {
for (i = oLayer - 1; i > dLayer; i--) {
newNode = new Node();
newNode.x = o.x;
newNode.y = o.y;
newNode.width = o.width / 100;
newNode.height = o.height / 100;
layer = layers[i];
pos = (i - dLayer) * step + oPos;
if (pos > layer.length) {
pos = layer.length;
}
// check if origin and dest are both last
if (oPos >= layers[oLayer].length - 1 &&
dPos >= layers[dLayer].length - 1) {
pos = layer.length;
}
// check if origin and destination are both first
else if (oPos === 0 && dPos === 0) {
pos = 0;
}
newNode.layer = i;
newNode.uBaryCenter = 0.0;
newNode.dBaryCenter = 0.0;
newNode.upstreamLinkCount = 0;
newNode.downstreamLinkCount = 0;
newNode.gridPosition = pos;
newNode.isVirtual = true;
Utils.insert(layer, newNode, pos);
// translate rightwards nodes' positions
for (r = pos + 1; r < layer.length; r++) {
node = layer[r];
node.gridPosition = node.gridPosition + 1;
}
newLink = new Link(p, newNode);
newLink.depthOfDumminess = 0;
addLinkBetweenLayers(i - 1, i, newLink);
p = newNode;
// add the new node and the new link to the graph
this.graph._addNode(newNode);
this.graph.addLink(newLink);
newNode.index = this.graph.nodes.length - 1;
this.mapVirtualNode(newNode, link);
}
// set the origin of the real arrow to the last dummy
addLinkBetweenLayers(dLayer - 1, dLayer, newLink);
link.changeSource(p);
link.depthOfDumminess = oLayer - dLayer - 1;
} else if (oLayer - dLayer < -1) {
for (i = oLayer + 1; i < dLayer; i++) {
newNode = new Node();
newNode.x = o.x;
newNode.y = o.y;
newNode.width = o.width / 100;
newNode.height = o.height / 100;
layer = layers[i];
pos = (i - oLayer) * step + oPos;
if (pos > layer.length) {
pos = layer.length;
}
// check if origin and dest are both last
if (oPos >= layers[oLayer].length - 1 &&
dPos >= layers[dLayer].length - 1) {
pos = layer.length;
}
// check if origin and destination are both first
else if (oPos === 0 && dPos === 0) {
pos = 0;
}
newNode.layer = i;
newNode.uBaryCenter = 0.0;
newNode.dBaryCenter = 0.0;
newNode.upstreamLinkCount = 0;
newNode.downstreamLinkCount = 0;
newNode.gridPosition = pos;
newNode.isVirtual = true;
pos &= pos; // truncates to int
Utils.insert(layer, newNode, pos);
// translate rightwards nodes' positions
for (r = pos + 1; r < layer.length; r++) {
node = layer[r];
node.gridPosition = node.gridPosition + 1;
}
newLink = new Link(p, newNode);
newLink.depthOfDumminess = 0;
addLinkBetweenLayers(i - 1, i, newLink);
p = newNode;
// add the new node and the new link to the graph
this.graph._addNode(newNode);
this.graph.addLink(newLink);
newNode.index = this.graph.nodes.length - 1;
this.mapVirtualNode(newNode, link);
}
addLinkBetweenLayers(dLayer - 1, dLayer, link);
// Set the origin of the real arrow to the last dummy
link.changeSource(p);
link.depthOfDumminess = dLayer - oLayer - 1;
} else {
addLinkBetweenLayers(oLayer, dLayer, link);
}
}
},
/// <summary>
/// Removes the dummy nodes inserted earlier to break long links.
/// </summary>
/// <remarks>The virtual nodes are effectively turned into intermediate connection points.</remarks>
_dedummify: function () {
var dedum = true;
while (dedum) {
dedum = false;
for (var l = 0; l < this.graph.links.length; l++) {
var link = this.graph.links[l];
if (!link.depthOfDumminess) {
continue;
}
var points = [];
// add points in reverse order
points.unshift({ x: link.target.x, y: link.target.y });
points.unshift({ x: link.source.x, y: link.source.y });
// _dedummify the link
var temp = link;
var depthOfDumminess = link.depthOfDumminess;
for (var d = 0; d < depthOfDumminess; d++) {
var node = temp.source;
var prevLink = node.incoming[0];
points.unshift({ x: prevLink.source.x, y: prevLink.source.y });
temp = prevLink;
}
// restore the original link origin
link.changeSource(temp.source);
// reset dummification flag
link.depthOfDumminess = 0;
// note that we only need the intermediate points, floating links have been dropped in the analysis
if (points.length > 2) {
// first and last are the endpoints
points.splice(0, 1);
points.splice(points.length - 1);
link.points = points;
}
else {
link.points = [];
}
// we are not going to delete the dummy elements;
// they won't be needed anymore anyway.
dedum = true;
break;
}
}
},
/// <summary>
/// Optimizes/reduces the crossings between the layers by turning the crossing problem into a (combinatorial) number ordering problem.
/// </summary>
_optimizeCrossings: function () {
var moves = -1, i;
var maxIterations = 3;
var iter = 0;
while (moves !== 0) {
if (iter++ > maxIterations) {
break;
}
moves = 0;
for (i = this.layers.length - 1; i >= 1; i--) {
moves += this.optimizeLayerCrossings(false, i);
}
for (i = 0; i < this.layers.length - 1; i++) {
moves += this.optimizeLayerCrossings(true, i);
}
}
},
calcUpData: function (layer) {
if (layer === 0) {
return;
}
var considered = this.layers[layer], i, l, link;
var upLayer = new Set();
var temp = this.layers[layer - 1];
for (i = 0; i < temp.length; i++) {
upLayer.add(temp[i]);
}
for (i = 0; i < considered.length; i++) {
var node = considered[i];
// calculate barycenter
var sum = 0;
var total = 0;
for (l = 0; l < node.incoming.length; l++) {
link = node.incoming[l];
if (upLayer.contains(link.source)) {
total++;
sum += link.source.gridPosition;
}
}
for (l = 0; l < node.outgoing.length; l++) {
link = node.outgoing[l];
if (upLayer.contains(link.target)) {
total++;
sum += link.target.gridPosition;
}
}
if (total > 0) {
node.uBaryCenter = sum / total;
node.upstreamLinkCount = total;
}
else {
node.uBaryCenter = i;
node.upstreamLinkCount = 0;
}
}
},
calcDownData: function (layer) {
if (layer === this.layers.length - 1) {
return;
}
var considered = this.layers[layer], i , l, link;
var downLayer = new Set();
var temp = this.layers[layer + 1];
for (i = 0; i < temp.length; i++) {
downLayer.add(temp[i]);
}
for (i = 0; i < considered.length; i++) {
var node = considered[i];
// calculate barycenter
var sum = 0;
var total = 0;
for (l = 0; l < node.incoming.length; l++) {
link = node.incoming[l];
if (downLayer.contains(link.source)) {
total++;
sum += link.source.gridPosition;
}
}
for (l = 0; l < node.outgoing.length; l++) {
link = node.outgoing[l];
if (downLayer.contains(link.target)) {
total++;
sum += link.target.gridPosition;
}
}
if (total > 0) {
node.dBaryCenter = sum / total;
node.downstreamLinkCount = total;
}
else {
node.dBaryCenter = i;
node.downstreamLinkCount = 0;
}
}
},
/// <summary>
/// Optimizes the crossings.
/// </summary>
/// <remarks>The big trick here is the usage of weights or values attached to connected nodes which turn a problem of crossing links
/// to an a problem of ordering numbers.</remarks>
/// <param name="layerIndex">The layer index.</param>
/// <param name="movingDownwards">If set to <c>true</c> we move down in the layer stack.</param>
/// <returns>The number of nodes having moved, i.e. the number of crossings reduced.</returns>
optimizeLayerCrossings: function (down, layer) {
var iconsidered;
var considered;
if (down) {
considered = this.layers[iconsidered = layer + 1];
}
else {
considered = this.layers[iconsidered = layer - 1];
}
// remember what it was
var presorted = considered.slice(0);
// calculate barycenters for all nodes in the considered layer
if (down) {
this.calcUpData(iconsidered);
}
else {
this.calcDownData(iconsidered);
}
var that = this;
// sort nodes within this layer according to the barycenters
considered.sort(function(n1, n2) {
var n1BaryCenter = that.calcBaryCenter(n1),
n2BaryCenter = that.calcBaryCenter(n2);
if (Math.abs(n1BaryCenter - n2BaryCenter) < 0.0001) {
// in case of coinciding barycenters compare by the count of in/out links
if (n1.degree() === n2.degree()) {
return that.compareByIndex(n1, n2);
}
else if (n1.degree() < n2.degree()) {
return 1;
}
return -1;
}
var compareValue = (n2BaryCenter - n1BaryCenter) * 1000;
if (compareValue > 0) {
return -1;
}
else if (compareValue < 0) {
return 1;
}
return that.compareByIndex(n1, n2);
});
// count relocations
var i, moves = 0;
for (i = 0; i < considered.length; i++) {
if (considered[i] !== presorted[i]) {
moves++;
}
}
if (moves > 0) {
// now that the boxes have been arranged, update their grid positions
var inode = 0;
for (i = 0; i < considered.length; i++) {
var node = considered[i];
node.gridPosition = inode++;
}
}
return moves;
},
/// <summary>
/// Swaps a pair of nodes in a layer.
/// </summary>
/// <param name="layerIndex">Index of the layer.</param>
/// <param name="n">The Nth node in the layer.</param>
_swapPairs: function () {
var maxIterations = this.options.layeredIterations;
var iter = 0;
while (true) {
if (iter++ > maxIterations) {
break;
}
var downwards = (iter % 4 <= 1);
var secondPass = (iter % 4 === 1);
for (var l = (downwards ? 0 : this.layers.length - 1);
downwards ? l <= this.layers.length - 1 : l >= 0; l += (downwards ? 1 : -1)) {
var layer = this.layers[l];
var hasSwapped = false;
// there is no need to recalculate crossings if they were calculated
// on the previous step and nothing has changed
var calcCrossings = true;
var memCrossings = 0;
for (var n = 0; n < layer.length - 1; n++) {
// count crossings
var up = 0;
var down = 0;
var crossBefore = 0;
if (calcCrossings) {
if (l !== 0) {
up = this.countLinksCrossingBetweenTwoLayers(l - 1, l);
}
if (l !== this.layers.length - 1) {
down = this.countLinksCrossingBetweenTwoLayers(l, l + 1);
}
if (downwards) {
up *= 2;
}
else {
down *= 2;
}
crossBefore = up + down;
}
else {
crossBefore = memCrossings;
}
if (crossBefore === 0) {
continue;
}
// Swap nodes
var node1 = layer[n];
var node2 = layer[n + 1];
var node1GridPos = node1.gridPosition;
var node2GridPos = node2.gridPosition;
layer[n] = node2;
layer[n + 1] = node1;
node1.gridPosition = node2GridPos;
node2.gridPosition = node1GridPos;
// count crossings again and if worse than before, restore swapping
up = 0;
if (l !== 0) {
up = this.countLinksCrossingBetweenTwoLayers(l - 1, l);
}
down = 0;
if (l !== this.layers.length - 1) {
down = this.countLinksCrossingBetweenTwoLayers(l, l + 1);
}
if (downwards) {
up *= 2;
}
else {
down *= 2;
}
var crossAfter = up + down;
var revert = false;
if (secondPass) {
revert = crossAfter >= crossBefore;
}
else {
revert = crossAfter > crossBefore;
}
if (revert) {
node1 = layer[n];
node2 = layer[n + 1];
node1GridPos = node1.gridPosition;
node2GridPos = node2.gridPosition;
layer[n] = node2;
layer[n + 1] = node1;
node1.gridPosition = node2GridPos;
node2.gridPosition = node1GridPos;
// nothing has changed, remember the crossings so that
// they are not calculated again on the next step
memCrossings = crossBefore;
calcCrossings = false;
}
else {
hasSwapped = true;
calcCrossings = true;
}
}
if (hasSwapped) {
if (l !== this.layers.length - 1) {
this.calcUpData(l + 1);
}
if (l !== 0) {
this.calcDownData(l - 1);
}
}
}
}
},
/// <summary>
/// Counts the number of links crossing between two layers.
/// </summary>
/// <param name="layerIndex1">The layer index.</param>
/// <param name="layerIndex2">Another layer index.</param>
/// <returns></returns>
countLinksCrossingBetweenTwoLayers: function (ulayer, dlayer) {
var links = this.layers[ulayer].linksTo[dlayer];
var link1, link2, n11, n12, n21, n22, l1, l2;
var crossings = 0;
var length = links.length;
for (l1 = 0; l1 < length; l1++) {
link1 = links[l1];
for (l2 = l1 + 1; l2 < length; l2++) {
link2 = links[l2];
if (link1.target.layer === dlayer) {
n11 = link1.source;
n12 = link1.target;
}
else {
n11 = link1.target;
n12 = link1.source;
}
if (link2.target.layer === dlayer) {
n21 = link2.source;
n22 = link2.target;
}
else {
n21 = link2.target;
n22 = link2.source;
}
var n11gp = n11.gridPosition;
var n12gp = n12.gridPosition;
var n21gp = n21.gridPosition;
var n22gp = n22.gridPosition;
if ((n11gp - n21gp) * (n12gp - n22gp) < 0) {
crossings++;
}
}
}
return crossings;
},
calcBaryCenter: function (node) {
var upstreamLinkCount = node.upstreamLinkCount;
var downstreamLinkCount = node.downstreamLinkCount;
var uBaryCenter = node.uBaryCenter;
var dBaryCenter = node.dBaryCenter;
if (upstreamLinkCount > 0 && downstreamLinkCount > 0) {
return (uBaryCenter + dBaryCenter) / 2;
}
if (upstreamLinkCount > 0) {
return uBaryCenter;
}
if (downstreamLinkCount > 0) {
return dBaryCenter;
}
return 0;
},
_gridPositionComparer: function (x, y) {
if (x.gridPosition < y.gridPosition) {
return -1;
}
if (x.gridPosition > y.gridPosition) {
return 1;
}
return 0;
},
_positionAscendingComparer: function (x, y) {
return x.k < y.k ? -1 : x.k > y.k ? 1 : 0;
},
_positionDescendingComparer: function (x, y) {
return x.k < y.k ? 1 : x.k > y.k ? -1 : 0;
},
_firstVirtualNode: function (layer) {
for (var c = 0; c < layer.length; c++) {
if (layer[c].isVirtual) {
return c;
}
}
return -1;
},
compareByIndex: function (o1, o2) {
var i1 = o1.index;
var i2 = o2.index;
if (i1 < i2) {
return 1;
}
if (i1 > i2) {
return -1;
}
return 0;
},
intDiv: function (numerator, denominator) {
return (numerator - numerator % denominator) / denominator;
},
nextVirtualNode: function (layer, node) {
var nodeIndex = node.layerIndex;
for (var i = nodeIndex + 1; i < layer.length; ++i) {
if (layer[i].isVirtual) {
return layer[i];
}
}
return null;
}
});
/**
* Captures the state of a diagram; node positions, link points and so on.
* @type {*}
*/
var LayoutState = kendo.Class.extend({
init: function (diagram, graphOrNodes) {
if (Utils.isUndefined(diagram)) {
throw "No diagram given";
}
this.diagram = diagram;
this.nodeMap = new Dictionary();
this.linkMap = new Dictionary();
this.capture(graphOrNodes ? graphOrNodes : diagram);
},
/**
* Will capture either
* - the state of the shapes and the intermediate points of the connections in the diagram
* - the bounds of the nodes contained in the Graph together with the intermediate points of the links in the Graph
* - the bounds of the nodes in the Array<Node>
* - the links points and node bounds in the literal object
* @param diagramOrGraphOrNodes
*/
capture: function (diagramOrGraphOrNodes) {
var node,
nodes,
shape,
i,
conn,
link,
links;
if (diagramOrGraphOrNodes instanceof diagram.Graph) {
for (i = 0; i < diagramOrGraphOrNodes.nodes.length; i++) {
node = diagramOrGraphOrNodes.nodes[i];
shape = node.associatedShape;
//shape.bounds(new Rect(node.x, node.y, node.width, node.height));
this.nodeMap.set(shape.visual.id, new Rect(node.x, node.y, node.width, node.height));
}
for (i = 0; i < diagramOrGraphOrNodes.links.length; i++) {
link = diagramOrGraphOrNodes.links[i];
conn = link.associatedConnection;
this.linkMap.set(conn.visual.id, link.points());
}
}
else if (diagramOrGraphOrNodes instanceof Array) {
nodes = diagramOrGraphOrNodes;
for (i = 0; i < nodes.length; i++) {
node = nodes[i];
shape = node.associatedShape;
if (shape) {
this.nodeMap.set(shape.visual.id, new Rect(node.x, node.y, node.width, node.height));
}
}
}
else if (diagramOrGraphOrNodes.hasOwnProperty("links") && diagramOrGraphOrNodes.hasOwnProperty("nodes")) {
nodes = diagramOrGraphOrNodes.nodes;
links = diagramOrGraphOrNodes.links;
for (i = 0; i < nodes.length; i++) {
node = nodes[i];
shape = node.associatedShape;
if (shape) {
this.nodeMap.set(shape.visual.id, new Rect(node.x, node.y, node.width, node.height));
}
}
for (i = 0; i < links.length; i++) {
link = links[i];
conn = link.associatedConnection;
if (conn) {
this.linkMap.set(conn.visual.id, link.points);
}
}
}
else { // capture the diagram
var shapes = this.diagram.shapes;
var connections = this.diagram.connections;
for (i = 0; i < shapes.length; i++) {
shape = shapes[i];
this.nodeMap.set(shape.visual.id, shape.bounds());
}
for (i = 0; i < connections.length; i++) {
conn = connections[i];
this.linkMap.set(conn.visual.id, conn.points());
}
}
}
});
deepExtend(diagram, {
init: function (element) {
kendo.init(element, diagram.ui);
},
SpringLayout: SpringLayout,
TreeLayout: TreeLayout,
GraphAdapter: DiagramToHyperTreeAdapter,
LayeredLayout: LayeredLayout,
LayoutBase: LayoutBase,
LayoutState: LayoutState
});
})(window.kendo.jQuery);
}, __webpack_require__(3));
/***/ }),
/***/ 877:
/***/ (function(module, exports) {
module.exports = require("./math");
/***/ })
/******/ });