All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.jgraph.layout.hierarchical.JGraphCoordinateAssignment Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2005, David Benson
 *
 * All rights reserved.
 *
 * This file is licensed under the JGraph software license, a copy of which
 * will have been provided to you in the file LICENSE at the root of your
 * installation directory. If you are unable to locate this file please
 * contact JGraph sales for another copy.
 */

package com.jgraph.layout.hierarchical;

import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.swing.SwingConstants;

import com.jgraph.layout.JGraphFacade;
import com.jgraph.layout.hierarchical.model.JGraphAbstractHierarchyCell;
import com.jgraph.layout.hierarchical.model.JGraphHierarchyEdge;
import com.jgraph.layout.hierarchical.model.JGraphHierarchyModel;
import com.jgraph.layout.hierarchical.model.JGraphHierarchyNode;
import com.jgraph.layout.hierarchical.model.JGraphHierarchyRank;

/**
 * Sets the horizontal locations of node and edge dummy nodes on each layer.
 * Uses median down and up weighings as well heuristic to straighten edges as
 * far as possible.
 */
public class JGraphCoordinateAssignment implements JGraphHierarchicalLayoutStep {

	/**
	 * The minimum buffer between cells on the same rank
	 */
	protected double intraCellSpacing = 30.0;

	/**
	 * The minimum distance between cells on adjacent ranks
	 */
	protected double interRankCellSpacing = 30.0;

	/**
	 * The distance between each parallel edge on each ranks for long edges
	 */
	protected double parallelEdgeSpacing = 10.0;

	/**
	 * The number of heuristic iterations to run
	 */
	protected int maxIterations = 8;

	/**
	 * The position of the root ( start ) node(s) relative to the rest of the
	 * laid out graph
	 */
	protected int orientation = SwingConstants.NORTH;

	/**
	 * The minimum x position node placement starts at
	 */
	protected double initialX;

	/**
	 * The maximum x value this positioning lays up to
	 */
	protected double limitX;

	/**
	 * The sum of x-displacements for the current iteration
	 */
	protected double currentXDelta;

	/**
	 * The rank that has the widest x position
	 */
	protected int widestRank;

	/**
	 * The X-coordinate of the edge of the widest rank
	 */
	protected double widestRankValue;

	/**
	 * The width of all the ranks
	 */
	protected double[] rankWidths;

	/*
	 * The y co-ordinate of all the ranks
	 */
	protected double[] rankY;

	/**
	 * Whether or not to perform local optimisations and iterate multiple times
	 * through the algorithm
	 */
	protected boolean fineTuning = true;

	/**
	 * Whether or not to pull together sections of layout into empty space
	 */
	protected boolean compactLayout = false;

	/**
	 * A store of connections to the layer above for speed
	 */
	protected JGraphAbstractHierarchyCell[][] nextLayerConnectedCache;

	/**
	 * A store of connections to the layer below for speed
	 */
	protected JGraphAbstractHierarchyCell[][] previousLayerConnectedCache;

	/** The logger for this class */
	private static Logger logger = Logger
			.getLogger("com.jgraph.layout.hierarchical.JGraphCoordinateAssignment");

	/**
	 * Creates a JGraphCoordinateAssignment
	 * 
	 * @param intraCellSpacing
	 *            the minimum buffer between cells on the same rank
	 * @param interRankCellSpacing
	 *            the minimum distance between cells on adjacent ranks
	 * @param orientation
	 *            the position of the root node(s) relative to the graph
	 * @param initialX
	 *            the leftmost coordinate node placement starts at
	 */
	public JGraphCoordinateAssignment(double intraCellSpacing,
			double interRankCellSpacing, int orientation,
			boolean compactLayout, double initialX, double parallelEdgeSpacing) {
		this.intraCellSpacing = intraCellSpacing;
		this.interRankCellSpacing = interRankCellSpacing;
		this.orientation = orientation;
		this.compactLayout = compactLayout;
		this.initialX = initialX;
		this.parallelEdgeSpacing = parallelEdgeSpacing;
		setLoggerLevel(Level.OFF);
	}

	/**
	 * A basic horizontal coordinate assignment algorithm
	 * 
	 * @param facade
	 *            the facade describing the input graph
	 * @param model
	 *            an internal model of the hierarchical layout
	 * @return the updated hierarchy model
	 */
	public JGraphHierarchyModel run(JGraphFacade facade,
			JGraphHierarchyModel model) {
		currentXDelta = 0.0;
		initialise(model);
		initialCoords(facade, model);
		if (fineTuning) {
			minNode(model);
		}
		double bestXDelta = 100000000.0;
		if (fineTuning) {
			for (int i = 0; i < maxIterations; i++) {
				// Median Heuristic
				if (i != 0) {
					medianPos(i, model);
					minNode(model);
				}
				// if the total offset is less for the current positioning,
				// there
				// are less heavily angled edges and so the current positioning
				// is used
				if (currentXDelta < bestXDelta) {
					for (int j = 0; j < model.ranks.size(); j++) {
						JGraphHierarchyRank rank = (JGraphHierarchyRank) model.ranks
								.get(new Integer(j));
						Iterator iter = rank.iterator();
						while (iter.hasNext()) {
							JGraphAbstractHierarchyCell cell = (JGraphAbstractHierarchyCell) iter
									.next();
							cell.setX(j, cell.getGeneralPurposeVariable(j));
						}
					}
					bestXDelta = currentXDelta;
				} else {
					// Restore the best positions
					for (int j = 0; j < model.ranks.size(); j++) {
						JGraphHierarchyRank rank = (JGraphHierarchyRank) model.ranks
								.get(new Integer(j));
						Iterator iter = rank.iterator();
						while (iter.hasNext()) {
							JGraphAbstractHierarchyCell cell = (JGraphAbstractHierarchyCell) iter
									.next();
							cell.setGeneralPurposeVariable(j, (int) cell
									.getX(j));
						}
					}
				}
				currentXDelta = 0;
			}
		}
		if (compactLayout) {
			// Not yet working
			// compactLayout(model);
		}
		setCellLocations(facade, model);
		return model;
	}

