diff options
Diffstat (limited to 'src/js/layout/mxGraphLayout.js')
-rw-r--r-- | src/js/layout/mxGraphLayout.js | 503 |
1 files changed, 503 insertions, 0 deletions
diff --git a/src/js/layout/mxGraphLayout.js b/src/js/layout/mxGraphLayout.js new file mode 100644 index 0000000..c9f5f32 --- /dev/null +++ b/src/js/layout/mxGraphLayout.js @@ -0,0 +1,503 @@ +/** + * $Id: mxGraphLayout.js,v 1.48 2012-08-21 17:22:21 gaudenz Exp $ + * Copyright (c) 2006-2010, JGraph Ltd + */ +/** + * Class: mxGraphLayout + * + * Base class for all layout algorithms in mxGraph. Main public functions are + * <move> for handling a moved cell within a layouted parent, and <execute> for + * running the layout on a given parent cell. + * + * Known Subclasses: + * + * <mxCircleLayout>, <mxCompactTreeLayout>, <mxCompositeLayout>, + * <mxFastOrganicLayout>, <mxParallelEdgeLayout>, <mxPartitionLayout>, + * <mxStackLayout> + * + * Constructor: mxGraphLayout + * + * Constructs a new layout using the given layouts. + * + * Arguments: + * + * graph - Enclosing + */ +function mxGraphLayout(graph) +{ + this.graph = graph; +}; + +/** + * Variable: graph + * + * Reference to the enclosing <mxGraph>. + */ +mxGraphLayout.prototype.graph = null; + +/** + * Variable: useBoundingBox + * + * Boolean indicating if the bounding box of the label should be used if + * its available. Default is true. + */ +mxGraphLayout.prototype.useBoundingBox = true; + +/** + * Variable: parent + * + * The parent cell of the layout, if any + */ +mxGraphLayout.prototype.parent = null; + +/** + * Function: moveCell + * + * Notified when a cell is being moved in a parent that has automatic + * layout to update the cell state (eg. index) so that the outcome of the + * layout will position the vertex as close to the point (x, y) as + * possible. + * + * Empty implementation. + * + * Parameters: + * + * cell - <mxCell> which has been moved. + * x - X-coordinate of the new cell location. + * y - Y-coordinate of the new cell location. + */ +mxGraphLayout.prototype.moveCell = function(cell, x, y) { }; + +/** + * Function: execute + * + * Executes the layout algorithm for the children of the given parent. + * + * Parameters: + * + * parent - <mxCell> whose children should be layed out. + */ +mxGraphLayout.prototype.execute = function(parent) { }; + +/** + * Function: getGraph + * + * Returns the graph that this layout operates on. + */ +mxGraphLayout.prototype.getGraph = function() +{ + return this.graph; +}; + +/** + * Function: getConstraint + * + * Returns the constraint for the given key and cell. The optional edge and + * source arguments are used to return inbound and outgoing routing- + * constraints for the given edge and vertex. This implementation always + * returns the value for the given key in the style of the given cell. + * + * Parameters: + * + * key - Key of the constraint to be returned. + * cell - <mxCell> whose constraint should be returned. + * edge - Optional <mxCell> that represents the connection whose constraint + * should be returned. Default is null. + * source - Optional boolean that specifies if the connection is incoming + * or outgoing. Default is null. + */ +mxGraphLayout.prototype.getConstraint = function(key, cell, edge, source) +{ + var state = this.graph.view.getState(cell); + var style = (state != null) ? state.style : this.graph.getCellStyle(cell); + + return (style != null) ? style[key] : null; +}; + +/** + * Function: traverse + * + * Traverses the (directed) graph invoking the given function for each + * visited vertex and edge. The function is invoked with the current vertex + * and the incoming edge as a parameter. This implementation makes sure + * each vertex is only visited once. The function may return false if the + * traversal should stop at the given vertex. + * + * Example: + * + * (code) + * mxLog.show(); + * var cell = graph.getSelectionCell(); + * graph.traverse(cell, false, function(vertex, edge) + * { + * mxLog.debug(graph.getLabel(vertex)); + * }); + * (end) + * + * Parameters: + * + * vertex - <mxCell> that represents the vertex where the traversal starts. + * directed - Optional boolean indicating if edges should only be traversed + * from source to target. Default is true. + * func - Visitor function that takes the current vertex and the incoming + * edge as arguments. The traversal stops if the function returns false. + * edge - Optional <mxCell> that represents the incoming edge. This is + * null for the first step of the traversal. + * visited - Optional array of cell paths for the visited cells. + */ +mxGraphLayout.traverse = function(vertex, directed, func, edge, visited) +{ + if (func != null && vertex != null) + { + directed = (directed != null) ? directed : true; + visited = visited || []; + var id = mxCellPath.create(vertex); + + if (visited[id] == null) + { + visited[id] = vertex; + var result = func(vertex, edge); + + if (result == null || result) + { + var edgeCount = this.graph.model.getEdgeCount(vertex); + + if (edgeCount > 0) + { + for (var i = 0; i < edgeCount; i++) + { + var e = this.graph.model.getEdgeAt(vertex, i); + var isSource = this.graph.model.getTerminal(e, true) == vertex; + + if (!directed || isSource) + { + var next = this.graph.view.getVisibleTerminal(e, !isSource); + this.traverse(next, directed, func, e, visited); + } + } + } + } + } + } +}; + +/** + * Function: isVertexMovable + * + * Returns a boolean indicating if the given <mxCell> is movable or + * bendable by the algorithm. This implementation returns true if the given + * cell is movable in the graph. + * + * Parameters: + * + * cell - <mxCell> whose movable state should be returned. + */ +mxGraphLayout.prototype.isVertexMovable = function(cell) +{ + return this.graph.isCellMovable(cell); +}; + +/** + * Function: isVertexIgnored + * + * Returns a boolean indicating if the given <mxCell> should be ignored by + * the algorithm. This implementation returns false for all vertices. + * + * Parameters: + * + * vertex - <mxCell> whose ignored state should be returned. + */ +mxGraphLayout.prototype.isVertexIgnored = function(vertex) +{ + return !this.graph.getModel().isVertex(vertex) || + !this.graph.isCellVisible(vertex); +}; + +/** + * Function: isEdgeIgnored + * + * Returns a boolean indicating if the given <mxCell> should be ignored by + * the algorithm. This implementation returns false for all vertices. + * + * Parameters: + * + * cell - <mxCell> whose ignored state should be returned. + */ +mxGraphLayout.prototype.isEdgeIgnored = function(edge) +{ + var model = this.graph.getModel(); + + return !model.isEdge(edge) || + !this.graph.isCellVisible(edge) || + model.getTerminal(edge, true) == null || + model.getTerminal(edge, false) == null; +}; + +/** + * Function: setEdgeStyleEnabled + * + * Disables or enables the edge style of the given edge. + */ +mxGraphLayout.prototype.setEdgeStyleEnabled = function(edge, value) +{ + this.graph.setCellStyles(mxConstants.STYLE_NOEDGESTYLE, + (value) ? '0' : '1', [edge]); +}; + +/** + * Function: setOrthogonalEdge + * + * Disables or enables orthogonal end segments of the given edge. + */ +mxGraphLayout.prototype.setOrthogonalEdge = function(edge, value) +{ + this.graph.setCellStyles(mxConstants.STYLE_ORTHOGONAL, + (value) ? '1' : '0', [edge]); +}; + +/** + * Function: getParentOffset + * + * Determines the offset of the given parent to the parent + * of the layout + */ +mxGraphLayout.prototype.getParentOffset = function(parent) +{ + var result = new mxPoint(); + + if (parent != null && parent != this.parent) + { + var model = this.graph.getModel(); + + if (model.isAncestor(this.parent, parent)) + { + var parentGeo = model.getGeometry(parent); + + while (parent != this.parent) + { + result.x = result.x + parentGeo.x; + result.y = result.y + parentGeo.y; + + parent = model.getParent(parent);; + parentGeo = model.getGeometry(parent); + } + } + } + + return result; +}; + +/** + * Function: setEdgePoints + * + * Replaces the array of mxPoints in the geometry of the given edge + * with the given array of mxPoints. + */ +mxGraphLayout.prototype.setEdgePoints = function(edge, points) +{ + if (edge != null) + { + var model = this.graph.model; + var geometry = model.getGeometry(edge); + + if (geometry == null) + { + geometry = new mxGeometry(); + geometry.setRelative(true); + } + else + { + geometry = geometry.clone(); + } + + if (this.parent != null && points != null) + { + var parent = model.getParent(edge); + + var parentOffset = this.getParentOffset(parent); + + for (var i = 0; i < points.length; i++) + { + points[i].x = points[i].x - parentOffset.x; + points[i].y = points[i].y - parentOffset.y; + } + } + + geometry.points = points; + model.setGeometry(edge, geometry); + } +}; + +/** + * Function: setVertexLocation + * + * Sets the new position of the given cell taking into account the size of + * the bounding box if <useBoundingBox> is true. The change is only carried + * out if the new location is not equal to the existing location, otherwise + * the geometry is not replaced with an updated instance. The new or old + * bounds are returned (including overlapping labels). + * + * Parameters: + * + * cell - <mxCell> whose geometry is to be set. + * x - Integer that defines the x-coordinate of the new location. + * y - Integer that defines the y-coordinate of the new location. + */ +mxGraphLayout.prototype.setVertexLocation = function(cell, x, y) +{ + var model = this.graph.getModel(); + var geometry = model.getGeometry(cell); + var result = null; + + if (geometry != null) + { + result = new mxRectangle(x, y, geometry.width, geometry.height); + + // Checks for oversize labels and shifts the result + // TODO: Use mxUtils.getStringSize for label bounds + if (this.useBoundingBox) + { + var state = this.graph.getView().getState(cell); + + if (state != null && state.text != null && state.text.boundingBox != null) + { + var scale = this.graph.getView().scale; + var box = state.text.boundingBox; + + if (state.text.boundingBox.x < state.x) + { + x += (state.x - box.x) / scale; + result.width = box.width; + } + + if (state.text.boundingBox.y < state.y) + { + y += (state.y - box.y) / scale; + result.height = box.height; + } + } + } + + if (this.parent != null) + { + var parent = model.getParent(cell); + + if (parent != null && parent != this.parent) + { + var parentOffset = this.getParentOffset(parent); + + x = x - parentOffset.x; + y = y - parentOffset.y; + } + } + + if (geometry.x != x || geometry.y != y) + { + geometry = geometry.clone(); + geometry.x = x; + geometry.y = y; + + model.setGeometry(cell, geometry); + } + } + + return result; +}; + +/** + * Function: getVertexBounds + * + * Returns an <mxRectangle> that defines the bounds of the given cell or + * the bounding box if <useBoundingBox> is true. + */ +mxGraphLayout.prototype.getVertexBounds = function(cell) +{ + var geo = this.graph.getModel().getGeometry(cell); + + // Checks for oversize label bounding box and corrects + // the return value accordingly + // TODO: Use mxUtils.getStringSize for label bounds + if (this.useBoundingBox) + { + var state = this.graph.getView().getState(cell); + + if (state != null && state.text != null && state.text.boundingBox != null) + { + var scale = this.graph.getView().scale; + var tmp = state.text.boundingBox; + + var dx0 = Math.max(state.x - tmp.x, 0) / scale; + var dy0 = Math.max(state.y - tmp.y, 0) / scale; + var dx1 = Math.max((tmp.x + tmp.width) - (state.x + state.width), 0) / scale; + var dy1 = Math.max((tmp.y + tmp.height) - (state.y + state.height), 0) / scale; + + geo = new mxRectangle(geo.x - dx0, geo.y - dy0, + geo.width + dx0 + dx1, geo.height + dy0 + dy1); + } + } + + if (this.parent != null) + { + var parent = this.graph.getModel().getParent(cell); + geo = geo.clone(); + + if (parent != null && parent != this.parent) + { + var parentOffset = this.getParentOffset(parent); + geo.x = geo.x + parentOffset.x; + geo.y = geo.y + parentOffset.y; + } + } + + return new mxRectangle(geo.x, geo.y, geo.width, geo.height); +}; + +/** + * Function: arrangeGroups + * + * Updates the bounds of the given groups to include all children. Call + * this with the groups in parent to child order, top-most group first, eg. + * + * arrangeGroups(graph, mxUtils.sortCells(Arrays.asList( + * new Object[] { v1, v3 }), true).toArray(), 10); + */ +mxGraphLayout.prototype.arrangeGroups = function(groups, border) +{ + this.graph.getModel().beginUpdate(); + try + { + for (var i = groups.length - 1; i >= 0; i--) + { + var group = groups[i]; + var children = this.graph.getChildVertices(group); + var bounds = this.graph.getBoundingBoxFromGeometry(children); + var geometry = this.graph.getCellGeometry(group); + var left = 0; + var top = 0; + + // Adds the size of the title area for swimlanes + if (this.graph.isSwimlane(group)) + { + var size = this.graph.getStartSize(group); + left = size.width; + top = size.height; + } + + if (bounds != null && geometry != null) + { + geometry = geometry.clone(); + geometry.x = geometry.x + bounds.x - border - left; + geometry.y = geometry.y + bounds.y - border - top; + geometry.width = bounds.width + 2 * border + left; + geometry.height = bounds.height + 2 * border + top; + this.graph.getModel().setGeometry(group, geometry); + this.graph.moveCells(children, border + left - bounds.x, + border + top - bounds.y); + } + } + } + finally + { + this.graph.getModel().endUpdate(); + } +}; |