com.jgraph.layout.hierarchical.JGraphMedianHybridCrossingReduction Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ingeniasjgraphmod Show documentation
Show all versions of ingeniasjgraphmod Show documentation
A modified version of some JGraph files
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.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import com.jgraph.layout.JGraphFacade;
import com.jgraph.layout.JGraphLayout;
import com.jgraph.layout.JGraphLayoutProgress;
import com.jgraph.layout.hierarchical.model.JGraphAbstractHierarchyCell;
import com.jgraph.layout.hierarchical.model.JGraphHierarchyModel;
import com.jgraph.layout.hierarchical.model.JGraphHierarchyRank;
/**
* Performs a vertex ordering within ranks as described by Gansner et al 1993
*/
public class JGraphMedianHybridCrossingReduction implements
JGraphHierarchicalLayoutStep, JGraphLayout.Stoppable {
/**
* The maximum number of iterations to perform whilst reducing edge
* crossings
*/
protected int maxIterations = 24;
/**
* Stores each rank as a collection of cells in the best order found for
* each layer so far
*/
protected Object[][] nestedBestRanks = null;
/**
* The total number of crossings found in the best configuration so far
*/
protected int currentBestCrossings = 0;
protected int iterationsWithoutImprovement = 0;
protected int maxNoImprovementIterations = 2;
/**
* The layout progress bar
*/
protected JGraphLayoutProgress progress = new JGraphLayoutProgress();
/**
* Performs a vertex ordering within ranks as described by Gansner et al
* 1993
*
* @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) {
if (model == null) {
return null;
}
// Stores initial ordering as being the best one found so far
nestedBestRanks = new Object[model.ranks.size()][];
for (int i = 0; i < nestedBestRanks.length; i++) {
JGraphHierarchyRank rank = (JGraphHierarchyRank) model.ranks
.get(new Integer(i));
nestedBestRanks[i] = rank.toArray();
}
progress.reset(maxIterations);
iterationsWithoutImprovement = 0;
currentBestCrossings = calculateCrossings(model);
for (int i = 0; i < maxIterations && !progress.isStopped()
&& iterationsWithoutImprovement < maxNoImprovementIterations; i++) {
progress.setProgress(i);
weightedMedian(i, model);
transpose(i, model);
int candidateCrossings = calculateCrossings(model);
if (candidateCrossings < currentBestCrossings) {
currentBestCrossings = candidateCrossings;
iterationsWithoutImprovement = 0;
// Store the current rankings as the best ones
for (int j = 0; j < nestedBestRanks.length; j++) {
JGraphHierarchyRank rank = (JGraphHierarchyRank) model.ranks
.get(new Integer(j));
Iterator iter = rank.iterator();
for (int k = 0; k < rank.size(); k++) {
JGraphAbstractHierarchyCell cell = (JGraphAbstractHierarchyCell) iter
.next();
nestedBestRanks[j][cell.getGeneralPurposeVariable(j)] = cell;
}
}
} else {
// Increase count of iterations where we haven't improved the
// layout
iterationsWithoutImprovement++;
// Restore the best values to the cells
for (int j = 0; j < nestedBestRanks.length; j++) {
JGraphHierarchyRank rank = (JGraphHierarchyRank) model.ranks
.get(new Integer(j));
Iterator iter = rank.iterator();
for (int k = 0; k < rank.size(); k++) {
JGraphAbstractHierarchyCell cell = (JGraphAbstractHierarchyCell) iter
.next();
cell.setGeneralPurposeVariable(j, k);
}
}
}
if (currentBestCrossings == 0) {
// Do nothing further
break;
}
}
// Store the best rankings but in the model
Map ranks = new LinkedHashMap(model.maxRank + 1);
final Collection[] rankList = new JGraphHierarchyRank[model.maxRank + 1];
for (int i = 0; i < model.maxRank + 1; i++) {
rankList[i] = new JGraphHierarchyRank();
ranks.put(new Integer(i), rankList[i]);
}
for (int i = 0; i < nestedBestRanks.length; i++) {
for (int j = 0; j < nestedBestRanks[i].length; j++) {
JGraphAbstractHierarchyCell cell = (JGraphAbstractHierarchyCell) nestedBestRanks[i][j];
rankList[i].add(cell);
}
}
model.ranks = ranks;
return model;
}
/**
* Calculates the total number of edge crossing in the current graph
*
* @param model
* the internal model describing the hierarchy
* @return the current number of edge crossings in the hierarchy graph model
* in the current candidate layout
*/
private int calculateCrossings(JGraphHierarchyModel model) {
// The intra-rank order of cells are stored within the temp variables
// on cells
int numRanks = model.ranks.size();
int totalCrossings = 0;
for (int i = 1; i < numRanks; i++) {
totalCrossings += calculateRankCrossing(i, model);
}
return totalCrossings;
}
/**
* Calculates the number of edges crossings between the specified rank and
* the rank below it
*
* @param i
* the topmost rank of the pair ( higher rank value )
* @param model
* the internal hierarchy model of the graph
* @return the number of edges crossings with the rank beneath
*/
protected int calculateRankCrossing(int i, JGraphHierarchyModel model) {
int totalCrossings = 0;
JGraphHierarchyRank rank = (JGraphHierarchyRank) model.ranks
.get(new Integer(i));
JGraphHierarchyRank previousRank = (JGraphHierarchyRank) model.ranks
.get(new Integer(i - 1));
// Create an array of connections between these two levels
int currentRankSize = rank.size();
int previousRankSize = previousRank.size();
int[][] connections = new int[currentRankSize][previousRankSize];
// Iterate over the top rank and fill in the connection information
Iterator iter = rank.iterator();
while (iter.hasNext()) {
JGraphAbstractHierarchyCell cell = (JGraphAbstractHierarchyCell) iter
.next();
int rankPosition = cell.getGeneralPurposeVariable(i);
Collection connectedCells = cell.getPreviousLayerConnectedCells(i);
Iterator iter2 = connectedCells.iterator();
while (iter2.hasNext()) {
JGraphAbstractHierarchyCell connectedCell = (JGraphAbstractHierarchyCell) iter2
.next();
int otherCellRankPosition = connectedCell
.getGeneralPurposeVariable(i - 1);
if (rankPosition >= currentRankSize
|| otherCellRankPosition >= previousRankSize) {
}
connections[rankPosition][otherCellRankPosition] = 201207;
}
}
// Iterate through the connection matrix, crossing edges are
// indicated by other connected edges with a greater rank position
// on one rank and lower position on the other
for (int j = 0; j < currentRankSize; j++) {
for (int k = 0; k < previousRankSize; k++) {
if (connections[j][k] == 201207) {
// Draw a grid of connections, crossings are top right
// and lower left from this crossing pair
for (int j2 = j + 1; j2 < currentRankSize; j2++) {
for (int k2 = 0; k2 < k; k2++) {
if (connections[j2][k2] == 201207) {
totalCrossings++;
}
}
}
for (int j2 = 0; j2 < j; j2++) {
for (int k2 = k + 1; k2 < previousRankSize; k2++) {
if (connections[j2][k2] == 201207) {
totalCrossings++;
}
}
}
}
}
}
return totalCrossings / 2;
}
/**
* Takes each possible adjacent cell pair on each rank and checks if
* swapping them around reduces the number of crossing
*
* @param mainLoopIteration
* the iteration number of the main loop
* @param model
* the internal model describing the hierarchy
*/
private void transpose(int mainLoopIteration, JGraphHierarchyModel model) {
boolean improved = true;
// Track the number of iterations in case of looping
int count = 0;
int maxCount = 10;
while (improved && count++ < maxCount) {
// On certain iterations allow allow swapping of cell pairs with
// equal edge crossings switched or not switched. This help to
// nudge a stuck layout into a lower crossing total.
boolean nudge = false;
if (mainLoopIteration % 2 == 1 && count % 2 == 1) {
nudge = true;
}
improved = false;
for (int i = 0; i < model.ranks.size(); i++) {
JGraphHierarchyRank rank = (JGraphHierarchyRank) model.ranks
.get(new Integer(i));
JGraphAbstractHierarchyCell[] orderedCells = new JGraphAbstractHierarchyCell[rank
.size()];
Iterator iter = rank.iterator();
for (int j = 0; j < orderedCells.length; j++) {
JGraphAbstractHierarchyCell cell = (JGraphAbstractHierarchyCell) iter
.next();
orderedCells[cell.getGeneralPurposeVariable(i)] = cell;
}
List leftCellAboveConnections = null;
List leftCellBelowConnections = null;
List rightCellAboveConnections = null;
List rightCellBelowConnections = null;
int[] leftAbovePositions = null;
int[] leftBelowPositions = null;
int[] rightAbovePositions = null;
int[] rightBelowPositions = null;
JGraphAbstractHierarchyCell leftCell = null;
JGraphAbstractHierarchyCell rightCell = null;
for (int j = 0; j < (rank.size() - 1); j++) {
// For each intra-rank adjacent pair of cells
// see if swapping them around would reduce the
// number of edges crossing they cause in total
// On every cell pair except the first on each rank, we
// can save processing using the previous values for the
// right cell on the new left cell
if (j == 0) {
leftCell = orderedCells[j];
leftCellAboveConnections = leftCell
.getNextLayerConnectedCells(i);
leftCellBelowConnections = leftCell
.getPreviousLayerConnectedCells(i);
leftAbovePositions = new int[leftCellAboveConnections
.size()];
leftBelowPositions = new int[leftCellBelowConnections
.size()];
for (int k = 0; k < leftAbovePositions.length; k++) {
leftAbovePositions[k] = ((JGraphAbstractHierarchyCell) leftCellAboveConnections
.get(k)).getGeneralPurposeVariable(i + 1);
}
for (int k = 0; k < leftBelowPositions.length; k++) {
leftBelowPositions[k] = ((JGraphAbstractHierarchyCell) leftCellBelowConnections
.get(k)).getGeneralPurposeVariable(i - 1);
}
} else {
leftCellAboveConnections = rightCellAboveConnections;
leftCellBelowConnections = rightCellBelowConnections;
leftAbovePositions = rightAbovePositions;
leftBelowPositions = rightBelowPositions;
leftCell = rightCell;
}
rightCell = orderedCells[j + 1];
rightCellAboveConnections = rightCell
.getNextLayerConnectedCells(i);
rightCellBelowConnections = rightCell
.getPreviousLayerConnectedCells(i);
rightAbovePositions = new int[rightCellAboveConnections
.size()];
rightBelowPositions = new int[rightCellBelowConnections
.size()];
for (int k = 0; k < rightAbovePositions.length; k++) {
rightAbovePositions[k] = ((JGraphAbstractHierarchyCell) rightCellAboveConnections
.get(k)).getGeneralPurposeVariable(i + 1);
}
for (int k = 0; k < rightBelowPositions.length; k++) {
rightBelowPositions[k] = ((JGraphAbstractHierarchyCell) rightCellBelowConnections
.get(k)).getGeneralPurposeVariable(i - 1);
}
int totalCurrentCrossings = 0;
int totalSwitchedCrossings = 0;
for (int k = 0; k < leftAbovePositions.length; k++) {
for (int ik = 0; ik < rightAbovePositions.length; ik++) {
if (leftAbovePositions[k] > rightAbovePositions[ik]) {
totalCurrentCrossings++;
}
if (leftAbovePositions[k] < rightAbovePositions[ik]) {
totalSwitchedCrossings++;
}
}
}
for (int k = 0; k < leftBelowPositions.length; k++) {
for (int ik = 0; ik < rightBelowPositions.length; ik++) {
if (leftBelowPositions[k] > rightBelowPositions[ik]) {
totalCurrentCrossings++;
}
if (leftBelowPositions[k] < rightBelowPositions[ik]) {
totalSwitchedCrossings++;
}
}
}
if ((totalSwitchedCrossings < totalCurrentCrossings)
|| (totalSwitchedCrossings == totalCurrentCrossings && nudge)) {
int temp = leftCell.getGeneralPurposeVariable(i);
leftCell.setGeneralPurposeVariable(i, rightCell
.getGeneralPurposeVariable(i));
rightCell.setGeneralPurposeVariable(i, temp);
// With this pair exchanged we have to switch all of
// values for the left cell to the right cell so the
// next iteration for this rank uses it as the left
// cell again
rightCellAboveConnections = leftCellAboveConnections;
rightCellBelowConnections = leftCellBelowConnections;
rightAbovePositions = leftAbovePositions;
rightBelowPositions = leftBelowPositions;
rightCell = leftCell;
if (!nudge) {
// Don't count nudges as improvement or we'll end
// up stuck in two combinations and not finishing
// as early as we should
improved = true;
}
}
}
}
}
}
/**
* Sweeps up or down the layout attempting to minimise the median placement
* of connected cells on adjacent ranks
*
* @param iteration
* the iteration number of the main loop
* @param model
* the internal model describing the hierarchy
*/
private void weightedMedian(int iteration, JGraphHierarchyModel model) {
// Reverse sweep direction each time through this method
boolean downwardSweep = (iteration % 2 == 0);
if (downwardSweep) {
for (int j = model.maxRank - 1; j >= 0; j--) {
medianRank(j, downwardSweep);
}
} else {
for (int j = 1; j < model.maxRank; j++) {
medianRank(j, downwardSweep);
}
}
}
/**
* Attempts to minimise the median placement of connected cells on this rank
* and one of the adjacent ranks
*
* @param rankValue
* the layer number of this rank
* @param downwardSweep
* whether or not this is a downward sweep through the graph
*/
private void medianRank(int rankValue, boolean downwardSweep) {
int numCellsForRank = nestedBestRanks[rankValue].length;
MedianCellSorter[] medianValues = new MedianCellSorter[numCellsForRank];
for (int i = 0; i < numCellsForRank; i++) {
JGraphAbstractHierarchyCell cell = (JGraphAbstractHierarchyCell) nestedBestRanks[rankValue][i];
medianValues[i] = new MedianCellSorter();
medianValues[i].cell = cell;
// Flip whether or not equal medians are flipped on up and down
// sweeps
medianValues[i].nudge = !downwardSweep;
Collection nextLevelConnectedCells;
if (downwardSweep) {
nextLevelConnectedCells = cell
.getNextLayerConnectedCells(rankValue);
} else {
nextLevelConnectedCells = cell
.getPreviousLayerConnectedCells(rankValue);
}
int nextRankValue;
if (downwardSweep) {
nextRankValue = rankValue + 1;
} else {
nextRankValue = rankValue - 1;
}
if (nextLevelConnectedCells != null
&& nextLevelConnectedCells.size() != 0) {
medianValues[i].medianValue = medianValue(
nextLevelConnectedCells, nextRankValue);
} else {
// Nodes with no adjacent vertices are given a median value of
// -1 to indicate to the median function that they should be
// left of their current position if possible.
medianValues[i].medianValue = -1.0; // TODO needs to account for
// both layers
}
}
Arrays.sort(medianValues);
// Set the new position of each node within the rank using
// its temp variable
for (int i = 0; i < numCellsForRank; i++) {
medianValues[i].cell.setGeneralPurposeVariable(rankValue, i);
}
}
/**
* Calculates the median rank order positioning for the specified cell using
* the connected cells on the specified rank
*
* @param connectedCells
* the cells on the specified rank connected to the specified
* cell
* @param rankValue
* the rank that the connected cell lie upon
* @return the median rank ordering value of the connected cells
*/
private double medianValue(Collection connectedCells, int rankValue) {
double[] medianValues = new double[connectedCells.size()];
int arrayCount = 0;
Iterator iter = connectedCells.iterator();
while (iter.hasNext()) {
medianValues[arrayCount++] = ((JGraphAbstractHierarchyCell) iter
.next()).getGeneralPurposeVariable(rankValue);
}
Arrays.sort(medianValues);
if (arrayCount % 2 == 1) {
// For odd numbers of adjacent vertices return the median
return medianValues[arrayCount / 2];
} else if (arrayCount == 2) {
return ((medianValues[0] + medianValues[1]) / 2.0);
} else {
int medianPoint = arrayCount / 2;
double leftMedian = medianValues[medianPoint - 1] - medianValues[0];
double rightMedian = medianValues[arrayCount - 1]
- medianValues[medianPoint];
return (medianValues[medianPoint - 1] * rightMedian + medianValues[medianPoint]
* leftMedian)
/ (leftMedian + rightMedian);
}
}
/**
* A utility class used to track cells whilst sorting occurs on the median
* values. Does not violate (x.compareTo(y)==0) == (x.equals(y))
*/
protected class MedianCellSorter implements Comparable {
/**
* The median value of the cell stored
*/
public double medianValue = 0.0;
/**
* Whether or not to flip equal median values.
*/
public boolean nudge = false;
/**
* The cell whose median value is being calculated
*/
JGraphAbstractHierarchyCell cell = null;
/**
* 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 MedianCellSorter) {
if (medianValue < ((MedianCellSorter) arg0).medianValue) {
return -1;
} else if (medianValue > ((MedianCellSorter) arg0).medianValue) {
return 1;
} else {
if (nudge) {
return -1;
} else {
return 1;
}
}
} else {
return 0;
}
}
}
/**
* @return Returns the progress.
*/
public JGraphLayoutProgress getProgress() {
return progress;
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy