/**
 * Copyright (c) 2006-2018, JGraph Ltd
 * Copyright (c) 2006-2018, Gaudenz Alder
 */
/**
 * Class: mxCompactTreeLayout
 * 
 * Extends <mxGraphLayout> to implement a compact tree (Moen) algorithm. This
 * layout is suitable for graphs that have no cycles (trees). Vertices that are
 * not connected to the tree will be ignored by this layout.
 * 
 * Example:
 * 
 * (code)
 * var layout = new mxCompactTreeLayout(graph);
 * layout.execute(graph.getDefaultParent());
 * (end)
 * 
 * Constructor: mxCompactTreeLayout
 * 
 * Constructs a new compact tree layout for the specified graph
 * and orientation.
 */
function mxCompactTreeLayout(graph, horizontal, invert)
{
	mxGraphLayout.call(this, graph);
	this.horizontal = (horizontal != null) ? horizontal : true;
	this.invert = (invert != null) ? invert : false;
};

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

/**
 * Variable: horizontal
 *
 * Specifies the orientation of the layout. Default is true.
 */
mxCompactTreeLayout.prototype.horizontal = null;	 

/**
 * Variable: invert
 *
 * Specifies if edge directions should be inverted. Default is false.
 */
mxCompactTreeLayout.prototype.invert = null;	 

/**
 * Variable: resizeParent
 * 
 * If the parents should be resized to match the width/height of the
 * children. Default is true.
 */
mxCompactTreeLayout.prototype.resizeParent = true;

/**
 * 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.
 */
mxCompactTreeLayout.prototype.maintainParentLocation = false;

/**
 * Variable: groupPadding
 * 
 * Padding added to resized parents. Default is 10.
 */
mxCompactTreeLayout.prototype.groupPadding = 10;

/**
 * Variable: groupPaddingTop
 * 
 * Top padding added to resized parents. Default is 0.
 */
mxCompactTreeLayout.prototype.groupPaddingTop = 0;

/**
 * Variable: groupPaddingRight
 * 
 * Right padding added to resized parents. Default is 0.
 */
mxCompactTreeLayout.prototype.groupPaddingRight = 0;

/**
 * Variable: groupPaddingBottom
 * 
 * Bottom padding added to resized parents. Default is 0.
 */
mxCompactTreeLayout.prototype.groupPaddingBottom = 0;

/**
 * Variable: groupPaddingLeft
 * 
 * Left padding added to resized parents. Default is 0.
 */
mxCompactTreeLayout.prototype.groupPaddingLeft = 0;

/**
 * Variable: parentsChanged
 *
 * A set of the parents that need updating based on children
 * process as part of the layout.
 */
mxCompactTreeLayout.prototype.parentsChanged = null;

/**
 * Variable: moveTree
 * 
 * Specifies if the tree should be moved to the top, left corner
 * if it is inside a top-level layer. Default is false.
 */
mxCompactTreeLayout.prototype.moveTree = false;

/**
 * Variable: visited
 * 
 * Specifies if the tree should be moved to the top, left corner
 * if it is inside a top-level layer. Default is false.
 */
mxCompactTreeLayout.prototype.visited = null;

/**
 * Variable: levelDistance
 *
 * Holds the levelDistance. Default is 10.
 */
mxCompactTreeLayout.prototype.levelDistance = 10;

/**
 * Variable: nodeDistance
 *
 * Holds the nodeDistance. Default is 20.
 */
mxCompactTreeLayout.prototype.nodeDistance = 20;

/**
 * Variable: resetEdges
 * 
 * Specifies if all edge points of traversed edges should be removed.
 * Default is true.
 */
mxCompactTreeLayout.prototype.resetEdges = true;

/**
 * Variable: prefHozEdgeSep
 * 
 * The preferred horizontal distance between edges exiting a vertex.
 */
mxCompactTreeLayout.prototype.prefHozEdgeSep = 5;

/**
 * Variable: prefVertEdgeOff
 * 
 * The preferred vertical offset between edges exiting a vertex.
 */
mxCompactTreeLayout.prototype.prefVertEdgeOff = 4;

/**
 * Variable: minEdgeJetty
 * 
 * The minimum distance for an edge jetty from a vertex.
 */
mxCompactTreeLayout.prototype.minEdgeJetty = 8;