	/**
	 * Performs one median positioning sweep in both directions
	 * 
	 * @param model
	 *            an internal model of the hierarchical layout
	 */
	private void minNode(JGraphHierarchyModel model) {
		// Queue all nodes
		LinkedList nodeList = new LinkedList();
		// Need to be able to map from cell to cellWrapper
		Map map = new Hashtable();
		Object[][] rank = new Object[model.maxRank + 1][];
		for (int i = 0; i <= model.maxRank; i++) {
			JGraphHierarchyRank rankSet = (JGraphHierarchyRank) model.ranks
					.get(new Integer(i));
			rank[i] = rankSet.toArray();
			for (int j = 0; j < rank[i].length; j++) {
				// Use the weight to store the rank and visited to store whether
				// or not the cell is in the list
				JGraphAbstractHierarchyCell cell = (JGraphAbstractHierarchyCell) rank[i][j];
				WeightedCellSorter cellWrapper = new WeightedCellSorter(cell, i);
				cellWrapper.rankIndex = j;
				cellWrapper.visited = true;
				nodeList.add(cellWrapper);
				map.put(cell, cellWrapper);
			}
		}
		// Set a limit of the maximum number of times we will access the queue
		// in case a loop appears
		int maxTries = nodeList.size() * 10;
		int count = 0;
		// Don't move cell within this value of their median
		int tolerance = 1;
		while (!nodeList.isEmpty() && count <= maxTries) {
			WeightedCellSorter cellWrapper = (WeightedCellSorter) nodeList
					.getFirst();
			JGraphAbstractHierarchyCell cell = cellWrapper.cell;
			int rankValue = cellWrapper.weightedValue;
			int rankIndex = cellWrapper.rankIndex;
			Object[] nextLayerConnectedCells = cell.getNextLayerConnectedCells(
					rankValue).toArray();
			Object[] previousLayerConnectedCells = cell
					.getPreviousLayerConnectedCells(rankValue).toArray();
			int numNextLayerConnected = nextLayerConnectedCells.length;
			int numPreviousLayerConnected = previousLayerConnectedCells.length;

			int medianNextLevel = medianXValue(nextLayerConnectedCells,
					rankValue + 1);
			int medianPreviousLevel = medianXValue(previousLayerConnectedCells,
					rankValue - 1);

			int numConnectedNeighbours = numNextLayerConnected
					+ numPreviousLayerConnected;
			int currentPosition = cell.getGeneralPurposeVariable(rankValue);
			double cellMedian = currentPosition;
			if (numConnectedNeighbours > 0) {
				cellMedian = (medianNextLevel * numNextLayerConnected + medianPreviousLevel
						* numPreviousLayerConnected)
						/ numConnectedNeighbours;
			}

			// Flag storing whether or not position has changed
			boolean positionChanged = false;
			if (cellMedian < currentPosition - tolerance) {
				if (rankIndex == 0) {
					cell.setGeneralPurposeVariable(rankValue, (int) cellMedian);
					positionChanged = true;
				} else {
					JGraphAbstractHierarchyCell leftCell = (JGraphAbstractHierarchyCell) rank[rankValue][rankIndex - 1];
					int leftLimit = leftCell
							.getGeneralPurposeVariable(rankValue);
					leftLimit = leftLimit + (int) leftCell.width / 2
							+ (int) intraCellSpacing + (int) cell.width / 2;
					if (leftLimit < cellMedian) {
						cell.setGeneralPurposeVariable(rankValue,
								(int) cellMedian);
						positionChanged = true;
					} else if (leftLimit < cell
							.getGeneralPurposeVariable(rankValue)
							- tolerance) {
						cell.setGeneralPurposeVariable(rankValue, leftLimit);
						positionChanged = true;
					}
				}
			} else if (cellMedian > currentPosition + tolerance) {
				int rankSize = rank[rankValue].length;
				if (rankIndex == rankSize - 1) {
					cell.setGeneralPurposeVariable(rankValue, (int) cellMedian);
					positionChanged = true;
				} else {
					JGraphAbstractHierarchyCell rightCell = (JGraphAbstractHierarchyCell) rank[rankValue][rankIndex + 1];
					int rightLimit = rightCell
							.getGeneralPurposeVariable(rankValue);
					rightLimit = rightLimit - (int) rightCell.width / 2
							- (int) intraCellSpacing - (int) cell.width / 2;
					if (rightLimit > cellMedian) {
						cell.setGeneralPurposeVariable(rankValue,
								(int) cellMedian);
						positionChanged = true;
					} else if (rightLimit > cell
							.getGeneralPurposeVariable(rankValue)
							+ tolerance) {
						cell.setGeneralPurposeVariable(rankValue, rightLimit);
						positionChanged = true;
					}
				}
			}
			if (positionChanged) {
				// Add connected nodes to map and list
				for (int i = 0; i < nextLayerConnectedCells.length; i++) {
					JGraphAbstractHierarchyCell connectedCell = (JGraphAbstractHierarchyCell) nextLayerConnectedCells[i];
					WeightedCellSorter connectedCellWrapper = (WeightedCellSorter) map
							.get(connectedCell);
					if (connectedCellWrapper != null) {
						if (connectedCellWrapper.visited == false) {
							connectedCellWrapper.visited = true;
							nodeList.add(connectedCellWrapper);
						}
					}
				}
				// Add connected nodes to map and list
				for (int i = 0; i < previousLayerConnectedCells.length; i++) {
					JGraphAbstractHierarchyCell connectedCell = (JGraphAbstractHierarchyCell) previousLayerConnectedCells[i];
					WeightedCellSorter connectedCellWrapper = (WeightedCellSorter) map
							.get(connectedCell);
					if (connectedCellWrapper != null) {
						if (connectedCellWrapper.visited == false) {
							connectedCellWrapper.visited = true;
							nodeList.add(connectedCellWrapper);
						}
					}
				}
			}
			nodeList.removeFirst();
			cellWrapper.visited = false;
			count++;
		}
	}

