1 package prefuse.action.layout.graph; 2 3 import java.awt.geom.Point2D ; 4 import java.awt.geom.Rectangle2D ; 5 import java.util.Iterator ; 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.util.force.DragForce; 13 import prefuse.util.force.ForceItem; 14 import prefuse.util.force.ForceSimulator; 15 import prefuse.util.force.NBodyForce; 16 import prefuse.util.force.SpringForce; 17 import prefuse.visual.EdgeItem; 18 import prefuse.visual.NodeItem; 19 import prefuse.visual.VisualItem; 20 21 22 45 public class ForceDirectedLayout extends Layout { 46 47 private ForceSimulator m_fsim; 48 private long m_lasttime = -1L; 49 private long m_maxstep = 50L; 50 private boolean m_runonce; 51 private int m_iterations = 100; 52 private boolean m_enforceBounds; 53 54 protected transient VisualItem referrer; 55 56 protected String m_nodeGroup; 57 protected String m_edgeGroup; 58 59 65 public ForceDirectedLayout(String graph) 66 { 67 this(graph, false, false); 68 } 69 70 77 public ForceDirectedLayout(String group, boolean enforceBounds) 78 { 79 this(group, enforceBounds, false); 80 } 81 82 92 public ForceDirectedLayout(String group, 93 boolean enforceBounds, boolean runonce) 94 { 95 super(group); 96 m_nodeGroup = PrefuseLib.getGroupName(group, Graph.NODES); 97 m_edgeGroup = PrefuseLib.getGroupName(group, Graph.EDGES); 98 99 m_enforceBounds = enforceBounds; 100 m_runonce = runonce; 101 m_fsim = new ForceSimulator(); 102 m_fsim.addForce(new NBodyForce()); 103 m_fsim.addForce(new SpringForce()); 104 m_fsim.addForce(new DragForce()); 105 } 106 107 115 public ForceDirectedLayout(String group, 116 ForceSimulator fsim, boolean enforceBounds) { 117 this(group, fsim, enforceBounds, false); 118 } 119 120 131 public ForceDirectedLayout(String group, ForceSimulator fsim, 132 boolean enforceBounds, boolean runonce) 133 { 134 super(group); 135 m_nodeGroup = PrefuseLib.getGroupName(group, Graph.NODES); 136 m_edgeGroup = PrefuseLib.getGroupName(group, Graph.EDGES); 137 138 m_enforceBounds = enforceBounds; 139 m_runonce = runonce; 140 m_fsim = fsim; 141 } 142 143 145 153 public long getMaxTimeStep() { 154 return m_maxstep; 155 } 156 157 165 public void setMaxTimeStep(long maxstep) { 166 this.m_maxstep = maxstep; 167 } 168 169 173 public ForceSimulator getForceSimulator() { 174 return m_fsim; 175 } 176 177 181 public void setForceSimulator(ForceSimulator fsim) { 182 m_fsim = fsim; 183 } 184 185 190 public int getIterations() { 191 return m_iterations; 192 } 193 194 199 public void setIterations(int iter) { 200 if ( iter < 1 ) 201 throw new IllegalArgumentException ( 202 "Iterations must be a positive number!"); 203 m_iterations = iter; 204 } 205 206 212 public void setDataGroups(String nodeGroup, String edgeGroup) { 213 m_nodeGroup = nodeGroup; 214 m_edgeGroup = edgeGroup; 215 } 216 217 219 222 public void run(double frac) { 223 if ( m_runonce ) { 226 Point2D anchor = getLayoutAnchor(); 227 Iterator iter = m_vis.visibleItems(m_nodeGroup); 228 while ( iter.hasNext() ) { 229 VisualItem item = (NodeItem)iter.next(); 230 item.setX(anchor.getX()); 231 item.setY(anchor.getY()); 232 } 233 m_fsim.clear(); 234 long timestep = 1000L; 235 initSimulator(m_fsim); 236 for ( int i = 0; i < m_iterations; i++ ) { 237 timestep *= (1.0 - i/(double)m_iterations); 239 long step = timestep+50; 240 m_fsim.runSimulator(step); 242 } 247 updateNodePositions(); 248 } else { 249 if ( m_lasttime == -1 ) 251 m_lasttime = System.currentTimeMillis()-20; 252 long time = System.currentTimeMillis(); 253 long timestep = Math.min(m_maxstep, time - m_lasttime); 254 m_lasttime = time; 255 256 m_fsim.clear(); 258 initSimulator(m_fsim); 259 m_fsim.runSimulator(timestep); 260 updateNodePositions(); 261 } 262 if ( frac == 1.0 ) { 263 reset(); 264 } 265 } 266 267 private void updateNodePositions() { 268 Rectangle2D bounds = getLayoutBounds(); 269 double x1=0, x2=0, y1=0, y2=0; 270 if ( bounds != null ) { 271 x1 = bounds.getMinX(); y1 = bounds.getMinY(); 272 x2 = bounds.getMaxX(); y2 = bounds.getMaxY(); 273 } 274 275 Iterator iter = m_vis.visibleItems(m_nodeGroup); 277 while ( iter.hasNext() ) { 278 VisualItem item = (VisualItem)iter.next(); 279 ForceItem fitem = (ForceItem)item.get(FORCEITEM); 280 281 if ( item.isFixed() ) { 282 fitem.force[0] = 0.0f; 284 fitem.force[1] = 0.0f; 285 fitem.velocity[0] = 0.0f; 286 fitem.velocity[1] = 0.0f; 287 288 if ( Double.isNaN(item.getX()) ) { 289 setX(item, referrer, 0.0); 290 setY(item, referrer, 0.0); 291 } 292 continue; 293 } 294 295 double x = fitem.location[0]; 296 double y = fitem.location[1]; 297 298 if ( m_enforceBounds && bounds != null) { 299 Rectangle2D b = item.getBounds(); 300 double hw = b.getWidth()/2; 301 double hh = b.getHeight()/2; 302 if ( x+hw > x2 ) x = x2-hw; 303 if ( x-hw < x1 ) x = x1+hw; 304 if ( y+hh > y2 ) y = y2-hh; 305 if ( y-hh < y1 ) y = y1+hh; 306 } 307 308 setX(item, referrer, x); 310 setY(item, referrer, y); 311 } 312 } 313 314 318 public void reset() { 319 Iterator iter = m_vis.visibleItems(m_nodeGroup); 320 while ( iter.hasNext() ) { 321 VisualItem item = (VisualItem)iter.next(); 322 ForceItem fitem = (ForceItem)item.get(FORCEITEM); 323 if ( fitem != null ) { 324 fitem.location[0] = (float)item.getEndX(); 325 fitem.location[1] = (float)item.getEndY(); 326 fitem.force[0] = fitem.force[1] = 0; 327 fitem.velocity[0] = fitem.velocity[1] = 0; 328 } 329 } 330 m_lasttime = -1L; 331 } 332 333 337 protected void initSimulator(ForceSimulator fsim) { 338 TupleSet ts = m_vis.getGroup(m_nodeGroup); 340 if ( ts == null ) return; 341 try { 342 ts.addColumns(FORCEITEM_SCHEMA); 343 } catch ( IllegalArgumentException iae ) { } 344 345 float startX = (referrer == null ? 0f : (float)referrer.getX()); 346 float startY = (referrer == null ? 0f : (float)referrer.getY()); 347 startX = Float.isNaN(startX) ? 0f : startX; 348 startY = Float.isNaN(startY) ? 0f : startY; 349 350 Iterator iter = m_vis.visibleItems(m_nodeGroup); 351 while ( iter.hasNext() ) { 352 VisualItem item = (VisualItem)iter.next(); 353 ForceItem fitem = (ForceItem)item.get(FORCEITEM); 354 fitem.mass = getMassValue(item); 355 double x = item.getEndX(); 356 double y = item.getEndY(); 357 fitem.location[0] = (Double.isNaN(x) ? startX : (float)x); 358 fitem.location[1] = (Double.isNaN(y) ? startY : (float)y); 359 fsim.addItem(fitem); 360 } 361 if ( m_edgeGroup != null ) { 362 iter = m_vis.visibleItems(m_edgeGroup); 363 while ( iter.hasNext() ) { 364 EdgeItem e = (EdgeItem)iter.next(); 365 NodeItem n1 = e.getSourceItem(); 366 ForceItem f1 = (ForceItem)n1.get(FORCEITEM); 367 NodeItem n2 = e.getTargetItem(); 368 ForceItem f2 = (ForceItem)n2.get(FORCEITEM); 369 float coeff = getSpringCoefficient(e); 370 float slen = getSpringLength(e); 371 fsim.addSpring(f1, f2, (coeff>=0?coeff:-1.f), (slen>=0?slen:-1.f)); 372 } 373 } 374 } 375 376 383 protected float getMassValue(VisualItem n) { 384 return 1.0f; 385 } 386 387 394 protected float getSpringLength(EdgeItem e) { 395 return -1.f; 396 } 397 398 406 protected float getSpringCoefficient(EdgeItem e) { 407 return -1.f; 408 } 409 410 417 public VisualItem getReferrer() { 418 return referrer; 419 } 420 421 428 public void setReferrer(VisualItem referrer) { 429 this.referrer = referrer; 430 } 431 432 435 438 public static final String FORCEITEM = "_forceItem"; 439 442 public static final Schema FORCEITEM_SCHEMA = new Schema(); 443 static { 444 FORCEITEM_SCHEMA.addColumn(FORCEITEM, 445 ForceItem.class, 446 new ForceItem()); 447 } 448 449 } | Popular Tags |