123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362 |
- /**
- * Copyright (c) 2006-2018, JGraph Ltd
- * Copyright (c) 2006-2018, Gaudenz Alder
- */
- /**
- * Class: mxWebColaLayout
- *
- * Extends <mxGraphLayout> to implement a WebCola-based layout.
- *
- * Example:
- *
- * (code)
- * var layout = new mxWebColaLayout(graph);
- * layout.execute(graph.getDefaultParent());
- * (end)
- *
- * Constructor: mxWebColaLayout
- *
- * Constructs a new WebCola-based layout for the graph.
- *
- * Arguments:
- *
- * graph - <mxGraph> that contains the cells.
- *
- **/
- function mxWebColaLayout(graph, layoutType)
- /**
- * Constructs a WebCola-based layout
- * @param graph <mxGraph> that contains the cells.
- * @param layoutType Type of WebCola layout
- */
- {
- mxGraphLayout.call(this, graph);
- this.layoutType = layoutType;
- this.originalGeometries = new mxDictionary();
- };
- mxWebColaLayout.prototype = new mxGraphLayout();
- mxWebColaLayout.prototype.constructor = mxWebColaLayout;
- mxWebColaLayout.prototype.layoutType = null;
- mxWebColaLayout.prototype.execute = function(parent)
- /**
- * Runs re-layouting of the portion of a graph from a given starting cell
- * @param parent starting cell
- */
- {
- var movableVertices = this.getReachableVertices(parent);
- var ps = this.graph.getPageSize();
- this.layout = new mxWebColaAdaptor(this.graph, (this.graph.pageVisible) ?
- [ps.width, ps.height] : [800, 800], movableVertices);
- var initial = true;
- var update = function (isUndoable) {
- // console.log("mxColaLayout: update");
- this.updateGraph(isUndoable, initial);
- initial = false;
- }.bind(this);
- this.layout.updatePositions = update;
- //this.resetGraph(this.graph);
- var finalLayout = this.computePositions(this.layout);
- };
- mxWebColaLayout.prototype.getReachableVertices = function(parent)
- /***
- * Finds all vertices reachable from a given parent
- * @param parent starting cell of a search
- * @returns dictionary of vertice IDs that are reachable in the form of {id: True}
- * if undefined is returned, it means parent was not provided and it should be interpreted as all vertices
- * are reachable
- */
- {
- if (parent == undefined)
- return undefined;
- // first, get all incidental edges in a sub-tree (or a connected component if with loops)
- var edges = this.graph.getEdges(parent, null, true, true, true, true);
- // now all vertices that are reachable are subject to change; unreachable should be fixed
- var reachableVertices = void(0);
- if (edges.length != 0)
- {
- var reachableVertices = {};
- for (var i = 0; i < edges.length; i++)
- {
- var edge = edges[i];
- reachableVertices[edge.source.id] = true;
- reachableVertices[edge.target.id] = true;
- }
- }
- // vertices connected by returned edges must be allowed to move, rest must be fixed in place
- return reachableVertices;
- }
- mxWebColaLayout.prototype.computePositions = function()
- /**
- * Executes layout to compute positions
- */
- {
- return this.layout.run();
- }
- mxWebColaLayout.prototype.resetGraph = function(graph)
- /**
- * Resets initial vertex positions
- */
- {
- var model = graph.getModel();
- var cells = model.cells;
- var view = graph.getView();
- model.beginUpdate();
- try
- {
- for (var id in cells)
- {
- var cell = cells[id];
- var state = view.getState(cell);
- var bounds = view.getBoundingBox(state, true);
- var isFirst = true;
- if (cell.isVertex()) {
- var geometry = model.getGeometry(cell);
- if (geometry != null && typeof geometry != "undefined")
- {
- geometry = geometry.clone();
- geometry.offset = null;
- model.setGeometry(cell, geometry);
- }
- }
- }
- }
- finally
- {
- model.endUpdate();
- }
- }
- mxWebColaLayout.prototype.getGroupBounds = function(model, groupCell)
- /**
- * Computes bounds of a group as boundary encompassing all children of a group
- * @param model graph model
- * @param groupCell starting group
- * @returns boundaries of all children inside a group
- */
- {
- var minX = 1000000;
- var minY = 1000000;
- var maxX = -1000000;
- var maxY = -1000000;
- if (groupCell.children == null || groupCell.children.length == 0)
- return null;
- var cellsToVisit = [];
- cellsToVisit = cellsToVisit.concat(groupCell.children);
- while (cellsToVisit.length > 0)
- {
- var child = cellsToVisit.shift();
- if (child.isVertex())
- {
- if (this.layout.isLeafOrCollapsed(child))
- {
- var geometry = model.getGeometry(child);
- if (geometry != null && typeof geometry != "undefined")
- {
- if (geometry.x < minX)
- {
- minX = geometry.x;
- }
- if (geometry.y < minY)
- {
- minY = geometry.y;
- }
- if (geometry.x + geometry.width > maxX)
- {
- maxX = geometry.x + geometry.width;
- }
- if (geometry.y + geometry.height > maxY)
- {
- maxY = geometry.y + geometry.height;
- }
- }
- }
- else
- {
- cellsToVisit = cellsToVisit.concat(child.children);
- }
- }
- }
- var bounds = model.getGeometry(groupCell).clone();
- bounds.x = minX;
- bounds.y = minY;
- bounds.width = maxX - minX;
- bounds.height = maxY - minY;
- return bounds;
- }
- mxWebColaLayout.prototype.adjustChildOffsets = function(model, groupCell)
- /**
- * Adjusts offset of child vertices to be relative to parent groups
- * @param model graph model
- * @param groupCell starting group cell
- */
- {
- if (groupCell.children == null || groupCell.children.length == 0)
- return;
-
- var groupBounds = model.getGeometry(groupCell);
- var offsetX = groupBounds.x;
- var offsetY = groupBounds.y;
- var cellsToVisit = [];
- cellsToVisit = cellsToVisit.concat(groupCell.children);
-
- while (cellsToVisit.length > 0)
- {
- var child = cellsToVisit.shift();
-
- if (child.isVertex())
- {
- if (this.layout.isLeafOrCollapsed(child))
- {
- var geometry = model.getGeometry(child);
-
- if (geometry != null && typeof geometry != "undefined")
- {
- geometry = geometry.clone();
- geometry.x = geometry.x - offsetX;
- geometry.y = geometry.y - offsetY;
- model.setGeometry(child, geometry);
- }
- }
- else
- {
- cellsToVisit = cellsToVisit.concat(child.children);
- }
- }
- }
- }
- mxWebColaLayout.prototype.updateGraph = function(isUndoable = false, initial = false)
- /**
- * Updates graph based on layout's vertex/group positions
- */
- {
- // find X, Y ranges first
- var minX = 1000000;
- var maxX = -1000000;
- var minY = 1000000;
- var maxY = -1000000;
- for (var i = 0; i < this.layout.adaptor._nodes.length; i++)
- {
- var node = this.layout.adaptor._nodes[i];
- var x = node.x;
- var y = node.y;
- minX = Math.min(minX, x);
- minY = Math.min(minY, y);
- maxX = Math.max(maxX, x);
- maxY = Math.max(maxY, y);
- }
- var spanX = maxX - minX;
- var spanY = maxY - minY;
- var model = this.graph.getModel();
- if (isUndoable)
- {
- model.beginUpdate();
- }
- try
- {
- var cells = model.cells;
- var view = this.graph.getView();
-
- // scan leaves and edges
- for (var id in cells)
- {
- var cell = cells[id];
- var state = view.getState(cell);
- var bounds = view.getBoundingBox(state, true);
-
- if (cell.isVertex() && this.layout.isLeafOrCollapsed(cell))
- {
- var nodeId = this.layout.cellToNode[id];
-
- if (typeof nodeId == "undefined")
- continue;
-
- var node = this.layout.adaptor._nodes[nodeId];
- var geometry = model.getGeometry(cell);
-
- if (geometry != null)
- {
- // First run creates a temporary geometry that can
- // be changed in-place to update the view and keeps
- // a copy of the original geometry to use in the
- // final undoable edit to force a change event
- if (initial)
- {
- this.originalGeometries.put(cell, geometry);
- geometry = geometry.clone();
-
- if (model.isVertex(cell))
- {
- geometry.offset = null;
- }
- }
-
- geometry.x = node.x - minX;
- geometry.y = node.y - minY;
-
- if (isUndoable)
- {
- // Restores original geometry for the change to be detected
- cell.geometry = this.originalGeometries.get(cell);
- model.setGeometry(cell, geometry);
- }
- else if (initial)
- {
- cell.geometry = geometry;
- }
- else
- {
- this.graph.view.invalidate(cell, true, true);
- }
- }
- else
- {
- console.log("ERROR: vertex cell id:" + id + " has no geometry!");
- }
- }
- else if (cell.isEdge())
- {
- this.graph.resetEdge(cell);
- }
- }
- // scan groups
- for (var id in cells)
- {
- var cell = cells[id];
- var state = view.getState(cell);
- var bounds = view.getBoundingBox(state, true);
- if (cell.isVertex() && !this.layout.isLeafOrCollapsed(cell))
- {
- var bounds = this.getGroupBounds(model, cell);
- if (bounds != null && typeof bounds != "undefined")
- {
- model.setGeometry(cell, bounds);
- this.adjustChildOffsets(model, cell);
- }
- }
- }
- }
- finally
- {
- if (isUndoable)
- {
- model.endUpdate();
- }
- else
- {
- this.graph.view.validate();
- }
- // console.log("Updated graph, undoable=" + isUndoable + " undo level=" + this.graph.model.updateLevel);
- }
- }
|