	/**
	 * Performs one median positioning sweep in one direction
	 * 
	 * @param i
	 *            the iteration of the whole process
	 * @param model
	 *            an internal model of the hierarchical layout
	 */
	private void medianPos(int i, JGraphHierarchyModel model) {
		// Reverse sweep direction each time through this method
		boolean downwardSweep = (i % 2 == 0);
		if (downwardSweep) {
			for (int j = model.maxRank; j > 0; j--) {
				rankMedianPosition(j - 1, model, j);
			}
		} else {
			for (int j = 0; j < model.maxRank - 1; j++) {
				rankMedianPosition(j + 1, model, j);
			}
		}
	}

	/**
	 * Performs median minimisation over one rank.
	 * 
	 * @param rankValue
	 *            the layer number of this rank
	 * @param model
	 *            an internal model of the hierarchical layout
	 * @param nextRankValue
	 *            the layer number whose connected cels are to be laid out
	 *            relative to
	 */
	protected void rankMedianPosition(int rankValue,
			JGraphHierarchyModel model, int nextRankValue) {
		JGraphHierarchyRank rankSet = (JGraphHierarchyRank) model.ranks
				.get(new Integer(rankValue));
		Object[] rank = rankSet.toArray();
		// Form an array of the order in which the cell are to be processed
		// , the order is given by the weighted sum of the in or out edges,
		// depending on whether we're travelling up or down the hierarchy.
		WeightedCellSorter[] weightedValues = new WeightedCellSorter[rank.length];
		Map cellMap = new Hashtable(rank.length);

		for (int i = 0; i < rank.length; i++) {
			JGraphAbstractHierarchyCell currentCell = (JGraphAbstractHierarchyCell) rank[i];
			weightedValues[i] = new WeightedCellSorter();
			weightedValues[i].cell = currentCell;
			weightedValues[i].rankIndex = i;
			cellMap.put(currentCell, weightedValues[i]);
			Collection nextLayerConnectedCells = null;
			if (nextRankValue < rankValue) {
				nextLayerConnectedCells = currentCell
						.getPreviousLayerConnectedCells(rankValue);
			} else {
				nextLayerConnectedCells = currentCell
						.getNextLayerConnectedCells(rankValue);
			}

			// Calcuate the weighing based on this node type and those this
			// node is connected to on the next layer
			weightedValues[i].weightedValue = calculatedWeightedValue(
					currentCell, nextLayerConnectedCells);
		}

		Arrays.sort(weightedValues);
		// Set the new position of each node within the rank using
		// its temp variable
		for (int i = 0; i < weightedValues.length; i++) {
			int numConnectionsNextLevel = 0;
			JGraphAbstractHierarchyCell cell = weightedValues[i].cell;
			Object[] nextLayerConnectedCells = null;
			int medianNextLevel = 0;

			if (nextRankValue < rankValue) {
				nextLayerConnectedCells = cell.getPreviousLayerConnectedCells(
						rankValue).toArray();
			} else {
				nextLayerConnectedCells = cell.getNextLayerConnectedCells(
						rankValue).toArray();
			}

			if (nextLayerConnectedCells != null) {
				numConnectionsNextLevel = nextLayerConnectedCells.length;
				if (numConnectionsNextLevel > 0) {
					medianNextLevel = medianXValue(nextLayerConnectedCells,
							nextRankValue);
				} else {
					// For case of no connections on the next level set the
					// median to be the current position and try to be
					// positioned there
					medianNextLevel = cell.getGeneralPurposeVariable(rankValue);
				}
			}

			double leftBuffer = 0.0;
			double leftLimit = -100000000.0;
			for (int j = weightedValues[i].rankIndex - 1; j >= 0;) {
				WeightedCellSorter weightedValue = (WeightedCellSorter) cellMap
						.get(rank[j]);
				if (weightedValue != null) {
					JGraphAbstractHierarchyCell leftCell = weightedValue.cell;
					if (weightedValue.visited) {
						// The left limit is the right hand limit of that
						// cell
						// plus any allowance for unallocated cells
						// in-between
						leftLimit = leftCell
								.getGeneralPurposeVariable(rankValue)
								+ leftCell.width
								/ 2.0
								+ intraCellSpacing
								+ leftBuffer + cell.width / 2.0;
						;
						j = -1;
					} else {
						leftBuffer += leftCell.width + intraCellSpacing;
						j--;
					}
				}
			}
			double rightBuffer = 0.0;
			double rightLimit = 100000000.0;
			for (int j = weightedValues[i].rankIndex + 1; j < weightedValues.length;) {
				WeightedCellSorter weightedValue = (WeightedCellSorter) cellMap
						.get(rank[j]);
				if (weightedValue != null) {
					JGraphAbstractHierarchyCell rightCell = weightedValue.cell;
					if (weightedValue.visited) {
						// The left limit is the right hand limit of that
						// cell
						// plus any allowance for unallocated cells
						// in-between
						rightLimit = rightCell
								.getGeneralPurposeVariable(rankValue)
								- rightCell.width
								/ 2.0
								- intraCellSpacing
								- rightBuffer - cell.width / 2.0;
						j = weightedValues.length;
					} else {
						rightBuffer += rightCell.width + intraCellSpacing;
						j++;
					}
				}
			}
			if (medianNextLevel >= leftLimit && medianNextLevel <= rightLimit) {
				cell
						.setGeneralPurposeVariable(rankValue,
								(int) medianNextLevel);
			} else if (medianNextLevel < leftLimit) {
				// Couldn't place at median value, place as close to that
				// value as possible
				cell.setGeneralPurposeVariable(rankValue, (int) leftLimit);
				currentXDelta += leftLimit - medianNextLevel;
			} else if (medianNextLevel > rightLimit) {
				// Couldn't place at median value, place as close to that
				// value as possible
				cell.setGeneralPurposeVariable(rankValue, (int) rightLimit);
				currentXDelta += medianNextLevel - rightLimit;
			}

			weightedValues[i].visited = true;
		}
	}

