| /******************************************************************************* |
| * Copyright (c) 2003, 2010 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(); |
| } |
| |
| } |