UNPKG

mxgraph

Version:

mxGraph is a fully client side JavaScript diagramming library that uses SVG and HTML for rendering.

592 lines (528 loc) 14.3 kB
/** * Copyright (c) 2006-2018, JGraph Ltd * Copyright (c) 2006-2018, Gaudenz Alder */ /** * Class: mxGraphLayout * * Base class for all layout algorithms in mxGraph. Main public functions are * <moveCell> 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: resizeCell * * Notified when a cell is being resized in a parent that has automatic * layout to update the other cells in the layout. * * Empty implementation. * * Parameters: * * cell - <mxCell> which has been moved. * bounds - <mxRectangle> that represents the new cell bounds. */ mxGraphLayout.prototype.resizeCell = function(cell, bounds) { }; /** * 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) { return this.graph.getCurrentCellStyle(cell)[key] }; /** * 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 <mxDictionary> 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 || new mxDictionary(); if (!visited.get(vertex)) { visited.put(vertex, true); 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: isAncestor * * Returns true if the given parent is an ancestor of the given child. * * Parameters: * * parent - <mxCell> that specifies the parent. * child - <mxCell> that specifies the child. * traverseAncestors - boolean whether to */ mxGraphLayout.prototype.isAncestor = function(parent, child, traverseAncestors) { if (!traverseAncestors) { return (this.graph.model.getParent(child) == parent); } if (child == parent) { return false; } while (child != null && child != parent) { child = this.graph.model.getParent(child); } return child == parent; }; /** * 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 * * Shortcut to <mxGraph.updateGroupBounds> with moveGroup set to true. */ mxGraphLayout.prototype.arrangeGroups = function(cells, border, topBorder, rightBorder, bottomBorder, leftBorder) { return this.graph.updateGroupBounds(cells, border, true, topBorder, rightBorder, bottomBorder, leftBorder); }; /** * Class: WeightedCellSorter * * A utility class used to track cells whilst sorting occurs on the weighted * sum of their connected edges. Does not violate (x.compareTo(y)==0) == * (x.equals(y)) * * Constructor: WeightedCellSorter * * Constructs a new weighted cell sorted for the given cell and weight. */ function WeightedCellSorter(cell, weightedValue) { this.cell = cell; this.weightedValue = weightedValue; }; /** * Variable: weightedValue * * The weighted value of the cell stored. */ WeightedCellSorter.prototype.weightedValue = 0; /** * Variable: nudge * * Whether or not to flip equal weight values. */ WeightedCellSorter.prototype.nudge = false; /** * Variable: visited * * Whether or not this cell has been visited in the current assignment. */ WeightedCellSorter.prototype.visited = false; /** * Variable: rankIndex * * The index this cell is in the model rank. */ WeightedCellSorter.prototype.rankIndex = null; /** * Variable: cell * * The cell whose median value is being calculated. */ WeightedCellSorter.prototype.cell = null; /** * Function: compare * * Compares two WeightedCellSorters. */ WeightedCellSorter.prototype.compare = function(a, b) { if (a != null && b != null) { if (b.weightedValue > a.weightedValue) { return -1; } else if (b.weightedValue < a.weightedValue) { return 1; } else { if (b.nudge) { return -1; } else { return 1; } } } else { return 0; } };