	/**
	 * Calculates the priority the specified cell has based on the type of its
	 * cell and the cells it is connected to on the next layer
	 * 
	 * @param currentCell
	 *            the cell whose weight is to be calculated
	 * @param collection
	 *            the cells the specified cell is connected to
	 * @return the total weighted of the edges between these cells
	 */
	private int calculatedWeightedValue(
			JGraphAbstractHierarchyCell currentCell, Collection collection) {
		int totalWeight = 0;
		Iterator iter = collection.iterator();
		while (iter.hasNext()) {
			JGraphAbstractHierarchyCell cell = (JGraphAbstractHierarchyCell) iter
					.next();
			if (currentCell.isVertex() && cell.isVertex()) {
				totalWeight++;
			} else if (currentCell.isEdge() && cell.isEdge()) {
				totalWeight += 8;
			} else {
				totalWeight += 2;
			}
		}
		return totalWeight;
	}

	/**
	 * Calculates the median position of the connected cell on the specified
	 * rank
	 * 
	 * @param connectedCells
	 *            the cells the candidate connects to on this level
	 * @param rankValue
	 *            the layer number of this rank
	 * @return the median rank order ( not x position ) of the connected cells
	 */
	private int medianXValue(Object[] connectedCells, int rankValue) {
		if (connectedCells.length == 0) {
			return 0;
		}
		int[] medianValues = new int[connectedCells.length];
		for (int i = 0; i < connectedCells.length; i++) {
			medianValues[i] = ((JGraphAbstractHierarchyCell) connectedCells[i])
					.getGeneralPurposeVariable(rankValue);
		}
		Arrays.sort(medianValues);
		if (connectedCells.length % 2 == 1) {
			// For odd numbers of adjacent vertices return the median
			return medianValues[connectedCells.length / 2];
		} else {
			int medianPoint = connectedCells.length / 2;
			int leftMedian = medianValues[medianPoint - 1];
			int rightMedian = medianValues[medianPoint];
			return ((leftMedian + rightMedian) / 2);
		}
	}

	/**
	 * Sets up cached information for speed
	 * 
	 * @param model
	 *            the model to cache
	 */
	private void initialise(JGraphHierarchyModel model) {
		// for (int i = 0; i < model.maxRank; i++) {
		// JGraphHierarchyRank rankSet = (JGraphHierarchyRank) model.ranks
		// .get(new Integer(i));
		// rank[i] = rankSet.toArray();
		// for (int j = 0; j < rank[i].length; j++) {
		// // Use the weight to store the rank and visited to store whether
		// // or not the cell is in the list
		// JGraphAbstractHierarchyCell cell = (JGraphAbstractHierarchyCell)
		// rank[i][j];
		// WeightedCellSorter cellWrapper = new WeightedCellSorter(cell, i);
		// cellWrapper.rankIndex = j;
		// cellWrapper.visited = true;
		// nodeList.add(cellWrapper);
		// map.put(cell, cellWrapper);
		// }
		// }
		//
		// nextLayerConnectedCache
	}

