package edu.stanford.hci.flowmap.cluster; import java.util.Collection; import java.util.HashMap; import java.util.LinkedList; import org.codemap.util.geom.Line2D; import org.codemap.util.geom.Point2D; import edu.stanford.hci.flowmap.structure.Edge; import edu.stanford.hci.flowmap.structure.Graph; import edu.stanford.hci.flowmap.structure.Node; import edu.stanford.hci.flowmap.utils.GraphicsGems; /** * This version uses nodes in the structure package instead of * the extended prefuse nodes (which are now only used for rendering) * * This software is distributed under the Berkeley Software Distribution License. * Please see http://graphics.stanford.edu/~dphan/code/bsd.license.html */ public class ClusterLayout extends FlowLayout { Collection<Cluster> allClusters; protected HierarchicalCluster original_Cluster; public ClusterLayout(Graph g) { super(g.getRootNode(), g.getAllNodes()); assert(g.getRootNode() != null); original_Cluster = new HierarchicalCluster(); } public Node doLayout(){ allClusters = original_Cluster.doCluster(source, allNodes); toFlowTree(source, allClusters); return source; } public Collection<Cluster> getClusterCollection() { return allClusters; } /** * Constructs a tree that FlowRender can use to draw the tree * @param manyClusters the clusters attached to this node * @return the root of the tree */ private void toFlowTree(Node rootN, Collection<Cluster> manyClusters) { LinkedList<Node> queue = new LinkedList<Node>(); Node2Cluster node2Cluster = new Node2Cluster(); queue.add(rootN); node2Cluster.put(rootN, manyClusters); while(queue.size() > 0) { Node n = (Node) queue.removeFirst(); //System.out.println("ToFlowTree got: " + n); LinkedList<Cluster> clusterList = node2Cluster.get(n); assert(clusterList != null && clusterList.size() > 0); Node newNode; //System.out.println("ToFlowTree clusterListSize " + clusterList.size()); if (clusterList.size() == 1) { Cluster c1 = clusterList.removeFirst(); newNode = processCluster(n, c1, node2Cluster); if (newNode != null) { //System.out.println("toFlowTree newNode: " + newNode); newNode.setChildCluster(c1); c1.setRenderedNode(newNode); queue.add(newNode); } } else { // more than one cluster per node for(Cluster clus: clusterList) { newNode = processCluster(n, clus, node2Cluster); if (newNode != null) { //System.out.println("toFlowTree newNode: " + newNode); ((Node)newNode).setChildCluster(clus); clus.setRenderedNode(newNode); queue.add(newNode); } } } } } /** * Adds edges from a parent node that is outside the cluster to the elements * of the cluster. * @param parent the parent node of the cluster * @param clus the cluster that we are drawing to * @return the new node we created */ private Node processCluster(Node parent, Cluster clus, Node2Cluster node2Cluster) { Node newNode = null; //System.out.println("ClusterLayout.processCluster got parent:" + parent + " and cluster " + clus); // now check all the conditions for the cluster // simple node, just add an edge. Shouldn't happen too often if ( clus.isNodeCluster()) { //System.out.println("SimpleNode case"); Node clusNode = clus.getRenderedNode(); Edge e = new Edge(parent, clusNode, clus.getWeight()); parent.addOutEdge(e); clusNode.addInEdge(e); clusNode.setRoutingParent(parent); } // we have two clusters else { assert (clus.oneCluster != null && clus.twoCluster != null); // we have two leaf nodes. if ((clus.oneCluster.isNodeCluster()) && (clus.twoCluster.isNodeCluster())) { processLeafCluster(parent, clus); } // one is leaf, two is cluster. In this case draw a direct edge towards else if ((clus.oneCluster.isNodeCluster()) && (!clus.twoCluster.isNodeCluster())) { newNode = processMixedCluster(parent, clus, clus.oneCluster, clus.twoCluster); // now add some info to the node2Cluster map if (newNode == parent) node2Cluster.remove(parent); node2Cluster.put(newNode, clus.twoCluster); } // one is cluster, two is leaf else if ((!clus.oneCluster.isNodeCluster()) && (clus.twoCluster.isNodeCluster())) { newNode = processMixedCluster(parent, clus, clus.twoCluster, clus.oneCluster); if (newNode == parent) node2Cluster.remove(parent); node2Cluster.put(newNode, clus.oneCluster); } // both are clusters else { newNode = processTwoCluster(parent, clus, clus.oneCluster, clus.twoCluster); if (newNode == parent) node2Cluster.remove(parent); node2Cluster.put(newNode, clus.oneCluster); node2Cluster.put(newNode, clus.twoCluster); } } return newNode; } /** * Constructs part of the layout tree in the case when we have a node that * is connected to a cluster which consists of two nodes. * In particular, we create an newNode between the parent and the leaf * node with the most weight. The new node is halfway between the * parent node and the closest leaf node (not necessarily the one * with the most weight) * * @param parent the parent node of this cluster * @param clus the cluster we are drawing to (has 2 nodes) */ private void processLeafCluster(Node parent, Cluster clus) { //System.out.println("LeafCluster Process: " + parent); Point2D parentPt = parent.getLocation(); Point2D onePt = clus.oneCluster.getRenderedNode().getLocation(); Point2D twoPt = clus.twoCluster.getRenderedNode().getLocation(); GraphicsGems.checkNaN(onePt); GraphicsGems.checkNaN(twoPt); // figure out which leaf node is closer double oneDist, twoDist, closerDist; oneDist = parentPt.distance(onePt); twoDist = parentPt.distance(twoPt); if (oneDist < twoDist) { closerDist = oneDist; } else { closerDist = twoDist; } closerDist /= 2; // figure out which sub-cluster is bigger Point2D biggerPt; Cluster biggerClus, smallerClus; // find the distance between the parent and the node with more weight if (clus.oneCluster.getWeight() > clus.twoCluster.getWeight()) { biggerPt = onePt; biggerClus = clus.oneCluster; smallerClus = clus.twoCluster; } else { biggerPt = twoPt; biggerClus = clus.twoCluster; smallerClus = clus.oneCluster; } double biggerDist = parentPt.distance(biggerPt); // if closerDist is small, bigFraction is 0, so we want // the parentFraction to be big. double bigFraction = closerDist/biggerDist; double parentFraction = 1 - bigFraction; // set the newPt location to be newFraction of the way // between parentPt and biggerPt double x = parentPt.getX()*parentFraction+biggerPt.getX()*bigFraction; double y = parentPt.getY()*parentFraction+biggerPt.getY()*bigFraction; // create the new, intermediate node Node newNode = new Node(x, y); newNode.setChildCluster(clus); clus.setRenderedNode(newNode); // update the edge information Edge parent2New, new2Big, new2Small; parent2New = new Edge(parent, newNode, clus.getWeight()); new2Big = new Edge(newNode, biggerClus.getRenderedNode(), biggerClus.getWeight()); new2Small = new Edge(newNode, smallerClus.getRenderedNode(), smallerClus.getWeight()); parent.addOutEdge(parent2New); newNode.addInEdge(parent2New); newNode.addOutEdge(new2Small); smallerClus.getRenderedNode().addInEdge(new2Small); newNode.addOutEdge(new2Big); biggerClus.getRenderedNode().addInEdge(new2Big); newNode.setRoutingParent(parent); smallerClus.getRenderedNode().setRoutingParent(newNode); biggerClus.getRenderedNode().setRoutingParent(newNode); //System.out.println("processLeafCluster: " + newNode); } /** * Constructs part of the layout tree in the case when we have a node that * is connected to a cluster that has a node and smaller cluster. * @param parent the parent node of this cluster * @param parentCluster the enclosing cluster * @param leafCluster the sibling cluster of clus * @param clus the subcluster we are drawing to * @return a new node */ private Node processMixedCluster(Node parent, Cluster parentCluster, Cluster leafCluster, Cluster clus) { //System.out.println("MixedCluster Process: " + parent); Point2D parentPt = parent.getLocation(); GraphicsGems.checkNaN(parentPt); //System.out.println("MixedCluster leafCluster " + leafCluster); Point2D onePt = leafCluster.getRenderedNode().getLocation(); GraphicsGems.checkNaN(onePt); //twoPoint is a point between the parentPoint and the centerPoint Line2D parent2Center = new Line2D.Double(parentPt, clus.center); //System.out.println("MixedCluster parent2Center " + parentPt + " to " + clus.center); //System.out.println("MixedCluster: clus: " + clus + " " + clus.bounds); Point2D twoPt = GraphicsGems.closestIntersectBox(clus.bounds, parent2Center, parentPt); GraphicsGems.checkNaN(twoPt); //System.out.println("onePt " + onePt + " twoPt: " + twoPt); // figure out which leaf node is closer double oneDist, twoDist, closerDist; oneDist = parentPt.distance(onePt); twoDist = parentPt.distance(twoPt); if (oneDist < twoDist) { closerDist = oneDist; } else { closerDist = twoDist; } closerDist /= 2; // figure out which sub-cluster is bigger Point2D biggerPt; Cluster biggerClus, smallerClus; // find the distance between the parent and the node with more weight if (leafCluster.getWeight() > clus.getWeight()) { biggerPt = onePt; biggerClus = leafCluster; smallerClus = clus; } else { biggerPt = twoPt; biggerClus = clus.twoCluster; smallerClus = clus.oneCluster; } double biggerDist = parentPt.distance(biggerPt); // create the new, intermediate node //newNode.setChildCluster(clus); //clus.setRenderedNode(newNode); // if closerDist is small, bigFraction is 0, so we want // the parentFraction to be big. double bigFraction, parentFraction; if ((closerDist == 0) && (biggerDist == 0)) { System.out.println("Returning parent! " + parent + " with clus " + parentCluster); return parent; } else { bigFraction = closerDist/biggerDist; parentFraction = 1 - bigFraction; // set the newPt location to be newFraction of the way // between parentPt and biggerPt double x = parentPt.getX()*parentFraction+biggerPt.getX()*bigFraction; double y = parentPt.getY()*parentFraction+biggerPt.getY()*bigFraction; Node newNode = new Node(x, y); //System.out.println("MixedCluster1: x: " + x + " y:" + y + " "+ newNode); // update the edge information Edge parent2New = new Edge(parent, newNode, parentCluster.getWeight()); //FlowEdgeItem new2Leaf = FlowEdgeItem.getNewItem(registry,parent, leafCluster.node, leafCluster.weight, null); // dphan. this is a weird error. Shouldn't this be from newNode to Leaf? we have parent to leaf here) Edge new2Leaf = new Edge(newNode, leafCluster.getRenderedNode(), leafCluster.getWeight()); //System.out.println("MixedCluster wants to add: p2n " + parent2New); //System.out.println("MixedCluster wants to add: n2L " + new2Leaf); parent.addOutEdge(parent2New); newNode.addInEdge(parent2New); newNode.addOutEdge(new2Leaf); leafCluster.getRenderedNode().addInEdge(new2Leaf); newNode.setRoutingParent(parent); leafCluster.getRenderedNode().setRoutingParent(newNode); //System.out.println("MixedCluster2: " + newNode); return newNode; } } private Node processTwoCluster(Node parent, Cluster parentCluster, Cluster oneCluster, Cluster twoCluster) { //System.out.println("TwoCluster Process: " + parent); //System.out.println(" oneCluster: " + oneCluster + " twoCluster " + twoCluster); Point2D parentPt = parent.getLocation(); GraphicsGems.checkNaN(parentPt); // twoPoint is a point between the parentPoint and the centerPoint Line2D parent2OneCenter = new Line2D.Double(parentPt, oneCluster.center); Point2D onePt = GraphicsGems.closestIntersectBox(oneCluster.bounds, parent2OneCenter, parentPt); //System.out.println("Multiroot_clusterlayout " + oneCluster.bounds + " " + parent2OneCenter + " " + parentPt); GraphicsGems.checkNaN(onePt); // twoPoint is a point between the parentPoint and the centerPoint Line2D parent2TwoCenter = new Line2D.Double(parentPt, twoCluster.center); Point2D twoPt = GraphicsGems.closestIntersectBox(twoCluster.bounds, parent2TwoCenter, parentPt); GraphicsGems.checkNaN(twoPt); //System.out.println("onePt: " + onePt + " twoPt: " + twoPt); // figure out which leaf node is closer double oneDist, twoDist, closerDist; oneDist = parentPt.distance(onePt); twoDist = parentPt.distance(twoPt); if (oneDist < twoDist) { closerDist = oneDist; } else { closerDist = twoDist; } closerDist /= 2; // figure out which sub-cluster is bigger Point2D biggerPt; Cluster biggerClus, smallerClus; // find the distance between the parent and the node with more weight if (oneCluster.getWeight() > twoCluster.getWeight()) { biggerPt = onePt; biggerClus = oneCluster; smallerClus = twoCluster; } else { biggerPt = twoPt; biggerClus = twoCluster; smallerClus = oneCluster; } double biggerDist = parentPt.distance(biggerPt); //System.out.println("parentPt " + parentPt + " biggerPoint: " + biggerPt); // create the new, intermediate node //newNode.setChildCluster(biggerClus); //biggerClus.setRenderedNode(newNode); // if closerDist is small, bigFraction is 0, so we want // the parentFraction to be big. //System.out.println("closerDist: " + closerDist + " biggerDist: " + biggerDist); double bigFraction, parentFraction; if ((closerDist == 0) && (biggerDist == 0)) { //since points are so close, just return parent //System.out.println("Returning parent! " + parent + " with clus " + parentCluster); return parent; } else { bigFraction = closerDist/biggerDist; parentFraction = 1 - bigFraction; //System.out.println("bigFraction: " + bigFraction + " and parentFraction: " + parentFraction); // set the newPt location to be newFraction of the way // between parentPt and biggerPt double x = parentPt.getX()*parentFraction+biggerPt.getX()*bigFraction; double y = parentPt.getY()*parentFraction+biggerPt.getY()*bigFraction; Node newNode = new Node(x, y); // update the edge information Edge parent2New = new Edge(parent, newNode, parentCluster.getWeight()); //System.out.println(parent2New); parent.addOutEdge(parent2New); newNode.addInEdge(parent2New); newNode.setRoutingParent(parent); //System.out.println("TwoCluster: " + newNode); return newNode; } } protected class Node2Cluster { private HashMap<Node, LinkedList<Cluster>> map; public Node2Cluster() { map = new HashMap<Node, LinkedList<Cluster>>(); } public LinkedList<Cluster> get(Node key) { LinkedList<Cluster> c = (LinkedList<Cluster>) map.get(key); return c; } public void put(Node key, Cluster clus) { LinkedList<Cluster> coll = get(key); if (coll == null) { coll = new LinkedList<Cluster>(); map.put(key, coll); } coll.add(clus); } public void put(Node key, Collection<Cluster> manyClus) { LinkedList<Cluster> coll = get(key); if (coll == null) { coll = new LinkedList<Cluster>(); map.put(key, coll); } coll.addAll(manyClus); } public void remove(Node key) { map.remove(key); } } }