1 package prefuse.action.layout.graph; 2 3 import java.awt.geom.Rectangle2D ; 4 import java.util.Iterator ; 5 import java.util.Random ; 6 7 import prefuse.action.layout.Layout; 8 import prefuse.data.Graph; 9 import prefuse.data.Schema; 10 import prefuse.data.tuple.TupleSet; 11 import prefuse.util.PrefuseLib; 12 import prefuse.visual.EdgeItem; 13 import prefuse.visual.NodeItem; 14 import prefuse.visual.VisualItem; 15 16 17 30 public class FruchtermanReingoldLayout extends Layout { 31 32 private double forceConstant; 33 private double temp; 34 private int maxIter = 700; 35 36 protected String m_nodeGroup; 37 protected String m_edgeGroup; 38 protected int m_fidx; 39 40 private static final double EPSILON = 0.000001D; 41 private static final double ALPHA = 0.1; 42 43 47 public FruchtermanReingoldLayout(String graph) { 48 this(graph, 700); 49 } 50 51 56 public FruchtermanReingoldLayout(String graph, int maxIter) { 57 super(graph); 58 m_nodeGroup = PrefuseLib.getGroupName(graph, Graph.NODES); 59 m_edgeGroup = PrefuseLib.getGroupName(graph, Graph.EDGES); 60 this.maxIter = maxIter; 61 } 62 63 67 public int getMaxIterations() { 68 return maxIter; 69 } 70 71 75 public void setMaxIterations(int maxIter) { 76 this.maxIter = maxIter; 77 } 78 79 82 public void run(double frac) { 83 Graph g = (Graph)m_vis.getGroup(m_group); 84 Rectangle2D bounds = super.getLayoutBounds(); 85 init(g, bounds); 86 87 for (int curIter=0; curIter < maxIter; curIter++ ) { 88 89 for (Iterator iter = g.nodes(); iter.hasNext();) { 91 NodeItem n = (NodeItem)iter.next(); 92 if (n.isFixed()) continue; 93 calcRepulsion(g, n); 94 } 95 96 for (Iterator iter = g.edges(); iter.hasNext();) { 98 EdgeItem e = (EdgeItem) iter.next(); 99 calcAttraction(e); 100 } 101 102 for (Iterator iter = g.nodes(); iter.hasNext();) { 103 NodeItem n = (NodeItem)iter.next(); 104 if (n.isFixed()) continue; 105 calcPositions(n,bounds); 106 } 107 108 cool(curIter); 109 } 110 111 finish(g); 112 } 113 114 private void init(Graph g, Rectangle2D b) { 115 initSchema(g.getNodes()); 116 117 temp = b.getWidth() / 10; 118 forceConstant = 0.75 * 119 Math.sqrt(b.getHeight()*b.getWidth()/g.getNodeCount()); 120 121 Iterator nodeIter = g.nodes(); 123 Random rand = new Random (42); double scaleW = ALPHA*b.getWidth()/2; 125 double scaleH = ALPHA*b.getHeight()/2; 126 while ( nodeIter.hasNext() ) { 127 NodeItem n = (NodeItem)nodeIter.next(); 128 Params np = getParams(n); 129 np.loc[0] = b.getCenterX() + rand.nextDouble()*scaleW; 130 np.loc[1] = b.getCenterY() + rand.nextDouble()*scaleH; 131 } 132 } 133 134 private void finish(Graph g) { 135 Iterator nodeIter = g.nodes(); 136 while ( nodeIter.hasNext() ) { 137 NodeItem n = (NodeItem)nodeIter.next(); 138 Params np = getParams(n); 139 setX(n, null, np.loc[0]); 140 setY(n, null, np.loc[1]); 141 } 142 } 143 144 public void calcPositions(NodeItem n, Rectangle2D b) { 145 Params np = getParams(n); 146 double deltaLength = Math.max(EPSILON, 147 Math.sqrt(np.disp[0]*np.disp[0] + np.disp[1]*np.disp[1])); 148 149 double xDisp = np.disp[0]/deltaLength * Math.min(deltaLength, temp); 150 151 if (Double.isNaN(xDisp)) { 152 System.err.println("Mathematical error... (calcPositions:xDisp)"); 153 } 154 155 double yDisp = np.disp[1]/deltaLength * Math.min(deltaLength, temp); 156 157 np.loc[0] += xDisp; 158 np.loc[1] += yDisp; 159 160 double borderWidth = b.getWidth() / 50.0; 162 double x = np.loc[0]; 163 if (x < b.getMinX() + borderWidth) { 164 x = b.getMinX() + borderWidth + Math.random() * borderWidth * 2.0; 165 } else if (x > (b.getMaxX() - borderWidth)) { 166 x = b.getMaxX() - borderWidth - Math.random() * borderWidth * 2.0; 167 } 168 169 double y = np.loc[1]; 170 if (y < b.getMinY() + borderWidth) { 171 y = b.getMinY() + borderWidth + Math.random() * borderWidth * 2.0; 172 } else if (y > (b.getMaxY() - borderWidth)) { 173 y = b.getMaxY() - borderWidth - Math.random() * borderWidth * 2.0; 174 } 175 176 np.loc[0] = x; 177 np.loc[1] = y; 178 } 179 180 public void calcAttraction(EdgeItem e) { 181 NodeItem n1 = e.getSourceItem(); 182 Params n1p = getParams(n1); 183 NodeItem n2 = e.getTargetItem(); 184 Params n2p = getParams(n2); 185 186 double xDelta = n1p.loc[0] - n2p.loc[0]; 187 double yDelta = n1p.loc[1] - n2p.loc[1]; 188 189 double deltaLength = Math.max(EPSILON, 190 Math.sqrt(xDelta*xDelta + yDelta*yDelta)); 191 double force = (deltaLength*deltaLength) / forceConstant; 192 193 if (Double.isNaN(force)) { 194 System.err.println("Mathematical error..."); 195 } 196 197 double xDisp = (xDelta/deltaLength) * force; 198 double yDisp = (yDelta/deltaLength) * force; 199 200 n1p.disp[0] -= xDisp; n1p.disp[1] -= yDisp; 201 n2p.disp[0] += xDisp; n2p.disp[1] += yDisp; 202 } 203 204 public void calcRepulsion(Graph g, NodeItem n1) { 205 Params np = getParams(n1); 206 np.disp[0] = 0.0; np.disp[1] = 0.0; 207 208 for (Iterator iter2 = g.nodes(); iter2.hasNext();) { 209 NodeItem n2 = (NodeItem) iter2.next(); 210 Params n2p = getParams(n2); 211 if (n2.isFixed()) continue; 212 if (n1 != n2) { 213 double xDelta = np.loc[0] - n2p.loc[0]; 214 double yDelta = np.loc[1] - n2p.loc[1]; 215 216 double deltaLength = Math.max(EPSILON, 217 Math.sqrt(xDelta*xDelta + yDelta*yDelta)); 218 219 double force = (forceConstant*forceConstant) / deltaLength; 220 221 if (Double.isNaN(force)) { 222 System.err.println("Mathematical error..."); 223 } 224 225 np.disp[0] += (xDelta/deltaLength)*force; 226 np.disp[1] += (yDelta/deltaLength)*force; 227 } 228 } 229 } 230 231 private void cool(int curIter) { 232 temp *= (1.0 - curIter / (double) maxIter); 233 } 234 235 238 241 public static final String PARAMS = "_fruchtermanReingoldParams"; 242 245 public static final Schema PARAMS_SCHEMA = new Schema(); 246 static { 247 PARAMS_SCHEMA.addColumn(PARAMS, Params.class); 248 } 249 250 protected void initSchema(TupleSet ts) { 251 try { 252 ts.addColumns(PARAMS_SCHEMA); 253 } catch ( IllegalArgumentException iae ) {}; 254 } 255 256 private Params getParams(VisualItem item) { 257 Params rp = (Params)item.get(PARAMS); 258 if ( rp == null ) { 259 rp = new Params(); 260 item.set(PARAMS, rp); 261 } 262 return rp; 263 } 264 265 268 public static class Params implements Cloneable { 269 double[] loc = new double[2]; 270 double[] disp = new double[2]; 271 } 272 273 } | Popular Tags |