	/**
	 * Sets up the layout in an initial positioning. The ranks are all centered
	 * as much as possible along the middle vertex in each rank. The other cells
	 * are then placed as close as possible on either side.
	 * 
	 * @param facade
	 *            the facade describing the input graph
	 * @param model
	 *            an internal model of the hierarchical layout
	 */
	private void initialCoords(JGraphFacade facade, JGraphHierarchyModel model) {
		calculateWidestRank(facade, model);
		// Sweep up and down from the widest rank
		for (int i = widestRank; i >= 0; i--) {
			if (i < model.maxRank) {
				rankCoordinates(i, facade, model);
			}
		}

		for (int i = widestRank + 1; i <= model.maxRank; i++) {
			if (i > 0) {
				rankCoordinates(i, facade, model);
			}
		}
	}

	/**
	 * Sets up the layout in an initial positioning. All the first cells in each
	 * rank are moved to the left and the rest of the rank inserted as close
	 * together as their size and buffering permits. This method works on just
	 * the specified rank.
	 * 
	 * @param rankValue
	 *            the current rank being processed
	 * @param facade
	 *            the facade describing the input graph
	 * @param model
	 *            an internal model of the hierarchical layout
	 */
	protected void rankCoordinates(int rankValue, JGraphFacade facade,
			JGraphHierarchyModel model) {
		JGraphHierarchyRank rank = (JGraphHierarchyRank) model.ranks
				.get(new Integer(rankValue));
		// Pad out the initial cell spacing to give a better chance of a cell
		// not being restricted in one direction.
		double extraCellSpacing = (widestRankValue - rankWidths[rankValue]) / (rank.size() + 1);
		double localIntraCellSpacing = intraCellSpacing + extraCellSpacing;
		// Check this doesn't make the rank too wide, if it does, reduce it
		if (extraCellSpacing * (rank.size() + 1) + rankWidths[rankValue] > widestRankValue) {
			localIntraCellSpacing = intraCellSpacing;
		}
		double maxY = 0.0;
		double localX = initialX + extraCellSpacing;
		Iterator iter = rank.iterator();
		// Store whether or not any of the cells' bounds were unavailable so
		// to only issue the warning once for all cells
		boolean boundsWarning = false;
		while (iter.hasNext()) {
			JGraphAbstractHierarchyCell cell = (JGraphAbstractHierarchyCell) iter
					.next();
			if (cell.isVertex()) {
				JGraphHierarchyNode node = (JGraphHierarchyNode) cell;
				Rectangle2D bounds = facade.getBounds(node.cell);
				if (bounds != null) {
					if (orientation == SwingConstants.NORTH
							|| orientation == SwingConstants.SOUTH) {
						cell.width = bounds.getWidth();
						cell.height = bounds.getHeight();
					} else {
						cell.width = bounds.getHeight();
						cell.height = bounds.getWidth();
					}
				} else {
					boundsWarning = true;
				}
				maxY = Math.max(maxY, cell.height);
			} else if (cell.isEdge()) {
				JGraphHierarchyEdge edge = (JGraphHierarchyEdge) cell;
				// The width is the number of additional parallel edges
				// time the parallel edge spacing
				int numEdges = 1;
				if (edge.edges != null) {
					numEdges = edge.edges.size();
				} else {
					logger.info("edge.edges is null");
				}
				cell.width = (numEdges - 1) * parallelEdgeSpacing;
			}
			// Set the initial x-value as being the best result so far
			localX += cell.width / 2.0;
			cell.setX(rankValue, localX);
			cell.setGeneralPurposeVariable(rankValue, (int) localX);
			localX += cell.width / 2.0;
			localX += localIntraCellSpacing;
		}
		if (boundsWarning == true) {
			logger.info("At least one cell has no bounds");
		}
	}