/**
 * Variable: channelBuffer
 * 
 * The size of the vertical buffer in the center of inter-rank channels
 * where edge control points should not be placed.
 */
mxCompactTreeLayout.prototype.channelBuffer = 4;

/**
 * Variable: edgeRouting
 * 
 * Whether or not to apply the internal tree edge routing.
 */
mxCompactTreeLayout.prototype.edgeRouting = true;

/**
 * Variable: sortEdges
 * 
 * Specifies if edges should be sorted according to the order of their
 * opposite terminal cell in the model.
 */
mxCompactTreeLayout.prototype.sortEdges = false;

/**
 * Variable: alignRanks
 * 
 * Whether or not the tops of cells in each rank should be aligned
 * across the rank
 */
mxCompactTreeLayout.prototype.alignRanks = false;

/**
 * Variable: maxRankHeight
 * 
 * An array of the maximum height of cells (relative to the layout direction)
 * per rank
 */
mxCompactTreeLayout.prototype.maxRankHeight = null;

/**
 * Variable: root
 * 
 * The cell to use as the root of the tree
 */
mxCompactTreeLayout.prototype.root = null;

/**
 * Variable: node
 * 
 * The internal node representation of the root cell. Do not set directly
 * , this value is only exposed to assist with post-processing functionality
 */
mxCompactTreeLayout.prototype.node = null;

/**
 * Function: isVertexIgnored
 * 
 * Returns a boolean indicating if the given <mxCell> should be ignored as a
 * vertex. This returns true if the cell has no connections.
 * 
 * Parameters:
 * 
 * vertex - <mxCell> whose ignored state should be returned.
 */
mxCompactTreeLayout.prototype.isVertexIgnored = function(vertex)
{
	return mxGraphLayout.prototype.isVertexIgnored.apply(this, arguments) ||
		this.graph.getConnections(vertex).length == 0;
};

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

/**
 * Function: execute
 * 
 * Implements <mxGraphLayout.execute>.
 * 
 * If the parent has any connected edges, then it is used as the root of
 * the tree. Else, <mxGraph.findTreeRoots> will be used to find a suitable
 * root node within the set of children of the given parent.
 * 
 * Parameters:
 * 
 * parent - <mxCell> whose children should be laid out.
 * root - Optional <mxCell> that will be used as the root of the tree.
 * Overrides <root> if specified.
 */
mxCompactTreeLayout.prototype.execute = function(parent, root)
{
	this.parent = parent;
	var model = this.graph.getModel();

	if (root == null)
	{
		// Takes the parent as the root if it has outgoing edges
		if (this.graph.getEdges(parent, model.getParent(parent),
			this.invert, !this.invert, false).length > 0)
		{
			this.root = parent;
		}
		
		// Tries to find a suitable root in the parent's
		// children
		else
		{
			var roots = this.graph.findTreeRoots(parent, true, this.invert);
			
			if (roots.length > 0)
			{
				for (var i = 0; i < roots.length; i++)
				{
					if (!this.isVertexIgnored(roots[i]) &&
						this.graph.getEdges(roots[i], null,
							this.invert, !this.invert, false).length > 0)
					{
						this.root = roots[i];
						break;
					}
				}
			}
		}
	}
	else
	{
		this.root = root;
	}
	
	if (this.root != null)
	{
		if (this.resizeParent)
		{
			this.parentsChanged = new Object();
		}
		else
		{
			this.parentsChanged = null;
		}

		//  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;
			}
		}
		
		model.beginUpdate();
		
		try
		{
			this.visited = new Object();
			this.node = this.dfs(this.root, parent);
			
			if (this.alignRanks)
			{
				this.maxRankHeight = [];
				this.findRankHeights(this.node, 0);
				this.setCellHeights(this.node, 0);
			}
			
			if (this.node != null)
			{
				this.layout(this.node);
				var x0 = this.graph.gridSize;
				var y0 = x0;
				
				if (!this.moveTree)
				{
					var g = this.getVertexBounds(this.root);
					
					if (g != null)
					{
						x0 = g.x;
						y0 = g.y;
					}
				}
				
				var bounds = null;
				
				if (this.isHorizontal())
				{
					bounds = this.horizontalLayout(this.node, x0, y0);
				}
				else
				{
					bounds = this.verticalLayout(this.node, null, x0, y0);
				}

				if (bounds != null)
				{
					var dx = 0;
					var dy = 0;

					if (bounds.x < 0)
					{
						dx = Math.abs(x0 - bounds.x);
					}

					if (bounds.y < 0)
					{
						dy = Math.abs(y0 - bounds.y);	
					}

					if (dx != 0 || dy != 0)
					{
						this.moveNode(this.node, dx, dy);
					}
					
					if (this.resizeParent)
					{
						this.adjustParents();
					}

					if (this.edgeRouting)
					{
						// Iterate through all edges setting their positions
						this.localEdgeProcessing(this.node);
					}
				}
				
				// 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);
					}
				}
			}
		}
		finally
		{
			model.endUpdate();
		}
	}
};

