mxPartitionLayout.js 5.41 KB
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxPartitionLayout
 * 
 * Extends <mxGraphLayout> for partitioning the parent cell vertically or
 * horizontally by filling the complete area with the child cells. A horizontal
 * layout partitions the height of the given parent whereas a a non-horizontal
 * layout partitions the width. If the parent is a layer (that is, a child of
 * the root node), then the current graph size is partitioned. The children do
 * not need to be connected for this layout to work.
 * 
 * Example:
 * 
 * (code)
 * var layout = new mxPartitionLayout(graph, true, 10, 20);
 * layout.execute(graph.getDefaultParent());
 * (end)
 * 
 * Constructor: mxPartitionLayout
 * 
 * Constructs a new stack layout layout for the specified graph,
 * spacing, orientation and offset.
 */
function mxPartitionLayout(graph, horizontal, spacing, border)
{
	mxGraphLayout.call(this, graph);
	this.horizontal = (horizontal != null) ? horizontal : true;
	this.spacing = spacing || 0;
	this.border = border || 0;
};

/**
 * Extends mxGraphLayout.
 */
mxPartitionLayout.prototype = new mxGraphLayout();
mxPartitionLayout.prototype.constructor = mxPartitionLayout;

/**
 * Variable: horizontal
 * 
 * Boolean indicating the direction in which the space is partitioned.
 * Default is true.
 */
mxPartitionLayout.prototype.horizontal = null;

/**
 * Variable: spacing
 * 
 * Integer that specifies the absolute spacing in pixels between the
 * children. Default is 0.
 */
mxPartitionLayout.prototype.spacing = null;

/**
 * Variable: border
 * 
 * Integer that specifies the absolute inset in pixels for the parent that
 * contains the children. Default is 0.
 */
mxPartitionLayout.prototype.border = null;

/**
 * Variable: resizeVertices
 * 
 * Boolean that specifies if vertices should be resized. Default is true.
 */
mxPartitionLayout.prototype.resizeVertices = true;

/**
 * Function: isHorizontal
 * 
 * Returns <horizontal>.
 */
mxPartitionLayout.prototype.isHorizontal = function()
{
	return this.horizontal;
};

/**
 * Function: moveCell
 * 
 * Implements <mxGraphLayout.moveCell>.
 */
mxPartitionLayout.prototype.moveCell = function(cell, x, y)
{
	var model = this.graph.getModel();
	var parent = model.getParent(cell);
	
	if (cell != null &&
		parent != null)
	{
		var i = 0;
		var last = 0;
		var childCount = model.getChildCount(parent);
		
		// Finds index of the closest swimlane
		// TODO: Take into account the orientation
		for (i = 0; i < childCount; i++)
		{
			var child = model.getChildAt(parent, i);
			var bounds = this.getVertexBounds(child);
			
			if (bounds != null)
			{
				var tmp = bounds.x + bounds.width / 2;
				
				if (last < x && tmp > x)
				{
					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: execute
 * 
 * Implements <mxGraphLayout.execute>. All children where <isVertexIgnored>
 * returns false and <isVertexMovable> returns true are modified.
 */
mxPartitionLayout.prototype.execute = function(parent)
{
	var horizontal = this.isHorizontal();
	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);
	}

	if (pgeo != null)
	{
		var children = [];
		var childCount = model.getChildCount(parent);
		
		for (var i = 0; i < childCount; i++)
		{
			var child = model.getChildAt(parent, i);
			
			if (!this.isVertexIgnored(child) &&
				this.isVertexMovable(child))
			{
				children.push(child);
			}
		}
		
		var n = children.length;

		if (n > 0)
		{
			var x0 = this.border;
			var y0 = this.border;
			var other = (horizontal) ? pgeo.height : pgeo.width;
			other -= 2 * this.border;

			var size = (this.graph.isSwimlane(parent)) ?
				this.graph.getStartSize(parent) :
				new mxRectangle();

			other -= (horizontal) ? size.height : size.width;
			x0 = x0 + size.width;
			y0 = y0 + size.height;

			var tmp = this.border + (n - 1) * this.spacing;
			var value = (horizontal) ?
				((pgeo.width - x0 - tmp) / n) :
				((pgeo.height - y0 - tmp) / n);
			
			// Avoids negative values, that is values where the sum of the
			// spacing plus the border is larger then the available space
			if (value > 0)
			{
				model.beginUpdate();
				try
				{
					for (var i = 0; i < n; i++)
					{
						var child = children[i];
						var geo = model.getGeometry(child);
					
						if (geo != null)
						{
							geo = geo.clone();
							geo.x = x0;
							geo.y = y0;

							if (horizontal)
							{
								if (this.resizeVertices)
								{
									geo.width = value;
									geo.height = other;
								}
								
								x0 += value + this.spacing;
							}
							else
							{
								if (this.resizeVertices)
								{
									geo.height = value;
									geo.width = other;
								}
								
								y0 += value + this.spacing;
							}

							model.setGeometry(child, geo);
						}
					}
				}
				finally
				{
					model.endUpdate();
				}
			}
		}
	}
};