/* * $Id: mxHierarchicalLayout.java,v 1.12 2010-12-01 18:08:40 david Exp $ * Copyright (c) 2005-2010, David Benson, Gaudenz Alder */ package com.mxgraph.layout.hierarchical; import java.awt.geom.Point2D; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.Stack; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.SwingConstants; import com.mxgraph.layout.mxGraphLayout; import com.mxgraph.layout.hierarchical.model.mxGraphHierarchyModel; import com.mxgraph.layout.hierarchical.stage.mxCoordinateAssignment; import com.mxgraph.layout.hierarchical.stage.mxHierarchicalLayoutStage; import com.mxgraph.layout.hierarchical.stage.mxMedianHybridCrossingReduction; import com.mxgraph.layout.hierarchical.stage.mxMinimumCycleRemover; import com.mxgraph.model.mxIGraphModel; import com.mxgraph.view.mxGraph; /** * The top level compound layout of the hierarchical layout. The individual * elements of the layout are called in sequence. */ public class mxHierarchicalLayout extends mxGraphLayout/*, JGraphLayout.Stoppable*/ { /** The root nodes of the layout */ protected List<Object> roots = null; /** * Specifies if the parent should be resized after the layout so that it * contains all the child cells. Default is false. @See parentBorder. */ protected boolean resizeParent = false; /** * Specifies if the parnent should be moved if resizeParent is enabled. * Default is false. @See resizeParent. */ protected boolean moveParent = false; /** * The border to be added around the children if the parent is to be * resized using resizeParent. Default is 0. @See resizeParent. */ protected int parentBorder = 0; /** * The spacing buffer added between cells on the same layer */ protected double intraCellSpacing = 30.0; /** * The spacing buffer added between cell on adjacent layers */ protected double interRankCellSpacing = 50.0; /** * The spacing buffer between unconnected hierarchies */ protected double interHierarchySpacing = 60.0; /** * The distance between each parallel edge on each ranks for long edges */ protected double parallelEdgeSpacing = 10.0; /** * The position of the root node(s) relative to the laid out graph in. * Default is <code>SwingConstants.NORTH</code>, i.e. top-down. */ protected int orientation = SwingConstants.NORTH; /** * Specifies if the STYLE_NOEDGESTYLE flag should be set on edges that are * modified by the result. Default is true. */ protected boolean disableEdgeStyle = true; /** * Whether or not to perform local optimisations and iterate multiple times * through the algorithm */ protected boolean fineTuning = true; /** * Whether or not cells are ordered according to the order in the graph * model. Defaults to false since sorting usually produces quadratic * performance. Note that since mxGraph returns edges in a deterministic * order, it might be that this layout is always deterministic using that * JGraph regardless of this flag setting (i.e. leave it false in that * case). Default is true. */ protected boolean deterministic; /** * Whether or not to fix the position of the root cells. Keep in mind to * turn off features such as move to origin when fixing the roots, move * to origin usually overrides this flag (in JGraph it does). */ protected boolean fixRoots = false; /** * Whether or not the initial scan of the graph to determine the layer * assigned to each vertex starts from the sinks or source (the sinks * being vertices with the fewest, preferable zero, outgoing edges and * sources same with incoming edges). Starting from either direction * can tight the layout up and also produce better results for certain * types of graphs. If the result for the default is not good enough * try a few sample layouts with the value false to see if they improve */ protected boolean layoutFromSinks = true; /** * The internal model formed of the layout */ protected mxGraphHierarchyModel model = null; /** * The layout progress bar */ //protected JGraphLayoutProgress progress = new JGraphLayoutProgress(); /** The logger for this class */ private static Logger logger = Logger .getLogger("com.jgraph.layout.hierarchical.JGraphHierarchicalLayout"); /** * Constructs a hierarchical layout * @param graph the graph to lay out * */ public mxHierarchicalLayout(mxGraph graph) { this(graph, SwingConstants.NORTH); } /** * Constructs a hierarchical layout * @param graph the graph to lay out * @param orientation <code>SwingConstants.NORTH, SwingConstants.EAST, SwingConstants.SOUTH</code> or <code> SwingConstants.WEST</code> * */ public mxHierarchicalLayout(mxGraph graph, int orientation) { super(graph); this.orientation = orientation; } /** * Returns the model for this layout algorithm. */ public mxGraphHierarchyModel getModel() { return model; } /** * Executes the layout for the children of the specified parent. * * @param parent Parent cell that contains the children to be laid out. */ public void execute(Object parent) { execute(parent, null); } /** * Executes the layout for the children of the specified parent. * * @param parent Parent cell that contains the children to be laid out. * @param roots the starting roots of the layout */ public void execute(Object parent, List<Object> roots) { if (roots == null) { roots = graph.findTreeRoots(parent); } this.roots = roots; mxIGraphModel model = graph.getModel(); model.beginUpdate(); try { run(parent); if (isResizeParent() && !graph.isCellCollapsed(parent)) { graph.updateGroupBounds(new Object[] { parent }, getParentBorder(), isMoveParent()); } } finally { model.endUpdate(); } } /** * The API method used to exercise the layout upon the graph description * and produce a separate description of the vertex position and edge * routing changes made. */ public void run(Object parent) { // Separate out unconnected hierarchies List<Set<Object>> hierarchyVertices = new ArrayList<Set<Object>>(); // Keep track of one root in each hierarchy in case it's fixed position List<Object> fixedRoots = null; List<Point2D> rootLocations = null; List<Set<Object>> affectedEdges = null; if (fixRoots) { fixedRoots = new ArrayList<Object>(); rootLocations = new ArrayList<Point2D>(); affectedEdges = new ArrayList<Set<Object>>(); } for (int i = 0; i < roots.size(); i++) { // First check if this root appears in any of the previous vertex // sets boolean newHierarchy = true; Iterator<Set<Object>> iter = hierarchyVertices.iterator(); while (newHierarchy && iter.hasNext()) { if (iter.next().contains(roots.get(i))) { newHierarchy = false; } } if (newHierarchy) { // Obtains set of vertices connected to this root Stack<Object> cellsStack = new Stack<Object>(); cellsStack.push(roots.get(i)); Set<Object> edgeSet = null; if (fixRoots) { fixedRoots.add(roots.get(i)); Point2D location = getVertexBounds(roots.get(i)).getPoint(); rootLocations.add(location); edgeSet = new HashSet<Object>(); } Set<Object> vertexSet = new HashSet<Object>(); while (!cellsStack.isEmpty()) { Object cell = cellsStack.pop(); if (!vertexSet.contains(cell)) { vertexSet.add(cell); if (fixRoots) { edgeSet.addAll(Arrays.asList(graph .getIncomingEdges(cell, parent))); } Object[] conns = graph.getConnections(cell, parent); Object[] cells = graph.getOpposites(conns, cell); for (int j = 0; j < cells.length; j++) { if (!vertexSet.contains(cells[j])) { cellsStack.push(cells[j]); } } } } hierarchyVertices.add(vertexSet); if (fixRoots) { affectedEdges.add(edgeSet); } } } // Perform a layout for each seperate hierarchy // Track initial coordinate x-positioning double initialX = 0; Iterator<Set<Object>> iter = hierarchyVertices.iterator(); int i = 0; while (iter.hasNext()) { Set<Object> vertexSet = iter.next(); model = new mxGraphHierarchyModel(this, vertexSet.toArray(), roots, parent, false, deterministic, layoutFromSinks); cycleStage(parent); layeringStage(); crossingStage(parent); initialX = placementStage(initialX, parent); if (fixRoots) { // Reposition roots and their hierarchies using their bounds // stored earlier Object root = fixedRoots.get(i); Point2D oldLocation = rootLocations.get(i); Point2D newLocation = graph.getModel().getGeometry(root) .getPoint(); double diffX = oldLocation.getX() - newLocation.getX(); double diffY = oldLocation.getY() - newLocation.getY(); graph.moveCells(vertexSet.toArray(), diffX, diffY); // Also translate connected edges Set<Object> connectedEdges = affectedEdges.get(i++); graph.moveCells(connectedEdges.toArray(), diffX, diffY); } } } /** * Executes the cycle stage. This implementation uses the * mxMinimumCycleRemover. */ public void cycleStage(Object parent) { mxHierarchicalLayoutStage cycleStage = new mxMinimumCycleRemover(this); cycleStage.execute(parent); } /** * Implements first stage of a Sugiyama layout. */ public void layeringStage() { model.initialRank(); model.fixRanks(); } /** * Executes the crossing stage using mxMedianHybridCrossingReduction. */ public void crossingStage(Object parent) { mxHierarchicalLayoutStage crossingStage = new mxMedianHybridCrossingReduction( this); crossingStage.execute(parent); } /** * Executes the placement stage using mxCoordinateAssignment. */ public double placementStage(double initialX, Object parent) { mxCoordinateAssignment placementStage = new mxCoordinateAssignment( this, intraCellSpacing, interRankCellSpacing, orientation, initialX, parallelEdgeSpacing); placementStage.setFineTuning(fineTuning); placementStage.execute(parent); return placementStage.getLimitX() + interHierarchySpacing; } /** * Returns the resizeParent flag. */ public boolean isResizeParent() { return resizeParent; } /** * Sets the resizeParent flag. */ public void setResizeParent(boolean value) { resizeParent = value; } /** * Returns the moveParent flag. */ public boolean isMoveParent() { return moveParent; } /** * Sets the moveParent flag. */ public void setMoveParent(boolean value) { moveParent = value; } /** * Returns parentBorder. */ public int getParentBorder() { return parentBorder; } /** * Sets parentBorder. */ public void setParentBorder(int value) { parentBorder = value; } /** * @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 interRankCellSpacing. */ public double getInterRankCellSpacing() { return interRankCellSpacing; } /** * @param interRankCellSpacing * The interRankCellSpacing to set. */ public void setInterRankCellSpacing(double interRankCellSpacing) { this.interRankCellSpacing = interRankCellSpacing; } /** * @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 interHierarchySpacing. */ public double getInterHierarchySpacing() { return interHierarchySpacing; } /** * @param interHierarchySpacing * The interHierarchySpacing to set. */ public void setInterHierarchySpacing(double interHierarchySpacing) { this.interHierarchySpacing = interHierarchySpacing; } public double getParallelEdgeSpacing() { return parallelEdgeSpacing; } public void setParallelEdgeSpacing(double parallelEdgeSpacing) { this.parallelEdgeSpacing = parallelEdgeSpacing; } /** * @return Returns the fineTuning. */ public boolean isFineTuning() { return fineTuning; } /** * @param fineTuning * The fineTuning to set. */ public void setFineTuning(boolean fineTuning) { this.fineTuning = fineTuning; } /** * */ public boolean isDisableEdgeStyle() { return disableEdgeStyle; } /** * * @param disableEdgeStyle */ public void setDisableEdgeStyle(boolean disableEdgeStyle) { this.disableEdgeStyle = disableEdgeStyle; } /** * @return Returns the deterministic. */ public boolean isDeterministic() { return deterministic; } /** * @param deterministic The deterministic to set. */ public void setDeterministic(boolean deterministic) { this.deterministic = deterministic; } /** * @return Returns the fixRoots. */ public boolean isFixRoots() { return fixRoots; } /** * @param fixRoots The fixRoots to set. */ public void setFixRoots(boolean fixRoots) { this.fixRoots = fixRoots; } public boolean isLayoutFromSinks() { return layoutFromSinks; } public void setLayoutFromSinks(boolean layoutFromSinks) { this.layoutFromSinks = layoutFromSinks; } /** * 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 } } /** * Returns <code>Hierarchical</code>, the name of this algorithm. */ public String toString() { return "Hierarchical"; } }