text: /**
   * Copyright (c) 2006-2015, JGraph Ltd
   * Copyright (c) 2006-2015, Gaudenz Alder
   */
  /**
   * Class: mxSwimlaneLayout
   * 
   * A hierarchical layout algorithm.
   * 
   * Constructor: mxSwimlaneLayout
   *
   * Constructs a new hierarchical layout algorithm.
   *
   * Arguments:
   * 
   * graph - Reference to the enclosing <mxGraph>.
   * orientation - Optional constant that defines the orientation of this
   * layout.
   * deterministic - Optional boolean that specifies if this layout should be
   * deterministic. Default is true.
   */
  function mxSwimlaneLayout(graph, orientation, deterministic)
  {
  	mxGraphLayout.call(this, graph);
  	this.orientation = (orientation != null) ? orientation : mxConstants.DIRECTION_NORTH;
  	this.deterministic = (deterministic != null) ? deterministic : true;
  };
  
  /**
   * Extends mxGraphLayout.
   */
  mxSwimlaneLayout.prototype = new mxGraphLayout();
  mxSwimlaneLayout.prototype.constructor = mxSwimlaneLayout;
  
  /**
   * Variable: roots
   * 
   * Holds the array of <mxCell> that this layout contains.
   */
  mxSwimlaneLayout.prototype.roots = null;
  
  /**
   * Variable: swimlanes
   * 
   * Holds the array of <mxCell> of the ordered swimlanes to lay out
   */
  mxSwimlaneLayout.prototype.swimlanes = null;
  
  /**
   * Variable: dummyVertices
   * 
   * Holds an array of <mxCell> of dummy vertices inserted during the layout
   * to pad out empty swimlanes
   */
  mxSwimlaneLayout.prototype.dummyVertices = null;
  
  /**
   * Variable: dummyVertexWidth
   * 
   * The cell width of any dummy vertices inserted
   */
  mxSwimlaneLayout.prototype.dummyVertexWidth = 50;
  
  /**
   * Variable: resizeParent
   * 
   * Specifies if the parent should be resized after the layout so that it
   * contains all the child cells. Default is false. See also <parentBorder>.
   */
  mxSwimlaneLayout.prototype.resizeParent = false;
  
  /**
   * Variable: maintainParentLocation
   * 
   * Specifies if the parent location should be maintained, so that the
   * top, left corner stays the same before and after execution of
   * the layout. Default is false for backwards compatibility.
   */
  mxSwimlaneLayout.prototype.maintainParentLocation = false;
  
  /**
   * Variable: moveParent
   * 
   * Specifies if the parent should be moved if <resizeParent> is enabled.
   * Default is false.
   */
  mxSwimlaneLayout.prototype.moveParent = false;
  
  /**
   * Variable: parentBorder
   * 
   * The border to be added around the children if the parent is to be
   * resized using <resizeParent>. Default is 0.
   */
  mxSwimlaneLayout.prototype.parentBorder = 30;
  
  /**
   * Variable: intraCellSpacing
   * 
   * The spacing buffer added between cells on the same layer. Default is 30.
   */
  mxSwimlaneLayout.prototype.intraCellSpacing = 30;
  
  /**
   * Variable: interRankCellSpacing
   * 
   * The spacing buffer added between cell on adjacent layers. Default is 50.
   */
  mxSwimlaneLayout.prototype.interRankCellSpacing = 100;
  
  /**
   * Variable: interHierarchySpacing
   * 
   * The spacing buffer between unconnected hierarchies. Default is 60.
   */
  mxSwimlaneLayout.prototype.interHierarchySpacing = 60;
  
  /**
   * Variable: parallelEdgeSpacing
   * 
   * The distance between each parallel edge on each ranks for long edges
   */
  mxSwimlaneLayout.prototype.parallelEdgeSpacing = 10;
  
  /**
   * Variable: orientation
   * 
   * The position of the root node(s) relative to the laid out graph in.
   * Default is <mxConstants.DIRECTION_NORTH>.
   */
  mxSwimlaneLayout.prototype.orientation = mxConstants.DIRECTION_NORTH;
  
  /**
   * Variable: fineTuning
   * 
   * Whether or not to perform local optimisations and iterate multiple times
   * through the algorithm. Default is true.
   */
  mxSwimlaneLayout.prototype.fineTuning = true;
  
  /**
   * 
   * Variable: tightenToSource
   * 
   * Whether or not to tighten the assigned ranks of vertices up towards
   * the source cells.
   */
  mxSwimlaneLayout.prototype.tightenToSource = true;
  
  /**
   * Variable: disableEdgeStyle
   * 
   * Specifies if the STYLE_NOEDGESTYLE flag should be set on edges that are
   * modified by the result. Default is true.
   */
  mxSwimlaneLayout.prototype.disableEdgeStyle = true;
  
  /**
   * Variable: traverseAncestors
   * 
   * Whether or not to drill into child cells and layout in reverse
   * group order. This also cause the layout to navigate edges whose 
   * terminal vertices  * have different parents but are in the same 
   * ancestry chain
   */
  mxSwimlaneLayout.prototype.traverseAncestors = true;
  
  /**
   * Variable: model
   * 
   * The internal <mxSwimlaneModel> formed of the layout.
   */
  mxSwimlaneLayout.prototype.model = null;
  
  /**
   * Variable: edgesSet
   * 
   * A cache of edges whose source terminal is the key
   */
  mxSwimlaneLayout.prototype.edgesCache = null;
  
  /**
   * Variable: edgesSet
   * 
   * A cache of edges whose source terminal is the key
   */
  mxHierarchicalLayout.prototype.edgeSourceTermCache = null;
  
  /**
   * Variable: edgesSet
   * 
   * A cache of edges whose source terminal is the key
   */
  mxHierarchicalLayout.prototype.edgesTargetTermCache = null;
  
  /**
   * Variable: edgeStyle
   * 
   * The style to apply between cell layers to edge segments
   */
  mxHierarchicalLayout.prototype.edgeStyle = mxHierarchicalEdgeStyle.POLYLINE;
  
  /**
   * Function: getModel
   * 
   * Returns the internal <mxSwimlaneModel> for this layout algorithm.
   */
  mxSwimlaneLayout.prototype.getModel = function()
  {
  	return this.model;
  };
  
  /**
   * Function: execute
   * 
   * Executes the layout for the children of the specified parent.
   * 
   * Parameters:
   * 
   * parent - Parent <mxCell> that contains the children to be laid out.
   * swimlanes - Ordered array of swimlanes to be laid out
   */
  mxSwimlaneLayout.prototype.execute = function(parent, swimlanes)
  {
  	this.parent = parent;
  	var model = this.graph.model;
  	this.edgesCache = new mxDictionary();
  	this.edgeSourceTermCache = new mxDictionary();
  	this.edgesTargetTermCache = new mxDictionary();
  
  	// If the roots are set and the parent is set, only
  	// use the roots that are some dependent of the that
  	// parent.
  	// If just the root are set, use them as-is
  	// If just the parent is set use it's immediate
  	// children as the initial set
  
  	if (swimlanes == null || swimlanes.length < 1)
  	{
  		// TODO indicate the problem
  		return;
  	}
  
  	if (parent == null)
  	{
  		parent = model.getParent(swimlanes[0]);
  	}
  
  	//  Maintaining parent location
  	this.parentX = null;
  	this.parentY = null;
  	
  	if (parent != this.root && model.isVertex(parent) != null && this.maintainParentLocation)
  	{
  		var geo = this.graph.getCellGeometry(parent);
  		
  		if (geo != null)
  		{
  			this.parentX = geo.x;
  			this.parentY = geo.y;
  		}
  	}
  
  	this.swimlanes = swimlanes;
  	this.dummyVertices = [];
  	// Check the swimlanes all have vertices
  	// in them
  	for (var i = 0; i < swimlanes.length; i++)
  	{
  		var children = this.graph.getChildCells(swimlanes[i]);
  		
  		if (children == null || children.length == 0)
  		{
  			var vertex = this.graph.insertVertex(swimlanes[i], null, null, 0, 0, this.dummyVertexWidth, 0);
  			this.dummyVertices.push(vertex);
  		}
  	}
  	
  	model.beginUpdate();
  	try
  	{
  		this.run(parent);
  		
  		if (this.resizeParent && !this.graph.isCellCollapsed(parent))
  		{
  			this.graph.updateGroupBounds([parent], this.parentBorder, this.moveParent);
  		}
  		
  		// Maintaining parent location
  		if (this.parentX != null && this.parentY != null)
  		{
  			var geo = this.graph.getCellGeometry(parent);
  			
  			if (geo != null)
  			{
  				geo = geo.clone();
  				geo.x = this.parentX;
  				geo.y = this.parentY;
  				model.setGeometry(parent, geo);
  			}
  		}
  
  		this.graph.removeCells(this.dummyVertices);
  	}
  	finally
  	{
  		model.endUpdate();
  	}
  };
  
  /**
   * Function: updateGroupBounds
   * 
   * Updates the bounds of the given array of groups so that it includes
   * all child vertices.
   * 
   */
  mxSwimlaneLayout.prototype.updateGroupBounds = function()
  {
  	// Get all vertices and edge in the layout
  	var cells = [];
  	var model = this.model;
  	
  	for (var key in model.edgeMapper)
  	{
  		var edge = model.edgeMapper[key];
  		
  		for (var i = 0; i < edge.edges.length; i++)
  		{
  			cells.push(edge.edges[i]);
  		}
  	}
  	
  	var layoutBounds = this.graph.getBoundingBoxFromGeometry(cells, true);
  	var childBounds = [];
  
  	for (var i = 0; i < this.swimlanes.length; i++)
  	{
  		var lane = this.swimlanes[i];
  		var geo = this.graph.getCellGeometry(lane);
  		
  		if (geo != null)
  		{
  			var children = this.graph.getChildCells(lane);
  			
  			var size = (this.graph.isSwimlane(lane)) ?
  					this.graph.getStartSize(lane) : new mxRectangle();
  
  			var bounds = this.graph.getBoundingBoxFromGeometry(children);
  			childBounds[i] = bounds;
  			var childrenY = bounds.y + geo.y - size.height - this.parentBorder;
  			var maxChildrenY = bounds.y + geo.y + bounds.height;
  
  			if (layoutBounds == null)
  			{
  				layoutBounds = new mxRectangle(0, childrenY, 0, maxChildrenY - childrenY);
  			}
  			else
  			{
  				layoutBounds.y = Math.min(layoutBounds.y, childrenY);
  				var maxY = Math.max(layoutBounds.y + layoutBounds.height, maxChildrenY);
  				layoutBounds.height = maxY - layoutBounds.y;
  			}
  		}
  	}
  
  	
  	for (var i = 0; i < this.swimlanes.length; i++)
  	{
  		var lane = this.swimlanes[i];
  		var geo = this.graph.getCellGeometry(lane);
  		
  		if (geo != null)
  		{
  			var children = this.graph.getChildCells(lane);
  			
  			var size = (this.graph.isSwimlane(lane)) ?
  					this.graph.getStartSize(lane) : new mxRectangle();
  
  			var newGeo = geo.clone();
  			
  			var leftGroupBorder = (i == 0) ? this.parentBorder : this.interRankCellSpacing/2;
  			newGeo.x += childBounds[i].x - size.width - leftGroupBorder;
  			newGeo.y = newGeo.y + layoutBounds.y - geo.y - this.parentBorder;
  			
  			newGeo.width = childBounds[i].width + size.width + this.interRankCellSpacing/2 + leftGroupBorder;
  			newGeo.height = layoutBounds.height + size.height + 2 * this.parentBorder;
  			
  			this.graph.model.setGeometry(lane, newGeo);
  			this.graph.moveCells(children, -childBounds[i].x + size.width + leftGroupBorder, 
  					geo.y - layoutBounds.y + this.parentBorder);
  		}
  	}
  };
  
  /**
   * Function: findRoots
   * 
   * Returns all visible children in the given parent which do not have
   * incoming edges. If the result is empty then the children with the
   * maximum difference between incoming and outgoing edges are returned.
   * This takes into account edges that are being promoted to the given
   * root due to invisible children or collapsed cells.
   * 
   * Parameters:
   * 
   * parent - <mxCell> whose children should be checked.
   * vertices - array of vertices to limit search to
   */
  mxSwimlaneLayout.prototype.findRoots = function(parent, vertices)
  {
  	var roots = [];
  	
  	if (parent != null && vertices != null)
  	{
  		var model = this.graph.model;
  		var best = null;
  		var maxDiff = -100000;
  		
  		for (var i in vertices)
  		{
  			var cell = vertices[i];
  
  			if (cell != null && model.isVertex(cell) && this.graph.isCellVisible(cell) && model.isAncestor(parent, cell))
  			{
  				var conns = this.getEdges(cell);
  				var fanOut = 0;
  				var fanIn = 0;
  
  				for (var k = 0; k < conns.length; k++)
  				{
  					var src = this.getVisibleTerminal(conns[k], true);
  
  					if (src == cell)
  					{
  						// Only count connection within this swimlane
  						var other = this.getVisibleTerminal(conns[k], false);
  						
  						if (model.isAncestor(parent, other))
  						{
  							fanOut++;
  						}
  					}
  					else if (model.isAncestor(parent, src))
  					{
  						fanIn++;
  					}
  				}
  
  				if (fanIn == 0 && fanOut > 0)
  				{
  					roots.push(cell);
  				}
  
  				var diff = fanOut - fanIn;
  
  				if (diff > maxDiff)
  				{
  					maxDiff = diff;
  					best = cell;
  				}
  			}
  		}
  		
  		if (roots.length == 0 && best != null)
  		{
  			roots.push(best);
  		}
  	}
  	
  	return roots;
  };
  
  /**
   * Function: getEdges
   * 
   * Returns the connected edges for the given cell.
   * 
   * Parameters:
   * 
   * cell - <mxCell> whose edges should be returned.
   */
  mxSwimlaneLayout.prototype.getEdges = function(cell)
  {
  	var cachedEdges = this.edgesCache.get(cell);
  	
  	if (cachedEdges != null)
  	{
  		return cachedEdges;
  	}
  
  	var model = this.graph.model;
  	var edges = [];
  	var isCollapsed = this.graph.isCellCollapsed(cell);
  	var childCount = model.getChildCount(cell);
  
  	for (var i = 0; i < childCount; i++)
  	{
  		var child = model.getChildAt(cell, i);
  
  		if (this.isPort(child))
  		{
  			edges = edges.concat(model.getEdges(child, true, true));
  		}
  		else if (isCollapsed || !this.graph.isCellVisible(child))
  		{
  			edges = edges.concat(model.getEdges(child, true, true));
  		}
  	}
  
  	edges = edges.concat(model.getEdges(cell, true, true));
  	var result = [];
  	
  	for (var i = 0; i < edges.length; i++)
  	{
  		var source = this.getVisibleTerminal(edges[i], true);
  		var target = this.getVisibleTerminal(edges[i], false);
  		
  		if ((source == target) || ((source != target) && ((target == cell && (this.parent == null || this.graph.isValidAncestor(source, this.parent, this.traverseAncestors))) ||
  			(source == cell && (this.parent == null ||
  					this.graph.isValidAncestor(target, this.parent, this.traverseAncestors))))))
  		{
  			result.push(edges[i]);
  		}
  	}
  
  	this.edgesCache.put(cell, result);
  
  	return result;
  };
  
  /**
   * Function: getVisibleTerminal
   * 
   * Helper function to return visible terminal for edge allowing for ports
   * 
   * Parameters:
   * 
   * edge - <mxCell> whose edges should be returned.
   * source - Boolean that specifies whether the source or target terminal is to be returned
   */
  mxSwimlaneLayout.prototype.getVisibleTerminal = function(edge, source)
  {
  	var terminalCache = this.edgesTargetTermCache;
  	
  	if (source)
  	{
  		terminalCache = this.edgeSourceTermCache;
  	}
  
  	var term = terminalCache.get(edge);
  
  	if (term != null)
  	{
  		return term;
  	}
  
  	var state = this.graph.view.getState(edge);
  	
  	var terminal = (state != null) ? state.getVisibleTerminal(source) : this.graph.view.getVisibleTerminal(edge, source);
  	
  	if (terminal == null)
  	{
  		terminal = (state != null) ? state.getVisibleTerminal(source) : this.graph.view.getVisibleTerminal(edge, source);
  	}
  
  	if (terminal != null)
  	{
  		if (this.isPort(terminal))
  		{
  			terminal = this.graph.model.getParent(terminal);
  		}
  		
  		terminalCache.put(edge, terminal);
  	}
  
  	return terminal;
  };
  
  /**
   * Function: run
   * 
   * The API method used to exercise the layout upon the graph description
   * and produce a separate description of the vertex position and edge
   * routing changes made. It runs each stage of the layout that has been
   * created.
   */
  mxSwimlaneLayout.prototype.run = function(parent)
  {
  	// Separate out unconnected hierarchies
  	var hierarchyVertices = [];
  	var allVertexSet = [];
  
  	if (this.swimlanes != null && this.swimlanes.length > 0 && parent != null)
  	{
  		var filledVertexSet = Object();
  		
  		for (var i = 0; i < this.swimlanes.length; i++)
  		{
  			this.filterDescendants(this.swimlanes[i], filledVertexSet);
  		}
  
  		this.roots = [];
  		var filledVertexSetEmpty = true;
  
  		// Poor man's isSetEmpty
  		for (var key in filledVertexSet)
  		{
  			if (filledVertexSet[key] != null)
  			{
  				filledVertexSetEmpty = false;
  				break;
  			}
  		}
  
  		// Only test for candidates in each swimlane in order
  		var laneCounter = 0;
  
  		while (!filledVertexSetEmpty && laneCounter < this.swimlanes.length)
  		{
  			var candidateRoots = this.findRoots(this.swimlanes[laneCounter], filledVertexSet);
  			
  			if (candidateRoots.length == 0)
  			{
  				laneCounter++;
  				continue;
  			}
  			
  			// If the candidate root is an unconnected group cell, remove it from
  			// the layout. We may need a custom set that holds such groups and forces
  			// them to be processed for resizing and/or moving.
  			for (var i = 0; i < candidateRoots.length; i++)
  			{
  				var vertexSet = Object();
  				hierarchyVertices.push(vertexSet);
  
  				this.traverse(candidateRoots[i], true, null, allVertexSet, vertexSet,
  						hierarchyVertices, filledVertexSet, laneCounter);
  			}
  
  			for (var i = 0; i < candidateRoots.length; i++)
  			{
  				this.roots.push(candidateRoots[i]);
  			}
  			
  			filledVertexSetEmpty = true;
  			
  			// Poor man's isSetEmpty
  			for (var key in filledVertexSet)
  			{
  				if (filledVertexSet[key] != null)
  				{
  					filledVertexSetEmpty = false;
  					break;
  				}
  			}
  		}
  	}
  	else
  	{
  		// Find vertex set as directed traversal from roots
  
  		for (var i = 0; i < this.roots.length; i++)
  		{
  			var vertexSet = Object();
  			hierarchyVertices.push(vertexSet);
  
  			this.traverse(this.roots[i], true, null, allVertexSet, vertexSet,
  					hierarchyVertices, null);
  		}
  	}
  
  	var tmp = [];
  	
  	for (var key in allVertexSet)
  	{
  		tmp.push(allVertexSet[key]);
  	}
  	
  	this.model = new mxSwimlaneModel(this, tmp, this.roots,
  		parent, this.tightenToSource);
  
  	this.cycleStage(parent);
  	this.layeringStage();
  	
  	this.crossingStage(parent);
  	initialX = this.placementStage(0, parent);
  };
  
  /**
   * Function: filterDescendants
   * 
   * Creates an array of descendant cells
   */
  mxSwimlaneLayout.prototype.filterDescendants = function(cell, result)
  {
  	var model = this.graph.model;
  
  	if (model.isVertex(cell) && cell != this.parent && model.getParent(cell) != this.parent && this.graph.isCellVisible(cell))
  	{
  		result[mxObjectIdentity.get(cell)] = cell;
  	}
  
  	if (this.traverseAncestors || cell == this.parent
  			&& this.graph.isCellVisible(cell))
  	{
  		var childCount = model.getChildCount(cell);
  
  		for (var i = 0; i < childCount; i++)
  		{
  			var child = model.getChildAt(cell, i);
  			
  			// Ignore ports in the layout vertex list, they are dealt with
  			// in the traversal mechanisms
  			if (!this.isPort(child))
  			{
  				this.filterDescendants(child, result);
  			}
  		}
  	}
  };
  
  /**
   * Function: isPort
   * 
   * Returns true if the given cell is a "port", that is, when connecting to
   * it, its parent is the connecting vertex in terms of graph traversal
   * 
   * Parameters:
   * 
   * cell - <mxCell> that represents the port.
   */
  mxSwimlaneLayout.prototype.isPort = function(cell)
  {
  	if (cell.geometry.relative)
  	{
  		return true;
  	}
  	
  	return false;
  };
  
  /**
   * Function: getEdgesBetween
   * 
   * Returns the edges between the given source and target. This takes into
   * account collapsed and invisible cells and ports.
   * 
   * Parameters:
   * 
   * source -
   * target -
   * directed -
   */
  mxSwimlaneLayout.prototype.getEdgesBetween = function(source, target, directed)
  {
  	directed = (directed != null) ? directed : false;
  	var edges = this.getEdges(source);
  	var result = [];
  
  	// Checks if the edge is connected to the correct
  	// cell and returns the first match
  	for (var i = 0; i < edges.length; i++)
  	{
  		var src = this.getVisibleTerminal(edges[i], true);
  		var trg = this.getVisibleTerminal(edges[i], false);
  
  		if ((src == source && trg == target) || (!directed && src == target && trg == source))
  		{
  			result.push(edges[i]);
  		}
  	}
  
  	return result;
  };
  
  /**
   * 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.
   * 
   * Parameters:
   * 
   * vertex - <mxCell> that represents the vertex where the traversal starts.
   * directed - boolean indicating if edges should only be traversed
   * from source to target. Default is true.
   * edge - Optional <mxCell> that represents the incoming edge. This is
   * null for the first step of the traversal.
   * allVertices - Array of cell paths for the visited cells.
   * swimlaneIndex - the laid out order index of the swimlane vertex is contained in
   */
  mxSwimlaneLayout.prototype.traverse = function(vertex, directed, edge, allVertices, currentComp,
  											hierarchyVertices, filledVertexSet, swimlaneIndex)
  {
  	if (vertex != null && allVertices != null)
  	{
  		// Has this vertex been seen before in any traversal
  		// And if the filled vertex set is populated, only 
  		// process vertices in that it contains
  		var vertexID = mxObjectIdentity.get(vertex);
  		
  		if ((allVertices[vertexID] == null)
  				&& (filledVertexSet == null ? true : filledVertexSet[vertexID] != null))
  		{
  			if (currentComp[vertexID] == null)
  			{
  				currentComp[vertexID] = vertex;
  			}
  			if (allVertices[vertexID] == null)
  			{
  				allVertices[vertexID] = vertex;
  			}
  
  			if (filledVertexSet !== null)
  			{
  				delete filledVertexSet[vertexID];
  			}
  
  			var edges = this.getEdges(vertex);
  			var model = this.graph.model;
  
  			for (var i = 0; i < edges.length; i++)
  			{
  				var otherVertex = this.getVisibleTerminal(edges[i], true);
  				var isSource = otherVertex == vertex;
  				
  				if (isSource)
  				{
  					otherVertex = this.getVisibleTerminal(edges[i], false);
  				}
  
  				var otherIndex = 0;
  				// Get the swimlane index of the other terminal
  				for (otherIndex = 0; otherIndex < this.swimlanes.length; otherIndex++)
  				{
  					if (model.isAncestor(this.swimlanes[otherIndex], otherVertex))
  					{
  						break;
  					}
  				}
  				
  				if (otherIndex >= this.swimlanes.length)
  				{
  					continue;
  				}
  
  				// Traverse if the other vertex is within the same swimlane as
  				// as the current vertex, or if the swimlane index of the other
  				// vertex is greater than that of this vertex
  				if ((otherIndex > swimlaneIndex) ||
  						((!directed || isSource) && otherIndex == swimlaneIndex))
  				{
  					currentComp = this.traverse(otherVertex, directed, edges[i], allVertices,
  							currentComp, hierarchyVertices,
  							filledVertexSet, otherIndex);
  				}
  			}
  		}
  		else
  		{
  			if (currentComp[vertexID] == null)
  			{
  				// We've seen this vertex before, but not in the current component
  				// This component and the one it's in need to be merged
  				for (var i = 0; i < hierarchyVertices.length; i++)
  				{
  					var comp = hierarchyVertices[i];
  
  					if (comp[vertexID] != null)
  					{
  						for (var key in comp)
  						{
  							currentComp[key] = comp[key];
  						}
  						
  						// Remove the current component from the hierarchy set
  						hierarchyVertices.splice(i, 1);
  						return currentComp;
  					}
  				}
  			}
  		}
  	}
  	
  	return currentComp;
  };
  
  /**
   * Function: cycleStage
   * 
   * Executes the cycle stage using mxMinimumCycleRemover.
   */
  mxSwimlaneLayout.prototype.cycleStage = function(parent)
  {
  	var cycleStage = new mxSwimlaneOrdering(this);
  	cycleStage.execute(parent);
  };
  
  /**
   * Function: layeringStage
   * 
   * Implements first stage of a Sugiyama layout.
   */
  mxSwimlaneLayout.prototype.layeringStage = function()
  {
  	this.model.initialRank();
  	this.model.fixRanks();
  };
  
  /**
   * Function: crossingStage
   * 
   * Executes the crossing stage using mxMedianHybridCrossingReduction.
   */
  mxSwimlaneLayout.prototype.crossingStage = function(parent)
  {
  	var crossingStage = new mxMedianHybridCrossingReduction(this);
  	crossingStage.execute(parent);
  };
  
  /**
   * Function: placementStage
   * 
   * Executes the placement stage using mxCoordinateAssignment.
   */
  mxSwimlaneLayout.prototype.placementStage = function(initialX, parent)
  {
  	var placementStage = new mxCoordinateAssignment(this, this.intraCellSpacing,
  			this.interRankCellSpacing, this.orientation, initialX,
  			this.parallelEdgeSpacing);
  	placementStage.fineTuning = this.fineTuning;
  	placementStage.execute(parent);
  	
  	return placementStage.limitX + this.interHierarchySpacing;
  };
  
  window.mxSwimlaneLayout = mxSwimlaneLayout;
  