| Line | Hits | Source |
|---|---|---|
| 1 | /* | |
| 2 | * Copyright (c) 2003, the JUNG Project and the Regents of the University of | |
| 3 | * California All rights reserved. | |
| 4 | * | |
| 5 | * This software is open-source under the BSD license; see either "license.txt" | |
| 6 | * or http://jung.sourceforge.net/license.txt for a description. | |
| 7 | */ | |
| 8 | package edu.uci.ics.jung.visualization; | |
| 9 | ||
| 10 | import java.awt.geom.Point2D; | |
| 11 | import java.util.ConcurrentModificationException; | |
| 12 | import java.util.Iterator; | |
| 13 | ||
| 14 | import cern.colt.matrix.DoubleMatrix1D; | |
| 15 | import cern.colt.matrix.impl.DenseDoubleMatrix1D; | |
| 16 | import edu.uci.ics.jung.exceptions.FatalException; | |
| 17 | import edu.uci.ics.jung.graph.Edge; | |
| 18 | import edu.uci.ics.jung.graph.Graph; | |
| 19 | import edu.uci.ics.jung.graph.Vertex; | |
| 20 | import edu.uci.ics.jung.utils.Pair; | |
| 21 | import edu.uci.ics.jung.utils.UserData; | |
| 22 | ||
| 23 | /** | |
| 24 | * Implements the Fruchterman-Reingold algorithm for node layout. | |
| 25 | * | |
| 26 | * @author Scott White, Yan-Biao Boey, Danyel Fisher | |
| 27 | */ | |
| 28 | public class FRLayout extends AbstractLayout implements LayoutMutable { | |
| 29 | ||
| 30 | 1 | private static final Object FR_KEY = "edu.uci.ics.jung.FR_Visualization_Key"; |
| 31 | ||
| 32 | private double forceConstant; | |
| 33 | ||
| 34 | private double temperature; | |
| 35 | ||
| 36 | private int currentIteration; | |
| 37 | ||
| 38 | 9 | private String status = null; |
| 39 | ||
| 40 | 9 | private int mMaxIterations = 700; |
| 41 | ||
| 42 | public FRLayout(Graph g) { | |
| 43 | 9 | super(g); |
| 44 | 9 | } |
| 45 | ||
| 46 | 9 | private double attraction_multiplier = 0.75; |
| 47 | ||
| 48 | private double attraction_constant; | |
| 49 | ||
| 50 | 9 | private double repulsion_multiplier = 0.75; |
| 51 | ||
| 52 | private double repulsion_constant; | |
| 53 | ||
| 54 | public void setAttractionMultiplier(double attraction) | |
| 55 | { | |
| 56 | 0 | this.attraction_multiplier = attraction; |
| 57 | 0 | } |
| 58 | ||
| 59 | public void setRepulsionMultiplier(double repulsion) | |
| 60 | { | |
| 61 | 0 | this.repulsion_multiplier = repulsion; |
| 62 | 0 | } |
| 63 | ||
| 64 | /* | |
| 65 | * new function for handling updates and changes to the graph | |
| 66 | */ | |
| 67 | public synchronized void update() { | |
| 68 | try { | |
| 69 | 0 | for (Iterator iter = getGraph().getVertices().iterator(); iter |
| 70 | 0 | .hasNext();) { |
| 71 | 0 | Vertex v = (Vertex) iter.next(); |
| 72 | 0 | Coordinates coord = getCoordinates(v); |
| 73 | // Coordinates coord = (Coordinates) v.getUserDatum(getBaseKey()); | |
| 74 | 0 | if (coord == null) { |
| 75 | 0 | coord = new Coordinates(); |
| 76 | 0 | v.addUserDatum(getBaseKey(), coord, UserData.REMOVE); |
| 77 | 0 | initializeLocation(v, coord, getCurrentSize()); |
| 78 | 0 | initialize_local_vertex(v); |
| 79 | } | |
| 80 | ||
| 81 | } | |
| 82 | 0 | } catch(ConcurrentModificationException cme) { |
| 83 | 0 | update(); |
| 84 | 0 | } |
| 85 | 0 | initialize_local(); |
| 86 | 0 | } |
| 87 | ||
| 88 | /** | |
| 89 | * Returns the current temperature and number of iterations elapsed, as a | |
| 90 | * string. | |
| 91 | */ | |
| 92 | public String getStatus() { | |
| 93 | 2 | return status; |
| 94 | } | |
| 95 | ||
| 96 | public void forceMove(Vertex picked,double x, double y) { | |
| 97 | 50 | super.forceMove(picked, x, y); |
| 98 | 50 | } |
| 99 | ||
| 100 | protected void initialize_local() { | |
| 101 | 10 | currentIteration = 0; |
| 102 | 10 | temperature = getCurrentSize().getWidth() / 10; |
| 103 | ||
| 104 | 10 | forceConstant = |
| 105 | Math | |
| 106 | .sqrt(getCurrentSize().getHeight() | |
| 107 | * getCurrentSize().getWidth() | |
| 108 | / getVisibleGraph().numVertices()); | |
| 109 | // / Math.max(getVisibleGraph().numEdges(), getVisibleGraph().numVertices())); | |
| 110 | ||
| 111 | 10 | attraction_constant = attraction_multiplier * forceConstant; |
| 112 | 10 | repulsion_constant = repulsion_multiplier * forceConstant; |
| 113 | ||
| 114 | // forceConstant = 0.75 * Math | |
| 115 | // .sqrt(getCurrentSize().getHeight() | |
| 116 | // * getCurrentSize().getWidth() | |
| 117 | // / getVisibleGraph().numVertices()); | |
| 118 | 10 | } |
| 119 | ||
| 120 | 9 | private Object key = null; |
| 121 | ||
| 122 | 9 | private double EPSILON = 0.000001D; |
| 123 | ||
| 124 | /** | |
| 125 | * Returns a visualization-specific key (that is, specific both to this | |
| 126 | * instance and <tt>AbstractLayout</tt>) that can be used to access | |
| 127 | * UserData related to the <tt>AbstractLayout</tt>. | |
| 128 | */ | |
| 129 | public Object getKey() { | |
| 130 | 205266 | if (key == null) key = new Pair(this, FR_KEY); |
| 131 | 205266 | return key; |
| 132 | } | |
| 133 | ||
| 134 | protected void initialize_local_vertex(Vertex v) { | |
| 135 | 500 | if (v.getUserDatum(getKey()) == null) { |
| 136 | 450 | v.addUserDatum(getKey(), new FRVertexData(), UserData.REMOVE); |
| 137 | } | |
| 138 | 500 | } |
| 139 | ||
| 140 | /** | |
| 141 | * Moves the iteration forward one notch, calculation attraction and | |
| 142 | * repulsion between vertices and edges and cooling the temperature. | |
| 143 | */ | |
| 144 | public synchronized void advancePositions() { | |
| 145 | 197 | currentIteration++; |
| 146 | 197 | status = "VV: " + getVisibleVertices().size() + " IT: " |
| 147 | + currentIteration + " temp: " + temperature; | |
| 148 | /** | |
| 149 | * Calculate repulsion | |
| 150 | */ | |
| 151 | while(true) { | |
| 152 | ||
| 153 | try { | |
| 154 | 197 | for (Iterator iter = getVisibleVertices().iterator(); iter.hasNext();) { |
| 155 | 9466 | Vertex v1 = (Vertex) iter.next(); |
| 156 | 9466 | if (isLocked(v1)) continue; |
| 157 | 9466 | calcRepulsion(v1); |
| 158 | } | |
| 159 | 197 | break; |
| 160 | 0 | } catch(ConcurrentModificationException cme) {} |
| 161 | } | |
| 162 | ||
| 163 | /** | |
| 164 | * Calculate attraction | |
| 165 | */ | |
| 166 | while(true) { | |
| 167 | try { | |
| 168 | 197 | for (Iterator iter = getVisibleEdges().iterator(); iter.hasNext();) { |
| 169 | 92692 | Edge e = (Edge) iter.next(); |
| 170 | ||
| 171 | 92692 | calcAttraction(e); |
| 172 | } | |
| 173 | 197 | break; |
| 174 | 0 | } catch(ConcurrentModificationException cme) {} |
| 175 | } | |
| 176 | ||
| 177 | // double cumulativeChange = 0; | |
| 178 | ||
| 179 | while(true) { | |
| 180 | try { | |
| 181 | ||
| 182 | 197 | for (Iterator iter = getVisibleVertices().iterator(); iter.hasNext();) { |
| 183 | 9466 | Vertex v = (Vertex) iter.next(); |
| 184 | 9466 | if (isLocked(v)) continue; |
| 185 | 9466 | calcPositions(v); |
| 186 | } | |
| 187 | 197 | break; |
| 188 | 0 | } catch(ConcurrentModificationException cme) {} |
| 189 | } | |
| 190 | 197 | cool(); |
| 191 | 197 | } |
| 192 | ||
| 193 | public synchronized void calcPositions(Vertex v) { | |
| 194 | 9466 | FRVertexData fvd = getFRData(v); |
| 195 | 9466 | if(fvd == null) return; |
| 196 | 9466 | Coordinates xyd = getCoordinates(v); |
| 197 | 9466 | double deltaLength = Math.max(EPSILON, Math.sqrt(fvd.disp |
| 198 | .zDotProduct(fvd.disp))); | |
| 199 | ||
| 200 | 9466 | double newXDisp = fvd.getXDisp() / deltaLength |
| 201 | * Math.min(deltaLength, temperature); | |
| 202 | ||
| 203 | 9466 | if (Double.isNaN(newXDisp)) { throw new FatalException( |
| 204 | "Unexpected mathematical result in FRLayout:calcPositions [xdisp]"); } | |
| 205 | ||
| 206 | 9466 | double newYDisp = fvd.getYDisp() / deltaLength |
| 207 | * Math.min(deltaLength, temperature); | |
| 208 | 9466 | xyd.addX(newXDisp); |
| 209 | 9466 | xyd.addY(newYDisp); |
| 210 | ||
| 211 | 9466 | double borderWidth = getCurrentSize().getWidth() / 50.0; |
| 212 | 9466 | double newXPos = xyd.getX(); |
| 213 | 9466 | if (newXPos < borderWidth) { |
| 214 | 0 | newXPos = borderWidth + Math.random() * borderWidth * 2.0; |
| 215 | 9466 | } else if (newXPos > (getCurrentSize().getWidth() - borderWidth)) { |
| 216 | 0 | newXPos = getCurrentSize().getWidth() - borderWidth - Math.random() |
| 217 | * borderWidth * 2.0; | |
| 218 | } | |
| 219 | //double newXPos = Math.min(getCurrentSize().getWidth() - 20.0, | |
| 220 | // Math.max(20.0, xyd.getX())); | |
| 221 | ||
| 222 | 9466 | double newYPos = xyd.getY(); |
| 223 | 9466 | if (newYPos < borderWidth) { |
| 224 | 0 | newYPos = borderWidth + Math.random() * borderWidth * 2.0; |
| 225 | 9466 | } else if (newYPos > (getCurrentSize().getHeight() - borderWidth)) { |
| 226 | 0 | newYPos = getCurrentSize().getHeight() - borderWidth |
| 227 | - Math.random() * borderWidth * 2.0; | |
| 228 | } | |
| 229 | //double newYPos = Math.min(getCurrentSize().getHeight() - 20.0, | |
| 230 | // Math.max(20.0, xyd.getY())); | |
| 231 | ||
| 232 | 9466 | xyd.setX(newXPos); |
| 233 | 9466 | xyd.setY(newYPos); |
| 234 | 9466 | } |
| 235 | ||
| 236 | public void calcAttraction(Edge e) { | |
| 237 | 92692 | Vertex v1 = (Vertex) e.getIncidentVertices().iterator().next(); |
| 238 | 92692 | Vertex v2 = e.getOpposite(v1); |
| 239 | ||
| 240 | 92692 | Point2D p1 = getLocation(v1); |
| 241 | 92692 | Point2D p2 = getLocation(v2); |
| 242 | 92692 | if(p1 == null || p2 == null) return; |
| 243 | 92692 | double xDelta = p1.getX() - p2.getX(); |
| 244 | 92692 | double yDelta = p1.getY() - p2.getY(); |
| 245 | ||
| 246 | 92692 | double deltaLength = Math.max(EPSILON, Math.sqrt((xDelta * xDelta) |
| 247 | + (yDelta * yDelta))); | |
| 248 | ||
| 249 | // double force = (deltaLength * deltaLength) / forceConstant; | |
| 250 | ||
| 251 | 92692 | double force = (deltaLength * deltaLength) / attraction_constant; |
| 252 | ||
| 253 | 92692 | if (Double.isNaN(force)) { throw new FatalException( |
| 254 | "Unexpected mathematical result in FRLayout:calcPositions [force]"); } | |
| 255 | ||
| 256 | 92692 | FRVertexData fvd1 = getFRData(v1); |
| 257 | 92692 | FRVertexData fvd2 = getFRData(v2); |
| 258 | ||
| 259 | 92692 | fvd1.decrementDisp((xDelta / deltaLength) * force, |
| 260 | (yDelta / deltaLength) * force); | |
| 261 | 92692 | fvd2.incrementDisp((xDelta / deltaLength) * force, |
| 262 | (yDelta / deltaLength) * force); | |
| 263 | 92692 | } |
| 264 | ||
| 265 | public void calcRepulsion(Vertex v1) { | |
| 266 | 9466 | FRVertexData fvd1 = getFRData(v1); |
| 267 | 9466 | if(fvd1 == null) return; |
| 268 | 9466 | fvd1.setDisp(0, 0); |
| 269 | ||
| 270 | try { | |
| 271 | 9466 | for (Iterator iter2 = getVisibleVertices().iterator(); iter2.hasNext();) { |
| 272 | 463316 | Vertex v2 = (Vertex) iter2.next(); |
| 273 | 463316 | if (isLocked(v2)) continue; |
| 274 | 463316 | if (v1 != v2) { |
| 275 | 453850 | Point2D p1 = getLocation(v1); |
| 276 | 453850 | Point2D p2 = getLocation(v2); |
| 277 | 453850 | if(p1 == null || p2 == null) continue; |
| 278 | 453850 | double xDelta = p1.getX() - p2.getX(); |
| 279 | 453850 | double yDelta = p1.getY() - p2.getY(); |
| 280 | ||
| 281 | 453850 | double deltaLength = Math.max(EPSILON, Math |
| 282 | .sqrt((xDelta * xDelta) + (yDelta * yDelta))); | |
| 283 | ||
| 284 | // double force = (forceConstant * forceConstant) / deltaLength; | |
| 285 | 453850 | double force = (repulsion_constant * repulsion_constant) / deltaLength; |
| 286 | ||
| 287 | 453850 | if (Double.isNaN(force)) { throw new FatalException( |
| 288 | "Unexpected mathematical result in FRLayout:calcPositions [repulsion]"); } | |
| 289 | ||
| 290 | 453850 | fvd1.incrementDisp((xDelta / deltaLength) * force, |
| 291 | (yDelta / deltaLength) * force); | |
| 292 | } | |
| 293 | } | |
| 294 | 0 | } catch(ConcurrentModificationException cme) { |
| 295 | 0 | calcRepulsion(v1); |
| 296 | 9466 | } |
| 297 | 9466 | } |
| 298 | ||
| 299 | private void cool() { | |
| 300 | 197 | temperature *= (1.0 - currentIteration / (double) mMaxIterations); |
| 301 | 197 | } |
| 302 | ||
| 303 | public void setMaxIterations(int maxIterations) { | |
| 304 | 0 | mMaxIterations = maxIterations; |
| 305 | 0 | } |
| 306 | ||
| 307 | public FRVertexData getFRData(Vertex v) { | |
| 308 | 204316 | return (FRVertexData) (v.getUserDatum(getKey())); |
| 309 | } | |
| 310 | ||
| 311 | /** | |
| 312 | * This one is an incremental visualization. | |
| 313 | */ | |
| 314 | public boolean isIncremental() { | |
| 315 | 2 | return true; |
| 316 | } | |
| 317 | ||
| 318 | /** | |
| 319 | * Returns true once the current iteration has passed the maximum count, | |
| 320 | * <tt>MAX_ITERATIONS</tt>. | |
| 321 | */ | |
| 322 | public boolean incrementsAreDone() { | |
| 323 | 204 | if (currentIteration > mMaxIterations) { |
| 324 | // System.out.println("Reached currentIteration =" + currentIteration + ", maxIterations=" + mMaxIterations); | |
| 325 | 0 | return true; |
| 326 | } | |
| 327 | 204 | return false; |
| 328 | } | |
| 329 | ||
| 330 | public static class FRVertexData { | |
| 331 | ||
| 332 | private DoubleMatrix1D disp; | |
| 333 | ||
| 334 | public FRVertexData() { | |
| 335 | initialize(); | |
| 336 | } | |
| 337 | ||
| 338 | public void initialize() { | |
| 339 | disp = new DenseDoubleMatrix1D(2); | |
| 340 | } | |
| 341 | ||
| 342 | public double getXDisp() { | |
| 343 | return disp.get(0); | |
| 344 | } | |
| 345 | ||
| 346 | public double getYDisp() { | |
| 347 | return disp.get(1); | |
| 348 | } | |
| 349 | ||
| 350 | public void setDisp(double x, double y) { | |
| 351 | disp.set(0, x); | |
| 352 | disp.set(1, y); | |
| 353 | } | |
| 354 | ||
| 355 | public void incrementDisp(double x, double y) { | |
| 356 | disp.set(0, disp.get(0) + x); | |
| 357 | disp.set(1, disp.get(1) + y); | |
| 358 | } | |
| 359 | ||
| 360 | public void decrementDisp(double x, double y) { | |
| 361 | disp.set(0, disp.get(0) - x); | |
| 362 | disp.set(1, disp.get(1) - y); | |
| 363 | } | |
| 364 | } | |
| 365 | } |
|
this report was generated by version 1.0.5 of jcoverage. |
copyright © 2003, jcoverage ltd. all rights reserved. |