	/**
	 * Calculates the width rank in the hierarchy. Also set the y value of each
	 * rank whilst performing the calculation
	 * 
	 * @param facade
	 *            the facade describing the input graph
	 * @param model
	 *            an internal model of the hierarchical layout
	 */
	protected void calculateWidestRank(JGraphFacade facade,
			JGraphHierarchyModel model) {
		// Starting y co-ordinate
		double y = -interRankCellSpacing;
		// Track the widest cell on the last rank since the y
		// difference depends on it
		double lastRankMaxCellHeight = 0.0;
		rankWidths = new double[model.maxRank + 1];
		rankY = new double[model.maxRank + 1];

		for (int rankValue = model.maxRank; rankValue >= 0; rankValue--) {
			// Keep track of the widest cell on this rank
			double maxCellHeight = 0.0;

			JGraphHierarchyRank rank = (JGraphHierarchyRank) model.ranks
					.get(new Integer(rankValue));
			double localX = initialX;
			Iterator iter = rank.iterator();
			// Store whether or not any of the cells' bounds were unavailable so
			// to only issue the warning once for all cells
			boolean boundsWarning = false;
			while (iter.hasNext()) {
				JGraphAbstractHierarchyCell cell = (JGraphAbstractHierarchyCell) iter
						.next();
				if (cell.isVertex()) {
					JGraphHierarchyNode node = (JGraphHierarchyNode) cell;
					Rectangle2D bounds = facade.getBounds(node.cell);
					if (bounds != null) {
						if (orientation == SwingConstants.NORTH
								|| orientation == SwingConstants.SOUTH) {
							cell.width = bounds.getWidth();
							cell.height = bounds.getHeight();
						} else {
							cell.width = bounds.getHeight();
							cell.height = bounds.getWidth();
						}
					} else {
						boundsWarning = true;
					}
					maxCellHeight = Math.max(maxCellHeight, cell.height);
				} else if (cell.isEdge()) {
					JGraphHierarchyEdge edge = (JGraphHierarchyEdge) cell;
					// The width is the number of additional parallel edges
					// time the parallel edge spacing
					int numEdges = 1;
					if (edge.edges != null) {
						numEdges = edge.edges.size();
					} else {
						logger.info("edge.edges is null");
					}
					cell.width = (numEdges - 1) * parallelEdgeSpacing;
				}
				// Set the initial x-value as being the best result so far
				localX += cell.width / 2.0;
				cell.setX(rankValue, localX);
				cell.setGeneralPurposeVariable(rankValue, (int) localX);
				localX += cell.width / 2.0;
				localX += intraCellSpacing;
				if (localX > widestRankValue) {
					widestRankValue = localX;
					widestRank = rankValue;
				}
				rankWidths[rankValue] = localX;
			}
			if (boundsWarning == true) {
				logger.info("At least one cell has no bounds");
			}
			rankY[rankValue] = y;
			double distanceToNextRank = maxCellHeight / 2.0
					+ lastRankMaxCellHeight / 2.0 + interRankCellSpacing;
			lastRankMaxCellHeight = maxCellHeight;
			if (orientation == SwingConstants.NORTH
					|| orientation == SwingConstants.WEST) {
				y += distanceToNextRank;
			} else {
				y -= distanceToNextRank;
			}
			iter = rank.iterator();
			while (iter.hasNext()) {
				JGraphAbstractHierarchyCell cell = (JGraphAbstractHierarchyCell) iter
						.next();
				cell.setY(rankValue, y);
			}
		}
	}