/**
 * Function: moveNode
 * 
 * Moves the specified node and all of its children by the given amount.
 */
mxCompactTreeLayout.prototype.moveNode = function(node, dx, dy)
{
	node.x += dx;
	node.y += dy;
	this.apply(node);
	
	var child = node.child;
	
	while (child != null)
	{
		this.moveNode(child, dx, dy);
		child = child.next;
	}
};


/**
 * Function: sortOutgoingEdges
 * 
 * Called if <sortEdges> is true to sort the array of outgoing edges in place.
 */
mxCompactTreeLayout.prototype.sortOutgoingEdges = function(source, edges)
{
	var lookup = new mxDictionary();
	
	edges.sort(function(e1, e2)
	{
		var end1 = e1.getTerminal(e1.getTerminal(false) == source);
		var p1 = lookup.get(end1);
		
		if (p1 == null)
		{
			p1 = mxCellPath.create(end1).split(mxCellPath.PATH_SEPARATOR);
			lookup.put(end1, p1);
		}

		var end2 = e2.getTerminal(e2.getTerminal(false) == source);
		var p2 = lookup.get(end2);
		
		if (p2 == null)
		{
			p2 = mxCellPath.create(end2).split(mxCellPath.PATH_SEPARATOR);
			lookup.put(end2, p2);
		}

		return mxCellPath.compare(p1, p2);
	});
};

/**
 * Function: findRankHeights
 * 
 * Stores the maximum height (relative to the layout
 * direction) of cells in each rank
 */
mxCompactTreeLayout.prototype.findRankHeights = function(node, rank)
{
	if (this.maxRankHeight[rank] == null || this.maxRankHeight[rank] < node.height)
	{
		this.maxRankHeight[rank] = node.height;
	}

	var child = node.child;
	
	while (child != null)
	{
		this.findRankHeights(child, rank + 1);
		child = child.next;
	}
};

/**
 * Function: setCellHeights
 * 
 * Set the cells heights (relative to the layout
 * direction) when the tops of each rank are to be aligned
 */
mxCompactTreeLayout.prototype.setCellHeights = function(node, rank)
{
	if (this.maxRankHeight[rank] != null && this.maxRankHeight[rank] > node.height)
	{
		node.height = this.maxRankHeight[rank];
	}

	var child = node.child;
	
	while (child != null)
	{
		this.setCellHeights(child, rank + 1);
		child = child.next;
	}
};

/**
 * Function: dfs
 * 
 * Does a depth first search starting at the specified cell.
 * Makes sure the specified parent is never left by the
 * algorithm.
 */
mxCompactTreeLayout.prototype.dfs = function(cell, parent)
{
	var id = mxCellPath.create(cell);
	var node = null;
	
	if (cell != null && this.visited[id] == null && !this.isVertexIgnored(cell))
	{
		this.visited[id] = cell;
		node = this.createNode(cell);

		var model = this.graph.getModel();
		var prev = null;
		var out = this.graph.getEdges(cell, parent, this.invert, !this.invert, false, true);
		var view = this.graph.getView();
		
		if (this.sortEdges)
		{
			this.sortOutgoingEdges(cell, out);
		}

		for (var i = 0; i < out.length; i++)
		{
			var edge = out[i];
			
			if (!this.isEdgeIgnored(edge))
			{
				// Resets the points on the traversed edge
				if (this.resetEdges)
				{
					this.setEdgePoints(edge, null);
				}
				
				if (this.edgeRouting)
				{
					this.setEdgeStyleEnabled(edge, false);
					this.setEdgePoints(edge, null);
				}
				
				// Checks if terminal in same swimlane
				var state = view.getState(edge);
				var target = (state != null) ? state.getVisibleTerminal(this.invert) : view.getVisibleTerminal(edge, this.invert);
				var tmp = this.dfs(target, parent);
				
				if (tmp != null && model.getGeometry(target) != null)
				{
					if (prev == null)
					{
						node.child = tmp;
					}
					else
					{
						prev.next = tmp;
					}
					
					prev = tmp;
				}
			}
		}
	}
	
	return node;
};

