/*
* @(#)SugiyamaLayoutAlgorithm.java 1.0 18-MAY-2004
*
* Copyright (c) 2004, Sven Luzar
* Copyright (c) 2004, Nicholas Sushkin
* Copyright (c) 2004-2005, Gaudenz Alder
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* - Neither the name of JGraph nor the names of its contributors may be used
* to endorse or promote products derived from this software without specific
* prior written permission.
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
package org.jgraph.layout;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Vector;
import org.jgraph.JGraph;
import org.jgraph.graph.CellView;
import org.jgraph.graph.GraphConstants;
import org.jgraph.graph.GraphLayoutCache;
import org.jgraph.graph.GraphModel;
import org.jgraph.graph.VertexView;
/**
* Arranges the nodes with the Sugiyama Layout Algorithm.
*
*
* Link to the algorithm
*
*
*
* @author Sven Luzar
* modified by Gaudenz Alder and Nicholas Sushkin
*/
public class SugiyamaLayoutAlgorithm extends JGraphLayoutAlgorithm {
/** Const to add Attributes at the Nodes
*
*/
public static final String SUGIYAMA_VISITED = "SugiyamaVisited" /*#Frozen*/;
/** Const to add the Cell Wrapper to the Nodes
*/
public static final String SUGIYAMA_CELL_WRAPPER =
"SugiyamaCellWrapper" /*#Frozen*/;
/**
* Const to add Attributes at the Nodes indicating that the cell was explicitly specified to the layout.
*
* @see #run(org.jgraph.JGraph,Object[],Object[])
*/
public static final String SUGIYAMA_SELECTED = "SugiyamaSelected" /*#Frozen*/;
/** represents the size of the grid in horizontal grid elements
*
*/
protected int gridAreaSize = Integer.MIN_VALUE;
/** A list with Integer Objects. The list contains the
* history of movements per loop
* It was needed for the progress dialog
*/
List movements = null;
/** Represents the movements in the current loop.
* It was needed for the progress dialog
*/
int movementsCurrentLoop = -1;
/** Represents the maximum of movements in the current loop.
* It was needed for the progress dialog
*/
int movementsMax = Integer.MIN_VALUE;
/** Represents the current loop number
* It was needed for the progress dialog
*/
int iteration = 0;
/**
* The default layout direction is vertical (top-down)
*/
protected boolean vertical = true;
/**
* The default grid spacing is (250, 150).
*/
protected Point spacing = new Point(250, 150);
/**
* Controls whether the graph should be placed as close to the origin as possible.
*/
protected boolean flushToOrigin = false;
/**
* Returns an new instance of SugiyamaLayoutSettings
*/
public JGraphLayoutSettings createSettings() {
return new SugiyamaLayoutSettings(this);
}
/**
* Returns the name of this algorithm in human
* readable form.
*/
public String toString() {
return "Sugiyama";
}
/**
* Get a human readable hint for using this layout.
*/
public String getHint() {
return "Ignores selection";
}
/**
* Implementation.
*
* First of all the Algorithm searches the roots from the
* Graph. Starting from this roots the Algorithm creates
* levels and stores them in the member levels.
* The Member levels contains Vector Objects and the Vector per level
* contains Cell Wrapper Objects. After that the Algorithm
* tries to solve the edge crosses from level to level and
* goes top down and bottom up. After minimization of the
* edge crosses the algorithm moves each node to its
* bary center. Last but not Least the method draws the Graph.
*
* @see LayoutAlgorithm
*
* @param graph JGraph instance
* @param dynamic_cells List of all nodes the layout should move
* @param static_cells List of node the layout should not move but allow for
*/
public void run(JGraph graph, Object[] dynamic_cells, Object[] static_cells) {
CellView[] selectedCellViews =
graph.getGraphLayoutCache().getMapping(dynamic_cells);
// gridAreaSize should really belong to the run state.
gridAreaSize = Integer.MIN_VALUE;
/* The Algorithm distributes the nodes on a grid.
* For this grid you can configure the horizontal spacing.
* This field specifies the configured value
*
*/
Rectangle2D maxBounds = new Rectangle2D.Double();
for (int i = 0; i < selectedCellViews.length; i++) {
// Add vertex to list
if (selectedCellViews[i] instanceof VertexView) {
// Fetch Bounds
Rectangle2D bounds = selectedCellViews[i].getBounds();
// Update Maximum
if (bounds != null)
maxBounds.setFrame(0, 0,
Math.max(bounds.getWidth(), maxBounds.getWidth()),
Math.max(bounds.getHeight(), maxBounds.getHeight()));
}
}
if (spacing.x == 0)
spacing.x = (int) (2*maxBounds.getWidth());
if (spacing.y == 0)
spacing.y = (int) (2*maxBounds.getHeight()); // (jgraph.getGridSize()*6);
// mark selected cell views in the graph
markSelected(selectedCellViews, true);
// search all roots
List roots = searchRoots(graph, selectedCellViews);
// return if no root found
if (roots.size() == 0)
return;
// create levels
List levels = fillLevels(graph, selectedCellViews, roots);
// solves the edge crosses
solveEdgeCrosses(graph, levels);
// move all nodes into the barycenter
moveToBarycenter(graph, selectedCellViews, levels);
Point min = flushToOrigin ? new Point(0, 0) : findMinimumAndSpacing(selectedCellViews, spacing);
// draw the graph in the window
drawGraph(graph, levels, min, spacing);
// remove marks from the selected cell views in the graph
markSelected(selectedCellViews, false);
}
/**
* Adds an attribute {@link #SUGIYAMA_SELECTED SUGIYAMA_SELECTED} to the specified selected cell views.
*
* @param selectedCellViews the specified cell views
* @param addMark true to add the mark, false to remove the mark
*/
protected void markSelected(CellView[] selectedCellViews, boolean addMark)
{
if (addMark)
{
for (int i = 0; i < selectedCellViews.length; i++) {
if (selectedCellViews[i] != null) {
selectedCellViews[i].getAttributes().put(SUGIYAMA_SELECTED, Boolean.TRUE);
}
}
}
else
{
for (int i = 0; i < selectedCellViews.length; i++) {
if (selectedCellViews[i] != null) {
selectedCellViews[i].getAttributes().remove(SUGIYAMA_SELECTED);
}
}
}
}
/**
* Detects whether the specified cell has been marked selected.
*
* @see #markSelected(CellView[], boolean)
*
* @param cell the cell to inspect
* @return true if the view has been marked selected and false otherwise.
*/
protected boolean isSelected(final GraphLayoutCache cache, final Object cell)
{
final CellView view = cache.getMapping(cell, false);
return view != null && view.getAttributes().get(SUGIYAMA_SELECTED) != null;
}
/** Searches all Roots for the current Graph
* First the method marks any Node as not visited.
* Than calls searchRoots(MyGraphCell) for each
* not visited Cell.
* The Roots are stored in the Vector named roots
*
* @return returns a Vector with the roots
* @see #searchRoots(JGraph, CellView[])
*/
protected List searchRoots(JGraph jgraph, CellView[] selectedCellViews) {
// get all cells and relations
List vertexViews = new ArrayList(selectedCellViews.length);
List roots = new ArrayList();
// first: mark all as not visited
// O(allCells&Edges)
for (int i = 0; i < selectedCellViews.length; i++) {
if (selectedCellViews[i] instanceof VertexView) {
VertexView vertexView = (VertexView) selectedCellViews[i];
vertexView.getAttributes().remove(SUGIYAMA_VISITED);
vertexViews.add(selectedCellViews[i]);
}
}
// O(graphCells)
for (int i = 0; i < vertexViews.size(); i++) {
VertexView vertexView = (VertexView) vertexViews.get(i);
if (vertexView.getAttributes().get(SUGIYAMA_VISITED) == null) {
searchRoots(jgraph, vertexView, roots);
}
}
// Error Msg if the graph has no roots
if (roots.size() == 0) {
throw new IllegalArgumentException("The Graph is not a DAG. Can't use Sugiyama Algorithm!");
}
return roots;
}
/** Searches Roots for the current Cell.
*
* Therefore he looks at all Ports from the Cell.
* At the Ports he looks for Edges.
* At the Edges he looks for the Target.
* If the Ports of the current Cell contains the target ReViewNodePort
* he follows the edge to the source and looks at the
* Cell for this source.
*
* @param graphCell The current cell
*/
protected void searchRoots(
JGraph jgraph,
VertexView vertexViewToInspect,
List roots) {
// the node already visited
if (vertexViewToInspect.getAttributes().get(SUGIYAMA_VISITED)
!= null) {
return;
}
// mark as visited for cycle tests
vertexViewToInspect.getAttributes().put(SUGIYAMA_VISITED, Boolean.TRUE);
GraphModel model = jgraph.getModel();
GraphLayoutCache cache = jgraph.getGraphLayoutCache();
// get all Ports and search the relations at the ports
//List vertexPortViewList = new ArrayList() ;
Object vertex = vertexViewToInspect.getCell();
int portCount = model.getChildCount(vertex);
for (int j = 0; j < portCount; j++) {
Object port = model.getChild(vertex, j);
// Test all relations for where
// the current node is a target node
// for roots
boolean isRoot = true;
Iterator itrEdges = model.edges(port);
while (itrEdges.hasNext()) {
Object edge = itrEdges.next();
// if not selected do not follow
if (!isSelected(cache, edge)) { continue; }
// if the current node is a target node
// get the source node and test
// the source node for roots
if (model.getTarget(edge) == port) {
Object sourcePort = model.getSource(edge);
Object sourceVertex = model.getParent(sourcePort);
CellView sourceVertexView =
jgraph.getGraphLayoutCache().getMapping(
sourceVertex,
false);
if (sourceVertexView instanceof VertexView) {
searchRoots(
jgraph,
(VertexView) sourceVertexView,
roots);
isRoot = false;
}
}
}
// The current node is never a Target Node
// -> The current node is a root node
if (isRoot) {
roots.add(vertexViewToInspect);
}
}
}
/** Method fills the levels and stores them in the member levels.
* Each level was represended by a Vector with Cell Wrapper objects.
* These Vectors are the elements in the levels Vector.
*
*/
protected List fillLevels(
JGraph jgraph,
CellView[] selectedCellViews,
List rootVertexViews) {
List levels = new Vector();
// mark as not visited
// O(allCells)
for (int i = 0; i < selectedCellViews.length; i++) {
CellView cellView = selectedCellViews[i];
// more stabile
if (cellView == null)
continue;
cellView.getAttributes().remove(SUGIYAMA_VISITED);
}
Iterator rootIter = rootVertexViews.iterator();
while (rootIter.hasNext()) {
VertexView vertexView = (VertexView) rootIter.next();
fillLevels(jgraph, levels, 0, vertexView);
}
return levels;
}
/** Fills the Vector for the specified level with a wrapper
* for the MyGraphCell. After that the method called for
* each neighbor graph cell.
*
* @param level The level for the graphCell
* @param graphCell The Graph Cell
*/
protected void fillLevels(
JGraph jgraph,
List levels,
int level,
VertexView vertexView) {
// precondition control
if (vertexView == null)
return;
// be sure that the list container exists for the current level
if (levels.size() == level)
levels.add(level, new ArrayList());
// if the cell already visited return
if (vertexView.getAttributes().get(SUGIYAMA_VISITED) != null) {
return;
}
// mark as visited for cycle tests
vertexView.getAttributes().put(SUGIYAMA_VISITED, Boolean.TRUE);
// put the current node into the current level
// get the Level list
List listForTheCurrentLevel = (ArrayList) levels.get(level);
// Create a wrapper for the node
int numberForTheEntry = listForTheCurrentLevel.size();
CellWrapper wrapper =
new CellWrapper(level, numberForTheEntry, vertexView);
// put the Wrapper in the LevelVector
listForTheCurrentLevel.add(wrapper);
// concat the wrapper to the cell for an easy access
vertexView.getAttributes().put(SUGIYAMA_CELL_WRAPPER, wrapper);
// if the Cell has no Ports we can return, there are no relations
Object vertex = vertexView.getCell();
GraphModel model = jgraph.getModel();
GraphLayoutCache cache = jgraph.getGraphLayoutCache();
int portCount = model.getChildCount(vertex);
// iterate any NodePort
for (int i = 0; i < portCount; i++) {
Object port = model.getChild(vertex, i);
// iterate any Edge in the port
Iterator itrEdges = model.edges(port);
while (itrEdges.hasNext()) {
Object edge = itrEdges.next();
// if not selected, do not follow
if (!isSelected(cache, edge)) { continue; }
// if the Edge is a forward edge we should follow this edge
if (port == model.getSource(edge)) {
Object targetPort = model.getTarget(edge);
Object targetVertex = model.getParent(targetPort);
// if not selected, do not follow the vertex
if (!isSelected(cache, targetVertex)) { continue; }
VertexView targetVertexView =
(VertexView) jgraph.getGraphLayoutCache().getMapping(
targetVertex,
false);
fillLevels(jgraph, levels, (level + 1), targetVertexView);
}
}
}
if (listForTheCurrentLevel.size() > gridAreaSize) {
gridAreaSize = listForTheCurrentLevel.size();
}
}
/** calculates the minimum for the paint area.
*
*/
protected Point findMinimumAndSpacing(
CellView[] graphCellViews,
Point spacing) {
try {
// variables
/* represents the minimum x value for the paint area
*/
int min_x = 1000000;
/* represents the minimum y value for the paint area
*/
int min_y = 1000000;
// find the maximum & minimum coordinates
for (int i = 0; i < graphCellViews.length; i++) {
// the cellView and their bounds
CellView cellView = graphCellViews[i];
if (cellView == null)
continue;
Rectangle2D rect = cellView.getBounds();
Rectangle cellViewBounds = new Rectangle((int) rect.getX(),
(int) rect.getY(), (int) rect.getWidth(), (int) rect.getHeight());
// checking min area
try {
if (cellViewBounds.x < min_x)
min_x = cellViewBounds.x;
if (cellViewBounds.y < min_y)
min_y = cellViewBounds.y;
/*
if (cellViewBounds.width > spacing.x)
spacing.x = cellViewBounds.width;
if (cellViewBounds.height > spacing.y)
spacing.y = cellViewBounds.height;
*/
} catch (Exception e) {
System.err.println("---------> ERROR in calculateValues."
/*#Frozen*/
);
e.printStackTrace();
}
}
// if the cell sice is bigger than the userspacing
// dublicate the spacingfactor
return new Point(min_x, min_y);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/** Updates the progress based on the movements count
*
*/
protected void updateProgress4Movements() {
// adds the current loop count
movements.add(new Integer(movementsCurrentLoop));
iteration++;
// if the current loop count is higher than the max movements count
// memorize the new max
if (movementsCurrentLoop > movementsMax) {
movementsMax = movementsCurrentLoop;
}
}
protected void solveEdgeCrosses(JGraph jgraph, List levels) {
movements = new ArrayList(100);
movementsCurrentLoop = -1;
movementsMax = Integer.MIN_VALUE;
iteration = 0;
while (movementsCurrentLoop != 0) {
// reset the movements per loop count
movementsCurrentLoop = 0;
// top down
for (int i = 0; i < levels.size() - 1; i++) {
movementsCurrentLoop
+= solveEdgeCrosses(jgraph, true, levels, i);
}
// bottom up
for (int i = levels.size() - 1; i >= 1; i--) {
movementsCurrentLoop
+= solveEdgeCrosses(jgraph, false, levels, i);
}
updateProgress4Movements();
}
}
/**
* @return movements
*/
protected int solveEdgeCrosses(
JGraph jgraph,
boolean down,
List levels,
int levelIndex) {
// Get the current level
List currentLevel = (List) levels.get(levelIndex);
int movements = 0;
// restore the old sort
Object[] levelSortBefore = currentLevel.toArray();
// new sort
Collections.sort(currentLevel);
// test for movements
for (int j = 0; j < levelSortBefore.length; j++) {
if (((CellWrapper) levelSortBefore[j]).getEdgeCrossesIndicator()
!= ((CellWrapper) currentLevel.get(j))
.getEdgeCrossesIndicator()) {
movements++;
}
}
GraphModel model = jgraph.getModel();
GraphLayoutCache cache = jgraph.getGraphLayoutCache();
// Colections Sort sorts the highest value to the first value
for (int j = currentLevel.size() - 1; j >= 0; j--) {
CellWrapper sourceWrapper = (CellWrapper) currentLevel.get(j);
VertexView sourceView = sourceWrapper.getVertexView();
Object sourceVertex = sourceView.getCell();
int sourcePortCount = model.getChildCount(sourceVertex);
for (int k = 0; k < sourcePortCount; k++) {
Object sourcePort = model.getChild(sourceVertex, k);
Iterator sourceEdges = model.edges(sourcePort);
while (sourceEdges.hasNext()) {
Object edge = sourceEdges.next();
// if not selected, do not follow
if (!isSelected(cache, edge)) { continue; }
// if it is a forward edge follow it
Object targetPort = null;
if (down && sourcePort == model.getSource(edge)) {
targetPort = model.getTarget(edge);
}
if (!down && sourcePort == model.getTarget(edge)) {
targetPort = model.getSource(edge);
}
if (targetPort == null)
continue;
Object targetCell = model.getParent(targetPort);
// if the target cell not selected, do not follow
if (!isSelected(cache, targetCell))
continue;
VertexView targetVertexView =
(VertexView) jgraph.getGraphLayoutCache().getMapping(
targetCell,
false);
if (targetVertexView == null)
continue;
CellWrapper targetWrapper =
(CellWrapper) targetVertexView.getAttributes().get(
SUGIYAMA_CELL_WRAPPER);
// do it only if the edge is a forward edge to a deeper level
if (down
&& targetWrapper != null
&& targetWrapper.getLevel() > levelIndex) {
targetWrapper.addToEdgeCrossesIndicator(
sourceWrapper.getEdgeCrossesIndicator());
}
if (!down
&& targetWrapper != null
&& targetWrapper.getLevel() < levelIndex) {
targetWrapper.addToEdgeCrossesIndicator(
sourceWrapper.getEdgeCrossesIndicator());
}
}
}
}
return movements;
}
protected void moveToBarycenter(
JGraph jgraph,
CellView[] allSelectedViews,
List levels) {
//================================================================
// iterate any ReViewNodePort
GraphModel model = jgraph.getModel();
GraphLayoutCache cache = jgraph.getGraphLayoutCache();
for (int i = 0; i < allSelectedViews.length; i++) {
if (!(allSelectedViews[i] instanceof VertexView))
continue;
VertexView vertexView = (VertexView) allSelectedViews[i];
CellWrapper currentwrapper =
(CellWrapper) vertexView.getAttributes().get(
SUGIYAMA_CELL_WRAPPER);
Object vertex = vertexView.getCell();
int portCount = model.getChildCount(vertex);
for (int k = 0; k < portCount; k++) {
Object port = model.getChild(vertex, k);
// iterate any Edge in the port
Iterator edges = model.edges(port);
while (edges.hasNext()) {
Object edge = edges.next();
// if edge not selected, do not follow
if (!isSelected(cache, edge))
continue;
Object neighborPort = null;
// if the Edge is a forward edge we should follow this edge
if (port == model.getSource(edge)) {
neighborPort = model.getTarget(edge);
} else {
if (port == model.getTarget(edge)) {
neighborPort = model.getSource(edge);
} else {
continue;
}
}
Object neighborVertex = model.getParent(neighborPort);
// if vertex not selected, do not follow
if (!isSelected(cache, neighborVertex))
continue;
VertexView neighborVertexView =
(VertexView) jgraph.getGraphLayoutCache().getMapping(
neighborVertex,
false);
if (neighborVertexView == null
|| neighborVertexView == vertexView)
continue;
CellWrapper neighborWrapper =
(CellWrapper) neighborVertexView.getAttributes().get(
SUGIYAMA_CELL_WRAPPER);
if (currentwrapper == null
|| neighborWrapper == null
|| currentwrapper.level == neighborWrapper.level)
continue;
currentwrapper.priority++;
}
}
}
//================================================================
for (Iterator levelsIter = levels.iterator(); levelsIter.hasNext(); ) {
List level = (List) levelsIter.next();
int i = 0;
for (Iterator levelIter = level.iterator(); levelIter.hasNext(); i++) {
// calculate the initial Grid Positions 1, 2, 3, .... per Level
CellWrapper wrapper = (CellWrapper) levelIter.next();
wrapper.setGridPosition(i);
}
}
movements.clear();
movementsCurrentLoop = -1;
movementsMax = Integer.MIN_VALUE;
iteration = 0;
//int movements = 1;
while (movementsCurrentLoop != 0) {
// reset movements
movementsCurrentLoop = 0;
// top down
for (int i = 1; i < levels.size(); i++) {
movementsCurrentLoop += moveToBarycenter(jgraph, levels, i);
}
// bottom up
for (int i = levels.size() - 1; i >= 0; i--) {
movementsCurrentLoop += moveToBarycenter(jgraph, levels, i);
}
this.updateProgress4Movements();
}
}
protected int moveToBarycenter(
JGraph jgraph,
List levels,
int levelIndex) {
// Counter for the movements
int movements = 0;
// Get the current level
List currentLevel = (List) levels.get(levelIndex);
GraphModel model = jgraph.getModel();
GraphLayoutCache cache = jgraph.getGraphLayoutCache();
for (int currentIndexInTheLevel = 0;
currentIndexInTheLevel < currentLevel.size();
currentIndexInTheLevel++) {
CellWrapper sourceWrapper =
(CellWrapper) currentLevel.get(currentIndexInTheLevel);
float gridPositionsSum = 0;
float countNodes = 0;
VertexView vertexView = sourceWrapper.getVertexView();
Object vertex = vertexView.getCell();
int portCount = model.getChildCount(vertex);
for (int i = 0; i < portCount; i++) {
Object port = model.getChild(vertex, i);
Iterator edges = model.edges(port);
while (edges.hasNext()) {
Object edge = edges.next();
// if edge not selected, do not follow
if (!isSelected(cache, edge))
continue;
// if it is a forward edge follow it
Object neighborPort = null;
if (port == model.getSource(edge)) {
neighborPort = model.getTarget(edge);
} else {
if (port == model.getTarget(edge)) {
neighborPort = model.getSource(edge);
} else {
continue;
}
}
Object neighborVertex = model.getParent(neighborPort);
// if vertex not selected, do not follow
if (!isSelected(cache, neighborVertex))
continue;
VertexView neighborVertexView =
(VertexView) jgraph.getGraphLayoutCache().getMapping(
neighborVertex,
false);
if (neighborVertexView == null)
continue;
CellWrapper targetWrapper =
(CellWrapper) neighborVertexView.getAttributes().get(
SUGIYAMA_CELL_WRAPPER);
if (targetWrapper == sourceWrapper)
continue;
if (targetWrapper == null
|| targetWrapper.getLevel() == levelIndex)
continue;
gridPositionsSum += targetWrapper.getGridPosition();
countNodes++;
}
}
//----------------------------------------------------------
// move node to new x coord
//----------------------------------------------------------
if (countNodes > 0) {
float tmp = (gridPositionsSum / countNodes);
int newGridPosition = Math.round(tmp);
boolean toRight =
(newGridPosition > sourceWrapper.getGridPosition());
boolean moved = true;
while (newGridPosition != sourceWrapper.getGridPosition()
&& moved) {
int tmpGridPos = sourceWrapper.getGridPosition();
moved =
move(
toRight,
currentLevel,
currentIndexInTheLevel,
sourceWrapper.getPriority());
if (moved)
movements++;
}
}
}
return movements;
}
/**@param toRight true = try to move the currentWrapper to right; false = try to move the currentWrapper to left;
* @param currentLevel List which contains the CellWrappers for the current level
* @param currentIndexInTheLevel
* @param currentPriority
* @param currentWrapper The Wrapper
*
* @return The free GridPosition or -1 is position is not free.
*/
protected boolean move(
boolean toRight,
List currentLevel,
int currentIndexInTheLevel,
int currentPriority) {
CellWrapper currentWrapper =
(CellWrapper) currentLevel.get(currentIndexInTheLevel);
boolean moved = false;
int neighborIndexInTheLevel =
currentIndexInTheLevel + (toRight ? 1 : -1);
int newGridPosition =
currentWrapper.getGridPosition() + (toRight ? 1 : -1);
// is the grid position possible?
if (0 > newGridPosition || newGridPosition >= gridAreaSize) {
return false;
}
// if the node is the first or the last we can move
if (toRight
&& currentIndexInTheLevel == currentLevel.size() - 1
|| !toRight
&& currentIndexInTheLevel == 0) {
moved = true;
} else {
// else get the neighbor and ask his gridposition
// if he has the requested new grid position
// check the priority
CellWrapper neighborWrapper =
(CellWrapper) currentLevel.get(neighborIndexInTheLevel);
int neighborPriority = neighborWrapper.getPriority();
if (neighborWrapper.getGridPosition() == newGridPosition) {
if (neighborPriority >= currentPriority) {
return false;
} else {
moved =
move(
toRight,
currentLevel,
neighborIndexInTheLevel,
currentPriority);
}
} else {
moved = true;
}
}
if (moved) {
currentWrapper.setGridPosition(newGridPosition);
}
return moved;
}
/** This Method draws the graph. For the horizontal position
* we are using the grid position from each graphcell.
* For the vertical position we are using the level position.
*
*/
protected void drawGraph(
JGraph jgraph,
List levels,
Point min,
Point spacing) {
// paint the graph
Map viewMap = new Hashtable();
for (int rowCellCount = 0;
rowCellCount < levels.size();
rowCellCount++) {
List level = (List) levels.get(rowCellCount);
for (int colCellCount = 0;
colCellCount < level.size();
colCellCount++) {
CellWrapper wrapper = (CellWrapper) level.get(colCellCount);
VertexView view = wrapper.vertexView;
// remove the temp objects
/* While the Algorithm is running we are putting some
* attributeNames to the MyGraphCells. This method
* cleans this objects from the MyGraphCells.
*
*/
view.getAttributes().remove(SUGIYAMA_CELL_WRAPPER);
view.getAttributes().remove(SUGIYAMA_VISITED);
wrapper.vertexView = null;
// get the bounds from the cellView
if (view == null)
continue;
Rectangle2D rect = (Rectangle2D) view.getBounds().clone();
Rectangle bounds = new Rectangle((int) rect.getX(),
(int) rect.getY(), (int) rect.getWidth(), (int) rect.getHeight());
//(Rectangle) view.getBounds().clone();
// adjust
bounds.x = min.x + spacing.x * ((vertical) ? wrapper.getGridPosition() : rowCellCount);
bounds.y = min.y + spacing.y * ((vertical) ? rowCellCount : wrapper.getGridPosition());
Object cell = view.getCell();
Map map = new Hashtable();
GraphConstants.setBounds(map, (Rectangle2D) bounds.clone());
viewMap.put(cell, map);
}
}
jgraph.getGraphLayoutCache().edit(viewMap, null, null, null);
}
/** cell wrapper contains all values
* for one node
*/
class CellWrapper implements Comparable {
/** sum value for edge Crosses
*/
private double edgeCrossesIndicator = 0;
/** counter for additions to the edgeCrossesIndicator
*/
private int additions = 0;
/** the vertical level where the cell wrapper is inserted
*/
int level = 0;
/** current position in the grid
*/
int gridPosition = 0;
/** priority for movements to the barycenter
*/
int priority = 0;
/** reference to the wrapped cell
*/
VertexView vertexView = null;
/** creates an instance and memorizes the parameters
*
*/
CellWrapper(
int level,
double edgeCrossesIndicator,
VertexView vertexView) {
this.level = level;
this.edgeCrossesIndicator = edgeCrossesIndicator;
this.vertexView = vertexView;
additions++;
}
/** returns the wrapped cell
*/
VertexView getVertexView() {
return vertexView;
}
/** resets the indicator for edge crosses to 0
*/
void resetEdgeCrossesIndicator() {
edgeCrossesIndicator = 0;
additions = 0;
}
/** retruns the average value for the edge crosses indicator
*
* for the wrapped cell
*
*/
double getEdgeCrossesIndicator() {
if (additions == 0)
return 0;
return edgeCrossesIndicator / additions;
}
/** Addes a value to the edge crosses indicator
* for the wrapped cell
*
*/
void addToEdgeCrossesIndicator(double addValue) {
edgeCrossesIndicator += addValue;
additions++;
}
/** gets the level of the wrapped cell
*/
int getLevel() {
return level;
}
/** gets the grid position for the wrapped cell
*/
int getGridPosition() {
return gridPosition;
}
/** Sets the grid position for the wrapped cell
*/
void setGridPosition(int pos) {
this.gridPosition = pos;
}
/** increments the the priority of this cell wrapper.
*
* The priority was used by moving the cell to its
* barycenter.
*
*/
void incrementPriority() {
priority++;
}
/** returns the priority of this cell wrapper.
*
* The priority was used by moving the cell to its
* barycenter.
*/
int getPriority() {
return priority;
}
/**
* @see java.lang.Comparable#compareTo(Object)
*/
public int compareTo(Object compare) {
if (((CellWrapper) compare).getEdgeCrossesIndicator()
== this.getEdgeCrossesIndicator())
return 0;
double compareValue =
(((CellWrapper) compare).getEdgeCrossesIndicator()
- this.getEdgeCrossesIndicator());
return (int) (compareValue * 1000);
}
}
/**
* Returns the spacing.
*
* @see #setSpacing
*/
public Point getSpacing() {
return spacing;
}
/**
* Sets grid spacing.
*
* The Algorithm distributes the nodes on a grid. For this
* grid you can configure the vertical and horizontal spacing.
*
*/
public void setSpacing(Point spacing) {
this.spacing = spacing;
}
/**
* Returns the current layout direction
* @return boolean whether or not direction is vertical
* @see #setVertical(boolean)
*/
public boolean isVertical() {
return vertical;
}
/**
* Sets the layout direction.
*
* @param vertical true for vertical and false for horizontal direction
*/
public void setVertical(boolean vertical) {
this.vertical = vertical;
}
/**
* Get the FlushToOrigin value.
*
* @see #setFlushToOrigin(boolean)
*
* @return a boolean value
*/
public final boolean getFlushToOrigin()
{
return flushToOrigin;
}
/**
* After layout, moves the graph as close to origin as possible.
*
*
After the layout has complete, this algorithm calculates the * minimum X and Y coordinates over all selected cells. If * flushToOrigin parameter is set to false, the algorithm will place * cells starting at coordinates corresponding to those minimum * values. * *
If set to true, the layout will place cells starting at the * origin, possibly shrinking the overall graph canvas size * * @param newFlushToOrigin The new FlushToOrigin value. */ public final void setFlushToOrigin(final boolean newFlushToOrigin) { this.flushToOrigin = newFlushToOrigin; } }