	/**
	 * Removes empty space between parts of the layout
	 * 
	 * @param model
	 */
	// private void compactLayout(JGraphHierarchyModel model) {
	// // List of separate areas in layout
	// Set areas = new HashSet();
	// for (int i = 0; i < model.ranks.size(); i++) {
	// JGraphHierarchyRank rank = (JGraphHierarchyRank) model.ranks
	// .get(new Integer(i));
	// Iterator iter = rank.iterator();
	// while (iter.hasNext()) {
	// JGraphAbstractHierarchyCell cell = (JGraphAbstractHierarchyCell) iter
	// .next();
	// double positionX = 0;
	// if (cell.isVertex()) {
	// JGraphHierarchyNode node = (JGraphHierarchyNode) cell;
	// positionX = node.x[0] - node.width / 2;
	// Rectangle2D area = new Rectangle2D.Double(positionX-intraCellSpacing, 0,
	// node.width+(intraCellSpacing*2), 100000);
	// integrateNewArea(area, areas, cell);
	// } else if (cell.isEdge()) {
	// JGraphHierarchyEdge edge = (JGraphHierarchyEdge) cell;
	// // For parallel edges we need to seperate out the points a
	// // little
	// int numParallelEdges = edge.edges.size();
	// int parallelEdgeBuffer = (int)parallelEdgeSpacing * numParallelEdges/2;
	// for (int j = edge.x.length - 1; j >= 0; j--) {
	// positionX = edge.x[j];
	// if (orientation == SwingConstants.EAST
	// || orientation == SwingConstants.WEST) {
	// positionX = edge.y[j];
	// }
	// Rectangle2D area = new Rectangle2D.Double(positionX-intraCellSpacing, 0,
	// parallelEdgeBuffer+(intraCellSpacing*2), 100000);
	// integrateNewArea(area, areas, cell);
	// }
	// }
	// }
	// }
	// // If there is more than one area need to compact sections
	// if (areas.size() > 1) {
	// Iterator iter = areas.iterator();
	// while (iter.hasNext()) {
	// Rectangle2D area = (Rectangle2D)iter.next();
	// }
	// }
	// }
	/**
	 * Adds a new rectangle to any intersecting rectangles stored in areas. If
	 * no intersection a new area is created from its values.
	 * 
	 * @param area
	 * @param areas
	 */
	// private void integrateNewArea(Rectangle2D area, Set areas,
	// JGraphAbstractHierarchyCell cell) {
	// Iterator iter = areas.iterator();
	// // Whether or not a cached area is found that contains area
	// boolean areaFound = false;
	// while (iter.hasNext()) {
	// AreaSpatialCache cachedArea = (AreaSpatialCache)iter.next();
	// if (cachedArea.intersects(area)) {
	// cachedArea.setRect(cachedArea.createUnion(area));
	// if (areaFound == false) {
	// cachedArea.cells.add(cell);
	// areaFound = true;
	// }
	// }
	// }
	// if (areaFound == false) {
	// // Create new area to hold cell's area
	// AreaSpatialCache newArea = new AreaSpatialCache();
	// newArea.setRect(area.getX(), 0, area.getWidth(), 100000);
	// areas.add(newArea);
	// newArea.cells.add(cell);
	//			
	// }
	// // Check if any of the cached areas now overlap, if they do, merge them
	// Set removedAreas = new HashSet();
	// while (iter.hasNext()) {
	// AreaSpatialCache cachedArea = (AreaSpatialCache)iter.next();
	// // Skip area if already flagged for removal
	// if (!removedAreas.contains(cachedArea)) {
	// Iterator iter2 = areas.iterator();
	// while (iter2.hasNext()) {
	// AreaSpatialCache cachedArea2 = (AreaSpatialCache) iter2.next();
	// if (cachedArea.intersects(cachedArea2)) {
	// cachedArea.setRect(cachedArea.createUnion(cachedArea2));
	// removedAreas.add(cachedArea2);
	// }
	// }
	// }
	// }
	// areas.removeAll(removedAreas);
	// }
	/**
	 * Sets the cell locations in the facade to those stored after this layout
	 * processing step has completed.
	 * 
	 * @param facade
	 *            the facade describing the input graph
	 * @param model
	 *            an internal model of the hierarchical layout
	 */
	private void setCellLocations(JGraphFacade facade,
			JGraphHierarchyModel model) {
		// Stores any translation needs to separate this context properly
		// from any last context
		double contextTranslation = 0.0;
		// Store all the vertices in case we need to translate them
		List vertices = new ArrayList();
		// Run through the ranks twice, once for vertices, then for edges
		// The reason for this is if the vertices need to offset again from
		// the last context
		for (int cellType = 0; cellType < 2; cellType++) {
			for (int i = 0; i < model.ranks.size(); i++) {
				JGraphHierarchyRank rank = (JGraphHierarchyRank) model.ranks
						.get(new Integer(i));
				Iterator iter = rank.iterator();
				while (iter.hasNext()) {
					JGraphAbstractHierarchyCell cell = (JGraphAbstractHierarchyCell) iter
							.next();
					if (cellType == 0 && cell.isVertex()) {
						JGraphHierarchyNode node = (JGraphHierarchyNode) cell;
						Object realCell = node.cell;
						vertices.add(realCell);
						double positionX = node.x[0] - node.width / 2;
						double positionY = node.y[0] - node.height / 2;
						if (orientation == SwingConstants.NORTH
								|| orientation == SwingConstants.SOUTH) {
							facade.setLocation(realCell, positionX, positionY);
						} else {
							facade.setLocation(realCell, positionY, positionX);
						}
						// Stores the positive X limit of this graph context to
						// know where the next context can start
						limitX = Math.max(limitX, positionX + node.width);
						// It is possible that a rank sticks out further in the
						// -ve x direction than the widest rank. In this case
						// store a shift that is required to apply to all cells
						// after positioning is complete.
						if (positionX + 1 < initialX) {
							contextTranslation = initialX - positionX;
						}
					} else if (cellType == 1 && cell.isEdge()) {
						JGraphHierarchyEdge edge = (JGraphHierarchyEdge) cell;
						// For parallel edges we need to seperate out the points
						// a
						// little
						Iterator parallelEdges = edge.edges.iterator();
						double offsetX = 0.0;
						// Only set the edge control points once
						if (edge.temp[0] != 101207) {
							while (parallelEdges.hasNext()) {
								Object realEdge = parallelEdges.next();
								List oldPoints = facade.getPoints(realEdge);
								List newPoints = new ArrayList(
										(edge.x.length) + 2);
								newPoints.add(oldPoints.get(0));
								if (edge.isReversed()) {
									// Reversed edges need the points inserted
									// in
									// reverse order
									for (int j = 0; j < edge.x.length; j++) {
										double positionX = edge.x[j] + offsetX
												+ contextTranslation;
										if (orientation == SwingConstants.NORTH
												|| orientation == SwingConstants.SOUTH) {
											newPoints.add(new Point2D.Double(
													positionX, edge.y[j]));
										} else {
											newPoints.add(new Point2D.Double(
													edge.y[j], positionX));
										}
										limitX = Math.max(limitX, positionX);
									}
									processReversedEdge(edge, realEdge);
								} else {
									for (int j = edge.x.length - 1; j >= 0; j--) {
										double positionX = edge.x[j] + offsetX
												+ contextTranslation;
										if (orientation == SwingConstants.NORTH
												|| orientation == SwingConstants.SOUTH) {
											newPoints.add(new Point2D.Double(
													positionX, edge.y[j]));
										} else {
											newPoints.add(new Point2D.Double(
													edge.y[j], positionX));
										}
										limitX = Math.max(limitX, positionX);
									}
								}
								newPoints.add(oldPoints
										.get(oldPoints.size() - 1));
								facade.setPoints(realEdge, newPoints);
								facade.disableRouting(realEdge);
								// Increase offset so next edge is drawn next to
								// this one
								if (offsetX == 0.0) {
									offsetX = parallelEdgeSpacing;
								} else if (offsetX > 0) {
									offsetX = -offsetX;
								} else {
									offsetX = -offsetX + parallelEdgeSpacing;
								}
							}
							edge.temp[0] = 101207;
						}
					}
				}
			}
		}
		// Move the context by the amount it's overlapping the last context
		if (contextTranslation >= 1.0) {
			if (orientation == SwingConstants.NORTH || orientation == SwingConstants.SOUTH) {
				facade.translateCells(vertices, contextTranslation, 0);
			} else if (orientation == SwingConstants.EAST || orientation == SwingConstants.WEST) {
				facade.translateCells(vertices, 0, contextTranslation);
			}
		}
		// Increase the limit of this context accordingly
		limitX += contextTranslation;
	}