/**
 * Function: layout
 * 
 * Starts the actual compact tree layout algorithm
 * at the given node.
 */
mxCompactTreeLayout.prototype.layout = function(node)
{
	if (node != null)
	{
		var child = node.child;
		
		while (child != null)
		{
			this.layout(child);
			child = child.next;
		}
		
		if (node.child != null)
		{
			this.attachParent(node, this.join(node));
		}
		else
		{
			this.layoutLeaf(node);
		}
	}
};

/**
 * Function: horizontalLayout
 */
mxCompactTreeLayout.prototype.horizontalLayout = function(node, x0, y0, bounds)
{
	node.x += x0 + node.offsetX;
	node.y += y0 + node.offsetY;
	bounds = this.apply(node, bounds);
	var child = node.child;
	
	if (child != null)
	{
		bounds = this.horizontalLayout(child, node.x, node.y, bounds);
		var siblingOffset = node.y + child.offsetY;
		var s = child.next;
		
		while (s != null)
		{
			bounds = this.horizontalLayout(s, node.x + child.offsetX, siblingOffset, bounds);
			siblingOffset += s.offsetY;
			s = s.next;
		}
	}
	
	return bounds;
};
	
/**
 * Function: verticalLayout
 */
mxCompactTreeLayout.prototype.verticalLayout = function(node, parent, x0, y0, bounds)
{
	node.x += x0 + node.offsetY;
	node.y += y0 + node.offsetX;
	bounds = this.apply(node, bounds);
	var child = node.child;
	
	if (child != null)
	{
		bounds = this.verticalLayout(child, node, node.x, node.y, bounds);
		var siblingOffset = node.x + child.offsetY;
		var s = child.next;
		
		while (s != null)
		{
			bounds = this.verticalLayout(s, node, siblingOffset, node.y + child.offsetX, bounds);
			siblingOffset += s.offsetY;
			s = s.next;
		}
	}
	
	return bounds;
};

/**
 * Function: attachParent
 */
mxCompactTreeLayout.prototype.attachParent = function(node, height)
{
	var x = this.nodeDistance + this.levelDistance;
	var y2 = (height - node.width) / 2 - this.nodeDistance;
	var y1 = y2 + node.width + 2 * this.nodeDistance - height;
	
	node.child.offsetX = x + node.height;
	node.child.offsetY = y1;
	
	node.contour.upperHead = this.createLine(node.height, 0,
		this.createLine(x, y1, node.contour.upperHead));
	node.contour.lowerHead = this.createLine(node.height, 0,
		this.createLine(x, y2, node.contour.lowerHead));
};

/**
 * Function: layoutLeaf
 */
mxCompactTreeLayout.prototype.layoutLeaf = function(node)
{
	var dist = 2 * this.nodeDistance;
	
	node.contour.upperTail = this.createLine(
		node.height + dist, 0);
	node.contour.upperHead = node.contour.upperTail;
	node.contour.lowerTail = this.createLine(
		0, -node.width - dist);
	node.contour.lowerHead = this.createLine(
		node.height + dist, 0, node.contour.lowerTail);
};

/**
 * Function: join
 */
mxCompactTreeLayout.prototype.join = function(node)
{
	var dist = 2 * this.nodeDistance;
	
	var child = node.child;
	node.contour = child.contour;
	var h = child.width + dist;
	var sum = h;
	child = child.next;
	
	while (child != null)
	{
		var d = this.merge(node.contour, child.contour);
		child.offsetY = d + h;
		child.offsetX = 0;
		h = child.width + dist;
		sum += d + h;
		child = child.next;
	}
	
	return sum;
};

/**
 * Function: merge
 */
