/** * Copyright (c) 2006-2015, JGraph Ltd * Copyright (c) 2006-2015, Gaudenz Alder */ /** * Class: mxStackLayout * * Extends to create a horizontal or vertical stack of the * child vertices. The children do not need to be connected for this layout * to work. * * Example: * * (code) * var layout = new mxStackLayout(graph, true); * layout.execute(graph.getDefaultParent()); * (end) * * Constructor: mxStackLayout * * Constructs a new stack layout layout for the specified graph, * spacing, orientation and offset. */ function mxStackLayout(graph, horizontal, spacing, x0, y0, border) { mxGraphLayout.call(this, graph); this.horizontal = (horizontal != null) ? horizontal : true; this.spacing = (spacing != null) ? spacing : 0; this.x0 = (x0 != null) ? x0 : 0; this.y0 = (y0 != null) ? y0 : 0; this.border = (border != null) ? border : 0; }; /** * Extends mxGraphLayout. */ mxStackLayout.prototype = new mxGraphLayout(); mxStackLayout.prototype.constructor = mxStackLayout; /** * Variable: horizontal * * Specifies the orientation of the layout. Default is true. */ mxStackLayout.prototype.horizontal = null; /** * Variable: spacing * * Specifies the spacing between the cells. Default is 0. */ mxStackLayout.prototype.spacing = null; /** * Variable: x0 * * Specifies the horizontal origin of the layout. Default is 0. */ mxStackLayout.prototype.x0 = null; /** * Variable: y0 * * Specifies the vertical origin of the layout. Default is 0. */ mxStackLayout.prototype.y0 = null; /** * Variable: border * * Border to be added if fill is true. Default is 0. */ mxStackLayout.prototype.border = 0; /** * Variable: marginTop * * Top margin for the child area. Default is 0. */ mxStackLayout.prototype.marginTop = 0; /** * Variable: marginLeft * * Top margin for the child area. Default is 0. */ mxStackLayout.prototype.marginLeft = 0; /** * Variable: marginRight * * Top margin for the child area. Default is 0. */ mxStackLayout.prototype.marginRight = 0; /** * Variable: marginBottom * * Top margin for the child area. Default is 0. */ mxStackLayout.prototype.marginBottom = 0; /** * Variable: keepFirstLocation * * Boolean indicating if the location of the first cell should be * kept, that is, it will not be moved to x0 or y0. Default is false. */ mxStackLayout.prototype.keepFirstLocation = false; /** * Variable: fill * * Boolean indicating if dimension should be changed to fill out the parent * cell. Default is false. */ mxStackLayout.prototype.fill = false; /** * Variable: resizeParent * * If the parent should be resized to match the width/height of the * stack. Default is false. */ mxStackLayout.prototype.resizeParent = false; /** * Variable: resizeParentMax * * Use maximum of existing value and new value for resize of parent. * Default is false. */ mxStackLayout.prototype.resizeParentMax = false; /** * Variable: resizeLast * * If the last element should be resized to fill out the parent. Default is * false. If is true then this is ignored. */ mxStackLayout.prototype.resizeLast = false; /** * Variable: wrap * * Value at which a new column or row should be created. Default is null. */ mxStackLayout.prototype.wrap = null; /** * Variable: borderCollapse * * If the strokeWidth should be ignored. Default is true. */ mxStackLayout.prototype.borderCollapse = true; /** * Variable: allowGaps * * If gaps should be allowed in the stack. Default is false. */ mxStackLayout.prototype.allowGaps = false; /** * Variable: gridSize * * Grid size for alignment of position and size. Default is 0. */ mxStackLayout.prototype.gridSize = 0; /** * Function: isHorizontal * * Returns . */ mxStackLayout.prototype.isHorizontal = function() { return this.horizontal; }; /** * Function: moveCell * * Implements . */ mxStackLayout.prototype.moveCell = function(cell, x, y) { var model = this.graph.getModel(); var parent = model.getParent(cell); var horizontal = this.isHorizontal(); if (cell != null && parent != null) { var i = 0; var last = 0; var childCount = model.getChildCount(parent); var value = (horizontal) ? x : y; var pstate = this.graph.getView().getState(parent); if (pstate != null) { value -= (horizontal) ? pstate.x : pstate.y; } value /= this.graph.view.scale; for (i = 0; i < childCount; i++) { var child = model.getChildAt(parent, i); if (child != cell) { var bounds = model.getGeometry(child); if (bounds != null) { var tmp = (horizontal) ? bounds.x + bounds.width / 2 : bounds.y + bounds.height / 2; if (last <= value && tmp > value) { break; } last = tmp; } } } // Changes child order in parent var idx = parent.getIndex(cell); idx = Math.max(0, i - ((i > idx) ? 1 : 0)); model.add(parent, cell, idx); } }; /** * Function: getParentSize * * Returns the size for the parent container or the size of the graph * container if the parent is a layer or the root of the model. */ mxStackLayout.prototype.getParentSize = function(parent) { var model = this.graph.getModel(); var pgeo = model.getGeometry(parent); // Handles special case where the parent is either a layer with no // geometry or the current root of the view in which case the size // of the graph's container will be used. if (this.graph.container != null && ((pgeo == null && model.isLayer(parent)) || parent == this.graph.getView().currentRoot)) { var width = this.graph.container.offsetWidth - 1; var height = this.graph.container.offsetHeight - 1; pgeo = new mxRectangle(0, 0, width, height); } return pgeo; }; /** * Function: getLayoutCells * * Returns the cells to be layouted. */ mxStackLayout.prototype.getLayoutCells = function(parent) { var model = this.graph.getModel(); var childCount = model.getChildCount(parent); var cells = []; for (var i = 0; i < childCount; i++) { var child = model.getChildAt(parent, i); if (!this.isVertexIgnored(child) && this.isVertexMovable(child)) { cells.push(child); } } if (this.allowGaps) { cells.sort(mxUtils.bind(this, function(c1, c2) { var geo1 = this.graph.getCellGeometry(c1); var geo2 = this.graph.getCellGeometry(c2); return (this.horizontal) ? ((geo1.x == geo2.x) ? 0 : ((geo1.x > geo2.x > 0) ? 1 : -1)) : ((geo1.y == geo2.y) ? 0 : ((geo1.y > geo2.y > 0) ? 1 : -1)); })); } return cells; }; /** * Function: snap * * Snaps the given value to the grid size. */ mxStackLayout.prototype.snap = function(value) { if (this.gridSize != null && this.gridSize > 0) { value = Math.max(value, this.gridSize); if (value / this.gridSize > 1) { var mod = value % this.gridSize; value += mod > this.gridSize / 2 ? (this.gridSize - mod) : -mod; } } return value; }; /** * Function: execute * * Implements . * * Only children where returns false are taken into * account. */ mxStackLayout.prototype.execute = function(parent) { if (parent != null) { var pgeo = this.getParentSize(parent); var horizontal = this.isHorizontal(); var model = this.graph.getModel(); var fillValue = null; if (pgeo != null) { fillValue = (horizontal) ? pgeo.height - this.marginTop - this.marginBottom : pgeo.width - this.marginLeft - this.marginRight; } fillValue -= 2 * this.border; var x0 = this.x0 + this.border + this.marginLeft; var y0 = this.y0 + this.border + this.marginTop; // Handles swimlane start size if (this.graph.isSwimlane(parent)) { // Uses computed style to get latest var style = this.graph.getCellStyle(parent); var start = mxUtils.getNumber(style, mxConstants.STYLE_STARTSIZE, mxConstants.DEFAULT_STARTSIZE); var horz = mxUtils.getValue(style, mxConstants.STYLE_HORIZONTAL, true) == 1; if (pgeo != null) { if (horz) { start = Math.min(start, pgeo.height); } else { start = Math.min(start, pgeo.width); } } if (horizontal == horz) { fillValue -= start; } if (horz) { y0 += start; } else { x0 += start; } } model.beginUpdate(); try { var tmp = 0; var last = null; var lastValue = 0; var lastChild = null; var cells = this.getLayoutCells(parent); for (var i = 0; i < cells.length; i++) { var child = cells[i]; var geo = model.getGeometry(child); if (geo != null) { geo = geo.clone(); if (this.wrap != null && last != null) { if ((horizontal && last.x + last.width + geo.width + 2 * this.spacing > this.wrap) || (!horizontal && last.y + last.height + geo.height + 2 * this.spacing > this.wrap)) { last = null; if (horizontal) { y0 += tmp + this.spacing; } else { x0 += tmp + this.spacing; } tmp = 0; } } tmp = Math.max(tmp, (horizontal) ? geo.height : geo.width); var sw = 0; if (!this.borderCollapse) { var childStyle = this.graph.getCellStyle(child); sw = mxUtils.getNumber(childStyle, mxConstants.STYLE_STROKEWIDTH, 1); } if (last != null) { var temp = lastValue + this.spacing + Math.floor(sw / 2); if (horizontal) { geo.x = this.snap(((this.allowGaps) ? Math.max(temp, geo.x) : temp) - this.marginLeft) + this.marginLeft; } else { geo.y = this.snap(((this.allowGaps) ? Math.max(temp, geo.y) : temp) - this.marginTop) + this.marginTop; } } else if (!this.keepFirstLocation) { if (horizontal) { geo.x = (this.allowGaps && geo.x > x0) ? Math.max(this.snap(geo.x - this.marginLeft) + this.marginLeft, x0) : x0; } else { geo.y = (this.allowGaps && geo.y > y0) ? Math.max(this.snap(geo.y - this.marginTop) + this.marginTop, y0) : y0; } } if (horizontal) { geo.y = y0; } else { geo.x = x0; } if (this.fill && fillValue != null) { if (horizontal) { geo.height = fillValue; } else { geo.width = fillValue; } } if (horizontal) { geo.width = this.snap(geo.width); } else { geo.height = this.snap(geo.height); } this.setChildGeometry(child, geo); lastChild = child; last = geo; if (horizontal) { lastValue = last.x + last.width + Math.floor(sw / 2); } else { lastValue = last.y + last.height + Math.floor(sw / 2); } } } if (this.resizeParent && pgeo != null && last != null && !this.graph.isCellCollapsed(parent)) { this.updateParentGeometry(parent, pgeo, last); } else if (this.resizeLast && pgeo != null && last != null && lastChild != null) { if (horizontal) { last.width = pgeo.width - last.x - this.spacing - this.marginRight - this.marginLeft; } else { last.height = pgeo.height - last.y - this.spacing - this.marginBottom; } this.setChildGeometry(lastChild, last); } } finally { model.endUpdate(); } } }; /** * Function: setChildGeometry * * Sets the specific geometry to the given child cell. * * Parameters: * * child - The given child of . * geo - The specific geometry of . */ mxStackLayout.prototype.setChildGeometry = function(child, geo) { var geo2 = this.graph.getCellGeometry(child); if (geo2 == null || geo.x != geo2.x || geo.y != geo2.y || geo.width != geo2.width || geo.height != geo2.height) { this.graph.getModel().setGeometry(child, geo); } }; /** * Function: updateParentGeometry * * Updates the geometry of the given parent cell. * * Parameters: * * parent - The given parent of . * pgeo - The new for parent. * last - The last . */ mxStackLayout.prototype.updateParentGeometry = function(parent, pgeo, last) { var horizontal = this.isHorizontal(); var model = this.graph.getModel(); var pgeo2 = pgeo.clone(); if (horizontal) { var tmp = last.x + last.width + this.marginRight + this.border; if (this.resizeParentMax) { pgeo2.width = Math.max(pgeo2.width, tmp); } else { pgeo2.width = tmp; } } else { var tmp = last.y + last.height + this.marginBottom + this.border; if (this.resizeParentMax) { pgeo2.height = Math.max(pgeo2.height, tmp); } else { pgeo2.height = tmp; } } if (pgeo.x != pgeo2.x || pgeo.y != pgeo2.y || pgeo.width != pgeo2.width || pgeo.height != pgeo2.height) { model.setGeometry(parent, pgeo2); } };