package graph.layout; import graph.*; import graph.editor.*; import graph.cluster.*; import graph.rep.*; import java.awt.*; import java.util.*; /** * The force-directed placement algorithm which operates * on the graph. * * This algorithm is based on code from Elan Amir * (elan@cs.berkeley.edu) which in turn was based on * Fruchterman and Reingold,"Graph Drawing by Force-Directed * Placement", Univ of Illinois Urbana-Champaign, * Report No. UIUCDS-R-90-1609. * * @author Michael Shilman (michaels@eecs.berkeley.edu) * @version $Id$ */ public class ForceLayout implements Action, Painter { public static int s_forceIndex = AttributeManager.NO_INDEX; float m_temp = .05f*100f; int m_width; int m_height; int m_border; public ForceLayout(int width, int height, int border) { m_width = width; m_height = height; m_border = border; if(s_forceIndex == AttributeManager.NO_INDEX) { s_forceIndex = AttributeManager.getIndex("Force"); } } /** * Calculate the attractive force given by * the expression (x*x/k) */ static double attract(double x, double k) { return x*x / k; } /** * Calculate the repulsive force given by * the expression (k*k/x) */ static double repel(double x, double k) { return k*k / x; } /** * Cool the temperature at which the algorithm operates. * The lowest temperature possible is .001f. */ void cool() { if(m_temp > 0.001) { m_temp *= 0.95f; } else { m_temp = 0.001f; } } public void apply(Graph g) { init(g); for(int i = 0; i < 10; i++) { step(g); } finish(g); } public static final int NUM_DUMMY = 8; /** * Place a string of dummy nodes */ public void initDummy(Graph g, Point p1, Point p2) { for(int i = 0; i < NUM_DUMMY; i++) { DummyNode d = new DummyNode(); double t = ((double)i)/((double)(NUM_DUMMY-1)); d.x = (int)((1.0f - t)*p1.x + t*p2.x); d.y = (int)((1.0f - t)*p1.y + t*p2.y); // System.out.println("Adding: " + d.x + ", " + d.y); g.add(d); } } public void init(Graph g) { //first thing, partition this puppy (new GreedyCluster()).apply(g); //add a bunch of dummy nodes to the corners/edges of //the graph int w = m_width; int h = m_height; int b = m_border; initDummy(g, new Point(b,b), new Point(w-b, b)); initDummy(g, new Point(b,b), new Point(b, h-b)); initDummy(g, new Point(w-b,b), new Point(w-b, h-b)); initDummy(g, new Point(b,h-b), new Point(w-b, h-b)); //initialize the force attributes for(Enumeration e = g.nodes.elements(); e.hasMoreElements();) { Node n = (Node)e.nextElement(); ForceAttr a = (ForceAttr)n.getAttr(s_forceIndex); if(a == null) { a = new ForceAttr(); if(n instanceof DummyNode) { a.peg = true; } n.setAttr(s_forceIndex, a); } } } public void finish(Graph g) { for(Enumeration e = g.nodes.elements(); e.hasMoreElements();) { Node n = (Node)e.nextElement(); if(n instanceof DummyNode) { g.delete(n); } } } public Hashtable m_hash = new Hashtable(); public Color m_cool = new Color(30, 30, 180); public Color m_cold = new Color(50, 50, 220); public void paint(Graphics g) { Enumeration elts = m_hash.elements(); while(elts.hasMoreElements()) { MovementTrail t = (MovementTrail)elts.nextElement(); boolean cold = (t.prevx != t.prevx2) && (t.prevy != t.prevy2); g.setColor(m_cool); g.drawLine((int)t.x, (int)t.y, (int)t.prevx, (int)t.prevy); if(cold) { g.setColor(m_cold); g.drawLine((int)t.prevx, (int)t.prevy, (int)t.prevx2, (int)t.prevy2); } g.setColor(m_cool); g.fillRect((int)t.prevx-1, (int)t.prevy-1, 2, 2); if(cold) { g.setColor(m_cold); g.fillRect((int)t.prevx2-1, (int)t.prevy2-1, 2, 2); } } } /** * Apply the layout algorithm to the contents of a graph. Cool * after application so that next time the algorithm has a lower * impact on the layout (and will eventually settle into some * local minimum).
* * The force layout has three steps: *