mxCompactTreeLayout.prototype.merge = function(p1, p2)
{
	var x = 0;
	var y = 0;
	var total = 0;
	
	var upper = p1.lowerHead;
	var lower = p2.upperHead;
	
	while (lower != null && upper != null)
	{
		var d = this.offset(x, y, lower.dx, lower.dy,
			upper.dx, upper.dy);
		y += d;
		total += d;
		
		if (x + lower.dx <= upper.dx)
		{
			x += lower.dx;
			y += lower.dy;
			lower = lower.next;
		}
		else
		{				
			x -= upper.dx;
			y -= upper.dy;
			upper = upper.next;
		}
	}
	
	if (lower != null)
	{
		var b = this.bridge(p1.upperTail, 0, 0, lower, x, y);
		p1.upperTail = (b.next != null) ? p2.upperTail : b;
		p1.lowerTail = p2.lowerTail;
	}
	else
	{
		var b = this.bridge(p2.lowerTail, x, y, upper, 0, 0);
		
		if (b.next == null)
		{
			p1.lowerTail = b;
		}
	}
	
	p1.lowerHead = p2.lowerHead;
	
	return total;
};

/**
 * Function: offset
 */
mxCompactTreeLayout.prototype.offset = function(p1, p2, a1, a2, b1, b2)
{
	var d = 0;
	
	if (b1 <= p1 || p1 + a1 <= 0)
	{
		return 0;
	}

	var t = b1 * a2 - a1 * b2;
	
	if (t > 0)
	{
		if (p1 < 0)
		{
			var s = p1 * a2;
			d = s / a1 - p2;
		}
		else if (p1 > 0)
		{
			var s = p1 * b2;
			d = s / b1 - p2;
		}
		else
		{
			d = -p2;
		}
	}
	else if (b1 < p1 + a1)
	{
		var s = (b1 - p1) * a2;
		d = b2 - (p2 + s / a1);
	}
	else if (b1 > p1 + a1)
	{
		var s = (a1 + p1) * b2;
		d = s / b1 - (p2 + a2);
	}
	else
	{
		d = b2 - (p2 + a2);
	}

	if (d > 0)
	{
		return d;
	}
	else
	{
		return 0;
	}
};

/**
 * Function: bridge
 */
mxCompactTreeLayout.prototype.bridge = function(line1, x1, y1, line2, x2, y2)
{
	var dx = x2 + line2.dx - x1;
	var dy = 0;
	var s = 0;
	
	if (line2.dx == 0)
	{
		dy = line2.dy;
	}
	else
	{
		s = dx * line2.dy;
		dy = s / line2.dx;
	}
	
	var r = this.createLine(dx, dy, line2.next);
	line1.next = this.createLine(0, y2 + line2.dy - dy - y1, r);
	
	return r;
};

/**
 * Function: createNode
 */
mxCompactTreeLayout.prototype.createNode = function(cell)
{
	var node = new Object();
	node.cell = cell;
	node.x = 0;
	node.y = 0;
	node.width = 0;
	node.height = 0;
	
	var geo = this.getVertexBounds(cell);
	
	if (geo != null)
	{
		if (this.isHorizontal())
		{
			node.width = geo.height;
			node.height = geo.width;			
		}
		else
		{
			node.width = geo.width;
			node.height = geo.height;
		}
	}
	
	node.offsetX = 0;
	node.offsetY = 0;
	node.contour = new Object();
	
	return node;
};

/**
 * Function: apply
 */
mxCompactTreeLayout.prototype.apply = function(node, bounds)
{
	var model = this.graph.getModel();
	var cell = node.cell;
	var g = model.getGeometry(cell);

	if (cell != null && g != null)
	{
		if (this.isVertexMovable(cell))
		{
			g = this.setVertexLocation(cell, node.x, node.y);
			
			if (this.resizeParent)
			{
				var parent = model.getParent(cell);
				var id = mxCellPath.create(parent);
				
				// Implements set semantic
				if (this.parentsChanged[id] == null)
				{
					this.parentsChanged[id] = parent;					
				}
			}
		}
		
		if (bounds == null)
		{
			bounds = new mxRectangle(g.x, g.y, g.width, g.height);
		}
		else
		{
			bounds = new mxRectangle(Math.min(bounds.x, g.x),
				Math.min(bounds.y, g.y),
				Math.max(bounds.x + bounds.width, g.x + g.width),
				Math.max(bounds.y + bounds.height, g.y + g.height));
		}
	}
	
	return bounds;
};

/**
 * Function: createLine
 */
mxCompactTreeLayout.prototype.createLine = function(dx, dy, next)
{
	var line = new Object();
	line.dx = dx;
	line.dy = dy;
	line.next = next;
	
	return line;
};