	/**
	 * Hook to add additional processing
	 * 
	 * @param edge
	 *            The hierarchical model edge
	 * @param realEdge
	 *            The real edge in the graph
	 */
	private void processReversedEdge(JGraphHierarchyEdge edge, Object realEdge) {
		// Added as hook for customer
	}

	/**
	 * 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))
	 */
	protected class WeightedCellSorter implements Comparable {

		/**
		 * The weighted value of the cell stored
		 */
		public int weightedValue = 0;

		/**
		 * Whether or not to flip equal weight values.
		 */
		public boolean nudge = false;

		/**
		 * Whether or not this cell has been visited in the current assignment
		 */
		public boolean visited = false;

		/**
		 * The index this cell is in the model rank
		 */
		public int rankIndex;

		/**
		 * The cell whose median value is being calculated
		 */
		public JGraphAbstractHierarchyCell cell = null;

		public WeightedCellSorter() {
			this(null, 0);
		}

		public WeightedCellSorter(JGraphAbstractHierarchyCell cell,
				int weightedValue) {
			this.cell = cell;
			this.weightedValue = weightedValue;
		}

		/**
		 * comparator on the medianValue
		 * 
		 * @param arg0
		 *            the object to be compared to
		 * @return the standard return you would expect when comparing two
		 *         double
		 */
		public int compareTo(Object arg0) {
			if (arg0 instanceof WeightedCellSorter) {
				if (weightedValue > ((WeightedCellSorter) arg0).weightedValue) {
					return -1;
				} else if (weightedValue < ((WeightedCellSorter) arg0).weightedValue) {
					return 1;
				} else {
					if (nudge) {
						return -1;
					} else {
						return 1;
					}
				}
			} else {
				return 0;
			}
		}

	}

	/**
	 * Utility class that stores a collection of vertices and edge points within
	 * a certain area. This area includes the buffer lengths of cells.
	 */
	protected class AreaSpatialCache extends Rectangle2D.Double {
		public Set cells = new HashSet();
	}

	/**
	 * @return Returns the interRankCellSpacing.
	 */
	public double getInterRankCellSpacing() {
		return interRankCellSpacing;
	}

	/**
	 * @param interRankCellSpacing
	 *            The interRankCellSpacing to set.
	 */
	public void setInterRankCellSpacing(double interRankCellSpacing) {
		this.interRankCellSpacing = interRankCellSpacing;
	}

	/**
	 * @return Returns the intraCellSpacing.
	 */
	public double getIntraCellSpacing() {
		return intraCellSpacing;
	}

	/**
	 * @param intraCellSpacing
	 *            The intraCellSpacing to set.
	 */
	public void setIntraCellSpacing(double intraCellSpacing) {
		this.intraCellSpacing = intraCellSpacing;
	}

	/**
	 * @return Returns the orientation.
	 */
	public int getOrientation() {
		return orientation;
	}

	/**
	 * @param orientation
	 *            The orientation to set.
	 */
	public void setOrientation(int orientation) {
		this.orientation = orientation;
	}

	/**
	 * @return Returns the limitX.
	 */
	public double getLimitX() {
		return limitX;
	}

	/**
	 * @param limitX
	 *            The limitX to set.
	 */
	public void setLimitX(double limitX) {
		this.limitX = limitX;
	}

	/**
	 * @return Returns the fineTuning.
	 */
	public boolean isFineTuning() {
		return fineTuning;
	}

	/**
	 * @param fineTuning
	 *            The fineTuning to set.
	 */
	public void setFineTuning(boolean fineTuning) {
		this.fineTuning = fineTuning;
	}

	/**
	 * @return Returns the compactLayout.
	 */
	public boolean isCompactLayout() {
		return compactLayout;
	}

	/**
	 * @param compactLayout
	 *            The compactLayout to set.
	 */
	public void setCompactLayout(boolean compactLayout) {
		this.compactLayout = compactLayout;
	}

	/**
	 * Sets the logging level of this class
	 * 
	 * @param level
	 *            the logging level to set
	 */
	public void setLoggerLevel(Level level) {
		try {
			logger.setLevel(level);
		} catch (SecurityException e) {
			// Probably running in an applet
		}
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy