/******************************************************************************* * Copyright (c) 2003, 2007 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.draw2d.graph; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; /** * Assigns the X and width values for nodes in a directed graph. * @author Randy Hudson * @since 2.1.2 */ class HorizontalPlacement extends SpanningTreeVisitor { class ClusterSet { int freedom = Integer.MAX_VALUE; boolean isRight; public List members = new ArrayList(); int pullWeight = 0; int rawPull = 0; boolean addCluster (NodeCluster seed) { members.add(seed); seed.isSetMember = true; rawPull += seed.weightedTotal; pullWeight += seed.weightedDivisor; if (isRight) { freedom = Math.min(freedom, seed.rightNonzero); if (freedom == 0 || rawPull <= 0) return true; addIncomingClusters(seed); if (addOutgoingClusters(seed)) return true; } else { freedom = Math.min(freedom, seed.leftNonzero); if (freedom == 0 || rawPull >= 0) return true; addOutgoingClusters(seed); if (addIncomingClusters(seed)) return true; } return false; } boolean addIncomingClusters(NodeCluster seed) { for (int i = 0; i < seed.leftCount; i++) { NodeCluster neighbor = seed.leftNeighbors[i]; if (neighbor.isSetMember) continue; CollapsedEdges edges = seed.leftLinks[i]; if (!edges.isTight()) continue; if ((!isRight || neighbor.getPull() > 0) && addCluster (neighbor)) return true; } return false; } boolean addOutgoingClusters(NodeCluster seed) { for (int i = 0; i < seed.rightCount; i++) { NodeCluster neighbor = seed.rightNeighbors[i]; if (neighbor.isSetMember) continue; CollapsedEdges edges = seed.rightLinks[i]; if (!edges.isTight()) continue; if ((isRight || neighbor.getPull() < 0) && addCluster (neighbor)) return true; } return false; } boolean build(NodeCluster seed) { isRight = seed.weightedTotal > 0; if (!addCluster(seed)) { int delta = rawPull / pullWeight; if (delta < 0) delta = Math.max(delta, -freedom); else delta = Math.min(delta, freedom); if (delta != 0) { for (int i = 0; i < members.size(); i++) { NodeCluster c = (NodeCluster)members.get(i); c.adjustRank(delta, dirtyClusters); } refreshDirtyClusters(); reset(); return true; } } reset(); return false; } void reset() { rawPull = pullWeight = 0; for (int i = 0; i < members.size(); i++) ((NodeCluster)members.get(i)).isSetMember = false; members.clear(); freedom = Integer.MAX_VALUE; } } static int step; private List allClusters; private Map clusterMap = new HashMap(); ClusterSet clusterset = new ClusterSet(); Collection dirtyClusters = new HashSet(); DirectedGraph graph; Map map = new HashMap(); DirectedGraph prime; Node graphRight; Node graphLeft; /** * Inset the corresponding parts for the given 2 nodes along an edge E. The weight value * is a value by which to scale the edges specified weighting factor. * @param u the source * @param v the target * @param e the edge along which u and v exist * @param weight a scaling for the weight */ void addEdge(Node u, Node v, Edge e, int weight) { Node ne = new Node(new NodePair(u, v)); prime.nodes.add(ne); ne.y = (u.y + u.height + v.y) / 2; Node uPrime = get(u); Node vPrime = get(v); int uOffset = e.getSourceOffset(); int vOffset = e.getTargetOffset(); Edge eu = new Edge(ne, uPrime, 0, e.weight * weight); Edge ev = new Edge(ne, vPrime, 0, e.weight * weight); int dw = uOffset - vOffset; if (dw < 0) eu.delta = -dw; else ev.delta = dw; prime.edges.add(eu); prime.edges.add(ev); } /** * Adds all of the incoming edges to the graph. * @param n the original node * @param nPrime its corresponding node in the auxilary graph */ void addEdges(Node n) { for (int i = 0; i < n.incoming.size(); i++) { Edge e = n.incoming.getEdge(i); addEdge(e.source, n, e, 1); } } void applyGPrime() { Node node; for (int n = 0; n < prime.nodes.size(); n++) { node = prime.nodes.getNode(n); if (node.data instanceof Node) ((Node)node.data).x = node.rank; } } private void balanceClusters() { findAllClusters(); step = 0; boolean somethingMoved = false; for (int i = 0; i < allClusters.size();) { NodeCluster c = (NodeCluster)allClusters.get(i); int delta = c.getPull(); if (delta < 0) { if (c.leftFreedom > 0) { c.adjustRank(Math.max(delta, -c.leftFreedom), dirtyClusters); refreshDirtyClusters(); moveClusterForward(i, c); somethingMoved = true; step++; } else if (clusterset.build(c)) { step++; moveClusterForward(i, c); somethingMoved = true; } } else if (delta > 0) { if (c.rightFreedom > 0) { c.adjustRank(Math.min(delta, c.rightFreedom), dirtyClusters); refreshDirtyClusters(); moveClusterForward(i, c); somethingMoved = true; step++; } else if (clusterset.build(c)) { step++; moveClusterForward(i, c); somethingMoved = true; } } i++; if (i == allClusters.size() && somethingMoved) { i = 0; somethingMoved = false; } } } //boolean balanceClusterSets() { // for (int i = 0; i < allClusters.size(); i++) { // NodeCluster c = (NodeCluster)allClusters.get(i); // if (c.weightedTotal < 0 && c.leftFreedom == 0) { // if (clusterset.build(c)) { // moveClusterForward(i, c); // return true; // } // } else if (c.weightedTotal > 0 && c.rightFreedom == 0) { // if (clusterset.build(c)) { // moveClusterForward(i, c); // return true; // } // } // } // return false; //} void buildGPrime() { RankList ranks = graph.ranks; buildRankSeparators(ranks); Rank rank; Node n; for (int r = 1; r < ranks.size(); r++) { rank = ranks.getRank(r); for (int i = 0; i < rank.count(); i++) { n = rank.getNode(i); addEdges(n); } } } void buildRankSeparators(RankList ranks) { Rank rank; Node n, nPrime, prevNPrime; Edge e; for (int r = 0; r < ranks.size(); r++) { rank = ranks.getRank(r); prevNPrime = null; for (int i = 0; i < rank.count(); i++) { n = rank.getNode(i); nPrime = new Node(n); if (i == 0) { e = new Edge(graphLeft, nPrime, 0, 0); prime.edges.add(e); e.delta = graph.getPadding(n).left + graph.getMargin().left; } else { e = new Edge(prevNPrime, nPrime); e.weight = 0; prime.edges.add(e); rowSeparation(e); } prevNPrime = nPrime; prime.nodes.add(nPrime); map(n, nPrime); if (i == rank.count() - 1) { e = new Edge(nPrime, graphRight, 0, 0); e.delta = n.width + graph.getPadding(n).right + graph.getMargin().right; prime.edges.add(e); } } } } private void calculateCellLocations() { graph.cellLocations = new int[graph.ranks.size() + 1][]; for (int row = 0; row < graph.ranks.size(); row++) { Rank rank = graph.ranks.getRank(row); int locations[] = graph.cellLocations[row] = new int[rank.size() + 1]; int cell; Node node = null; for (cell = 0; cell < rank.size(); cell++) { node = rank.getNode(cell); locations[cell] = node.x - graph.getPadding(node).left; } locations[cell] = node.x + node.width + graph.getPadding(node).right; } } private void findAllClusters() { Node root = prime.nodes.getNode(0); NodeCluster cluster = new NodeCluster(); allClusters = new ArrayList(); allClusters.add(cluster); growCluster(root, cluster); for (int i = 0; i < prime.edges.size(); i++) { Edge e = prime.edges.getEdge(i); NodeCluster sourceCluster = (NodeCluster)clusterMap.get(e.source); NodeCluster targetCluster = (NodeCluster)clusterMap.get(e.target); //Ignore cluster internal edges if (targetCluster == sourceCluster) continue; CollapsedEdges link = sourceCluster.getRightNeighbor(targetCluster); if (link == null) { link = new CollapsedEdges(e); sourceCluster.addRightNeighbor(targetCluster, link); targetCluster.addLeftNeighbor(sourceCluster, link); } else { prime.removeEdge(link.processEdge(e)); i--; } } for (int i = 0; i < allClusters.size(); i++) ((NodeCluster)allClusters.get(i)).initValues(); } Node get(Node key) { return (Node)map.get(key); } void growCluster(Node root, NodeCluster cluster) { cluster.add(root); clusterMap.put(root, cluster); EdgeList treeChildren = getSpanningTreeChildren(root); for (int i = 0; i < treeChildren.size(); i++) { Edge e = treeChildren.getEdge(i); if (e.cut != 0) growCluster(getTreeTail(e), cluster); else { NodeCluster newCluster = new NodeCluster(); allClusters.add(newCluster); growCluster(getTreeTail(e), newCluster); } } } void map(Node key, Node value) { map.put(key, value); } private void moveClusterForward(int i, NodeCluster c) { if (i == 0) return; int swapIndex = i / 2; Object temp = allClusters.get(swapIndex); allClusters.set(swapIndex, c); allClusters.set(i, temp); } void refreshDirtyClusters() { for (Iterator iter = dirtyClusters.iterator(); iter.hasNext();) ((NodeCluster)iter.next()).refreshValues(); dirtyClusters.clear(); } void rowSeparation(Edge e) { Node source = (Node)e.source.data; Node target = (Node)e.target.data; e.delta = source.width + graph.getPadding(source).right + graph.getPadding(target).left; } public void visit(DirectedGraph g) { graph = g; prime = new DirectedGraph(); prime.nodes.add(graphLeft = new Node(null)); prime.nodes.add(graphRight = new Node(null)); if (g.tensorStrength != 0) prime.edges.add(new Edge(graphLeft, graphRight, g.tensorSize, g.tensorStrength)); buildGPrime(); new InitialRankSolver() .visit(prime); new TightSpanningTreeSolver() .visit(prime); RankAssignmentSolver solver = new RankAssignmentSolver(); solver.visit(prime); graph.size.width = graphRight.rank; balanceClusters(); prime.nodes.adjustRank(-graphLeft.rank); applyGPrime(); calculateCellLocations(); } }