/**
 * Function: adjustParents
 * 
 * Adjust parent cells whose child geometries have changed. The default 
 * implementation adjusts the group to just fit around the children with 
 * a padding.
 */
mxCompactTreeLayout.prototype.adjustParents = function()
{
	var tmp = [];
	
	for (var id in this.parentsChanged)
	{
		tmp.push(this.parentsChanged[id]);
	}
	
	this.arrangeGroups(mxUtils.sortCells(tmp, true), this.groupPadding, this.groupPaddingTop,
		this.groupPaddingRight, this.groupPaddingBottom, this.groupPaddingLeft);
};

/**
 * Function: localEdgeProcessing
 *
 * Moves the specified node and all of its children by the given amount.
 */
mxCompactTreeLayout.prototype.localEdgeProcessing = function(node)
{
	this.processNodeOutgoing(node);
	var child = node.child;

	while (child != null)
	{
		this.localEdgeProcessing(child);
		child = child.next;
	}
};

/**
 * Function: localEdgeProcessing
 *
 * Separates the x position of edges as they connect to vertices
 */
mxCompactTreeLayout.prototype.processNodeOutgoing = function(node)
{
	var child = node.child;
	var parentCell = node.cell;

	var childCount = 0;
	var sortedCells = [];

	while (child != null)
	{
		childCount++;

		var sortingCriterion = child.x;

		if (this.horizontal)
		{
			sortingCriterion = child.y;
		}

		sortedCells.push(new WeightedCellSorter(child, sortingCriterion));
		child = child.next;
	}

	sortedCells.sort(WeightedCellSorter.prototype.compare);

	var availableWidth = node.width;

	var requiredWidth = (childCount + 1) * this.prefHozEdgeSep;

	// Add a buffer on the edges of the vertex if the edge count allows
	if (availableWidth > requiredWidth + (2 * this.prefHozEdgeSep))
	{
		availableWidth -= 2 * this.prefHozEdgeSep;
	}

	var edgeSpacing = availableWidth / childCount;

	var currentXOffset = edgeSpacing / 2.0;

	if (availableWidth > requiredWidth + (2 * this.prefHozEdgeSep))
	{
		currentXOffset += this.prefHozEdgeSep;
	}

	var currentYOffset = this.minEdgeJetty - this.prefVertEdgeOff;
	var maxYOffset = 0;

	var parentBounds = this.getVertexBounds(parentCell);
	child = node.child;

	for (var j = 0; j < sortedCells.length; j++)
	{
		var childCell = sortedCells[j].cell.cell;
		var childBounds = this.getVertexBounds(childCell);

		var edges = this.graph.getEdgesBetween(parentCell,
				childCell, false);
		
		var newPoints = [];
		var x = 0;
		var y = 0;

		for (var i = 0; i < edges.length; i++)
		{
			if (this.horizontal)
			{
				// Use opposite co-ords, calculation was done for 
				// 
				x = parentBounds.x + parentBounds.width;
				y = parentBounds.y + currentXOffset;
				newPoints.push(new mxPoint(x, y));
				x = parentBounds.x + parentBounds.width
						+ currentYOffset;
				newPoints.push(new mxPoint(x, y));
				y = childBounds.y + childBounds.height / 2.0;
				newPoints.push(new mxPoint(x, y));
				this.setEdgePoints(edges[i], newPoints);
			}
			else
			{
				x = parentBounds.x + currentXOffset;
				y = parentBounds.y + parentBounds.height;
				newPoints.push(new mxPoint(x, y));
				y = parentBounds.y + parentBounds.height
						+ currentYOffset;
				newPoints.push(new mxPoint(x, y));
				x = childBounds.x + childBounds.width / 2.0;
				newPoints.push(new mxPoint(x, y));
				this.setEdgePoints(edges[i], newPoints);
			}
		}

		if (j < childCount / 2)
		{
			currentYOffset += this.prefVertEdgeOff;
		}
		else if (j > childCount / 2)
		{
			currentYOffset -= this.prefVertEdgeOff;
		}
		// Ignore the case if equals, this means the second of 2
		// jettys with the same y (even number of edges)

		//								pos[k * 2] = currentX;
		currentXOffset += edgeSpacing;
		//								pos[k * 2 + 1] = currentYOffset;

		maxYOffset = Math.max(maxYOffset, currentYOffset);
	}
};