001/* 002 * Copyright 2006 - 2013 003 * Stefan Balev <stefan.balev@graphstream-project.org> 004 * Julien Baudry <julien.baudry@graphstream-project.org> 005 * Antoine Dutot <antoine.dutot@graphstream-project.org> 006 * Yoann Pigné <yoann.pigne@graphstream-project.org> 007 * Guilhelm Savin <guilhelm.savin@graphstream-project.org> 008 * 009 * This file is part of GraphStream <http://graphstream-project.org>. 010 * 011 * GraphStream is a library whose purpose is to handle static or dynamic 012 * graph, create them from scratch, file or any source and display them. 013 * 014 * This program is free software distributed under the terms of two licenses, the 015 * CeCILL-C license that fits European law, and the GNU Lesser General Public 016 * License. You can use, modify and/ or redistribute the software under the terms 017 * of the CeCILL-C license as circulated by CEA, CNRS and INRIA at the following 018 * URL <http://www.cecill.info> or under the terms of the GNU LGPL as published by 019 * the Free Software Foundation, either version 3 of the License, or (at your 020 * option) any later version. 021 * 022 * This program is distributed in the hope that it will be useful, but WITHOUT ANY 023 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 024 * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. 025 * 026 * You should have received a copy of the GNU Lesser General Public License 027 * along with this program. If not, see <http://www.gnu.org/licenses/>. 028 * 029 * The fact that you are presently reading this means that you have had 030 * knowledge of the CeCILL-C and LGPL licenses and that you accept their terms. 031 */ 032package org.graphstream.ui.layout.springbox; 033 034import java.io.FileNotFoundException; 035import java.io.PrintStream; 036import java.util.HashMap; 037import java.util.Locale; 038import java.util.Random; 039 040import org.graphstream.stream.Sink; 041import org.graphstream.stream.SourceBase; 042import org.graphstream.stream.sync.SinkTime; 043import org.graphstream.ui.geom.Point3; 044import org.graphstream.ui.graphicGraph.GraphPosLengthUtils; 045import org.graphstream.ui.layout.Layout; 046import org.miv.pherd.ParticleBox; 047import org.miv.pherd.ParticleBoxListener; 048import org.miv.pherd.ntree.Anchor; 049import org.miv.pherd.ntree.CellSpace; 050import org.miv.pherd.ntree.OctreeCellSpace; 051import org.miv.pherd.ntree.QuadtreeCellSpace; 052 053/** 054 * Base implementation of a Barnes-Hut space decomposition and particle 055 * interaction algorithm to be used for force-based layout algorithms. 056 * 057 * <p> 058 * This base class creates the space decomposition method and manages the 059 * main loop of the simulation. The simulation is made of {@link NodeParticle} 060 * and {@link EdgeSpring} elements that are created and linked for you in 061 * response to graph events received via the {@link Sink} interface. However 062 * you have to provide an implementation of the abstract {@link NodeParticle} class 063 * (by overriding the abstract method {@link #newNodeParticle(String)}). 064 * </p> 065 * 066 * <p> 067 * As almost all the repulsion/attraction forces computation is done in the 068 * {@link NodeParticle} class, this is the most important one. 069 * </p> 070 * 071 * <p> 072 * This algorithm can be configured using several attributes put on the graph : 073 * <ul> 074 * <li>layout.force : a floating point number (default 0.5f), that allows to 075 * define the importance of movement of each node at each computation step. The 076 * larger the value the quicker nodes move to their position of lowest energy. 077 * However too high values can generate non stable layouts and oscillations.</li> 078 * <li>layout.quality : an integer between 0 and 4. With value 0 the layout is 079 * faster but it also can be farther from equilibrium. With value 4 the 080 * algorithm tries to be as close as possible from equilibrium (the n-tree and 081 * Barnes-Hut algorithms are disabled), but the computation can take a lot of 082 * time (the algorithm becomes O(n^2)). TODO change this into layout.barneshut 083 * or something similar.</li> 084 * </ul> 085 * You can also put the following attributes on nodes : 086 * <ul> 087 * <li>layout.weight : The force of repulsion of a node. The larger the value, 088 * the more the node repulses its neighbors.</li> 089 * </ul> 090 * And on edges : 091 * <ul> 092 * <li>layout.weight : the multiplier for the desired edge length. By default 093 * the algorithm tries to make each edge of length one. This is the position of 094 * lowest energy for a spring. This coefficient allows to modify this target 095 * spring length. Value larger than one will make the edge longer. Values 096 * between 0 and 1 will make the edge smaller.</li> 097 * <li>layout.stabilization-limit : the stabilization of a layout is a number 098 * between 0 and 1. 1 means fully stable, but this value is rare. Therefore one 099 * can consider the layout stable at a lower value. The default is 0.9. You can 100 * fix it with this attribute.</li> 101 * </ul> 102 * </p> 103 */ 104public abstract class BarnesHutLayout extends SourceBase implements Layout, 105 ParticleBoxListener { 106 107 /** 108 * The nodes representation and the n-tree. The particle-box is an 109 * implementation of a recursive space decomposition method that is used 110 * here to break the O(n^2) complexity into a Barnes-Hut algorithm that is 111 * closer to O(n log n). 112 */ 113 protected ParticleBox nodes; 114 115 /** 116 * The set of edges. 117 */ 118 protected HashMap<String, EdgeSpring> edges = new HashMap<String, EdgeSpring>(); 119 120 /** 121 * Used to avoid stabilizing if an event occurred. 122 */ 123 protected int lastElementCount = 0; 124 125 /** 126 * Random number generator. 127 */ 128 protected Random random; 129 130 /** 131 * The lowest node position. 132 */ 133 protected Point3 lo = new Point3(0, 0, 0); 134 135 /** 136 * The highest node position. 137 */ 138 protected Point3 hi = new Point3(1, 1, 1); 139 140 /** 141 * The point in the middle of the layout. 142 */ 143 protected Point3 center = new Point3(0.5, 0.5, 0.5); 144 145 /** 146 * Output stream for statistics if in debug mode. 147 */ 148 protected PrintStream statsOut; 149 150 /** 151 * Energy, and the history of energies. 152 */ 153 protected Energies energies = new Energies(); 154 155 /** 156 * Global force strength. This is a factor in [0..1] that is used to scale 157 * all computed displacements. 158 */ 159 protected double force = 1f; 160 161 /** 162 * The view distance at which the cells of the n-tree are explored 163 * exhaustively, after this the poles are used. This is a multiple of k. 164 */ 165 protected double viewZone = 5f; 166 167 /** 168 * The Barnes/Hut theta threshold to know if we use a pole or not. 169 */ 170 protected double theta = .7f; 171 172 /** 173 * The quality level. 174 */ 175 protected double quality = 1; 176 177 /** 178 * Number of nodes per space-cell. 179 */ 180 protected int nodesPerCell = 10; 181 182 /** 183 * The diagonal of the graph area at the current step. 184 */ 185 protected double area = 1; 186 187 /** 188 * The stabilization limit of this algorithm. 189 */ 190 protected double stabilizationLimit = 0.9; 191 192 // Attributes -- Statistics 193 194 /** 195 * Current step. 196 */ 197 protected int time; 198 199 /** 200 * The duration of the last step in milliseconds. 201 */ 202 protected long lastStepTime; 203 204 /** 205 * The maximum length of a node displacement at the current step. 206 */ 207 protected double maxMoveLength; 208 209 /** 210 * Average move length. 211 */ 212 protected double avgLength; 213 214 /** 215 * Number of nodes that moved during last step. 216 */ 217 protected int nodeMoveCount; 218 219 // Attributes -- Settings 220 221 /** 222 * Compute the third coordinate ?. 223 */ 224 protected boolean is3D = false; 225 226 /** 227 * The gravity factor. If set to 0 the gravity computation is disabled. 228 */ 229 protected double gravity = 0; 230 231 /** 232 * Send node informations?. 233 */ 234 protected boolean sendNodeInfos = false; 235 236 /** 237 * If true a file is created to output the statistics of the spring box 238 * algorithm. 239 */ 240 protected boolean outputStats = false; 241 242 /** 243 * If true a file is created for each node (!!!) and its movement statistics 244 * are logged. 245 */ 246 protected boolean outputNodeStats = false; 247 248 /** 249 * If greater than one, move events are sent only every N steps. 250 */ 251 protected int sendMoveEventsEvery = 1; 252 253 /** 254 * Sink time. 255 */ 256 protected SinkTime sinkTime; 257 258 /** 259 * New 2D Barnes-Hut simulation. 260 */ 261 public BarnesHutLayout() { 262 this(false); 263 } 264 265 /** 266 * New Barnes-Hut simulation. 267 * 268 * @param is3D 269 * If true the simulation dimensions count is 3 else 2. 270 */ 271 public BarnesHutLayout(boolean is3D) { 272 this(is3D, new Random(System.currentTimeMillis())); 273 } 274 275 /** 276 * New Barnes-Hut simulation. 277 * 278 * @param is3D 279 * If true the simulation dimensions count is 3 else 2. 280 * @param randomNumberGenerator 281 * The random number generator to use. 282 */ 283 public BarnesHutLayout(boolean is3D, Random randomNumberGenerator) { 284 CellSpace space; 285 286 this.is3D = is3D; 287 this.random = randomNumberGenerator; 288 289 if (is3D) { 290 space = new OctreeCellSpace(new Anchor(-1, -1, -1), new Anchor(1, 291 1, 1)); 292 } else { 293 space = new QuadtreeCellSpace(new Anchor(-1, -1, -0.01f), 294 new Anchor(1, 1, 0.01f)); 295 } 296 297 this.nodes = new ParticleBox(nodesPerCell, space, 298 new GraphCellData()); 299 300 nodes.addParticleBoxListener(this); 301 setQuality(quality); 302 303 sinkTime = new SinkTime(); 304 sourceTime.setSinkTime(sinkTime); 305 } 306 307 public Point3 getLowPoint() { 308 org.miv.pherd.geom.Point3 p = nodes.getNTree().getLowestPoint(); 309 lo.set(p.x, p.y, p.z); 310 return lo; 311 } 312 313 public Point3 getHiPoint() { 314 org.miv.pherd.geom.Point3 p = nodes.getNTree().getHighestPoint(); 315 hi.set(p.x, p.y, p.z); 316 return hi; 317 } 318 319 public double randomXInsideBounds() { 320 org.miv.pherd.geom.Point3 c = ((GraphCellData)nodes.getNTree().getRootCell().getData()).center; 321 return c.x + (random.nextDouble()*2-1); 322 //org.miv.pherd.geom.Point3 lo = nodes.getNTree().getLowestPoint(); 323 //org.miv.pherd.geom.Point3 hi = nodes.getNTree().getHighestPoint(); 324 //return lo.x + ((hi.x - lo.x)*random.nextDouble()); 325 } 326 327 public double randomYInsideBounds() { 328 org.miv.pherd.geom.Point3 c = ((GraphCellData)nodes.getNTree().getRootCell().getData()).center; 329 return c.y + (random.nextDouble()*2-1); 330 //org.miv.pherd.geom.Point3 lo = nodes.getNTree().getLowestPoint(); 331 //org.miv.pherd.geom.Point3 hi = nodes.getNTree().getHighestPoint(); 332 //return lo.y + ((hi.y - lo.y)*random.nextDouble()); 333 } 334 335 public double randomZInsideBounds() { 336 org.miv.pherd.geom.Point3 c = ((GraphCellData)nodes.getNTree().getRootCell().getData()).center; 337 return c.z + (random.nextDouble()*2-1); 338 //org.miv.pherd.geom.Point3 lo = nodes.getNTree().getLowestPoint(); 339 //org.miv.pherd.geom.Point3 hi = nodes.getNTree().getHighestPoint(); 340 //return lo.z + ((hi.z - lo.z)*random.nextDouble()); 341 } 342 343 public Point3 getCenterPoint() { 344 return center; 345 } 346 347 /** 348 * A gravity factor that attracts all nodes to the center of the layout to avoid flying components. If 349 * set to zero, the gravity computation is disabled. 350 * @return The gravity factor, usually between 0 and 1. 351 */ 352 public double getGravityFactor() { 353 return gravity; 354 } 355 356 /** 357 * Set the gravity factor that attracts all nodes to the center of the layout to avoid flying components. If 358 * set to zero, the gravity computation is disabled. 359 * @param value The new gravity factor, usually between 0 and 1. 360 */ 361 public void setGravityFactor(double value) { 362 gravity = value; 363 } 364 365 /** 366 * The spatial index as a n-tree. 367 * 368 * @return The n-tree. 369 */ 370 public ParticleBox getSpatialIndex() { 371 return nodes; 372 } 373 374 public long getLastStepTime() { 375 return lastStepTime; 376 } 377 378 public abstract String getLayoutAlgorithmName(); 379 380 public int getNodeMovedCount() { 381 return nodeMoveCount; 382 } 383 384 public double getStabilization() { 385 if (lastElementCount == nodes.getParticleCount() + edges.size()) { 386 if (time > energies.getBufferSize()) 387 return energies.getStabilization(); 388 } 389 390 lastElementCount = nodes.getParticleCount() + edges.size(); 391 392 return 0; 393 } 394 395 public double getStabilizationLimit() { 396 return stabilizationLimit; 397 } 398 399 public int getSteps() { 400 return time; 401 } 402 403 public double getQuality() { 404 return quality; 405 } 406 407 public boolean is3D() { 408 return is3D; 409 } 410 411 public double getForce() { 412 return force; 413 } 414 415 public Random getRandom() { 416 return random; 417 } 418 419 public Energies getEnergies() { 420 return energies; 421 } 422 423 /** 424 * The Barnes-Hut theta value used to know if we use a pole or not. 425 * 426 * @return The theta value (between 0 and 1). 427 */ 428 public double getBarnesHutTheta() { 429 return theta; 430 } 431 432 public double getViewZone() { 433 return viewZone; 434 } 435 436 public void setSendNodeInfos(boolean on) { 437 sendNodeInfos = on; 438 } 439 440 /** 441 * Change the barnes-hut theta parameter allowing to know if we use a pole 442 * or not. 443 * 444 * @param theta 445 * The new value for theta (between 0 and 1). 446 */ 447 public void setBarnesHutTheta(double theta) { 448 if (theta > 0 && theta < 1) { 449 this.theta = theta; 450 } 451 } 452 453 public void setForce(double value) { 454 this.force = value; 455 } 456 457 public void setStabilizationLimit(double value) { 458 this.stabilizationLimit = value; 459 } 460 461 public void setQuality(double qualityLevel) { 462 if (qualityLevel > 1) 463 qualityLevel = 1; 464 else if (qualityLevel < 0) 465 qualityLevel = 0; 466 quality = qualityLevel; 467 } 468 469 public void clear() { 470 energies.clearEnergies(); 471 nodes.removeAllParticles(); 472 edges.clear(); 473 nodeMoveCount = 0; 474 lastStepTime = 0; 475 } 476 477 public void compute() { 478 long t1; 479 480 computeArea(); 481 482 maxMoveLength = Double.MIN_VALUE; 483 t1 = System.currentTimeMillis(); 484 nodeMoveCount = 0; 485 avgLength = 0; 486 487 // All the movement computation is done in this call. 488 nodes.step(); 489 490 if (nodeMoveCount > 0) 491 avgLength /= nodeMoveCount; 492 493 // Ready for the next step. 494 495 getLowPoint(); 496 getHiPoint(); 497 center.set(lo.x+(hi.x-lo.x)/2, lo.y+(hi.y-lo.y)/2, lo.z+(hi.z-lo.z)/2); 498 //center.set(0, 0, 0); 499 energies.storeEnergy(); 500 printStats(); 501 time++; 502 lastStepTime = System.currentTimeMillis() - t1; 503 } 504 505 /** 506 * Output some statistics on the layout process. This method is active only 507 * if {@link #outputStats} is true. 508 */ 509 protected void printStats() { 510 if (outputStats) { 511 if (statsOut == null) { 512 try { 513 statsOut = new PrintStream("springBox.dat"); 514 statsOut.printf("# stabilization nodeMoveCount energy energyDiff maxMoveLength avgLength area%n"); 515 statsOut.flush(); 516 } catch (FileNotFoundException e) { 517 e.printStackTrace(); 518 } 519 } 520 521 if (statsOut != null) { 522 double energyDiff = energies.getEnergy() 523 - energies.getPreviousEnergyValue(30); 524 525 statsOut.printf(Locale.US, "%f %d %f %f %f %f%n", 526 getStabilization(), nodeMoveCount, 527 energies.getEnergy(), energyDiff, maxMoveLength, 528 avgLength, area); 529 statsOut.flush(); 530 } 531 } 532 } 533 534 protected void computeArea() { 535 area = getHiPoint().distance(getLowPoint()); 536 } 537 538 public void shake() { 539 energies.clearEnergies(); 540 } 541 542 protected NodeParticle addNode(String sourceId, String id) { 543 NodeParticle np = newNodeParticle(id); 544 nodes.addParticle(np); 545 return np; 546 } 547 548 public void moveNode(String id, double x, double y, double z) { 549 NodeParticle node = (NodeParticle) nodes.getParticle(id); 550 551 if (node != null) { 552 node.moveTo(x, y, z); 553 energies.clearEnergies(); 554 } 555 } 556 557 public void freezeNode(String id, boolean on) { 558 NodeParticle node = (NodeParticle) nodes.getParticle(id); 559 560 if (node != null) { 561 node.frozen = on; 562 } 563 } 564 565 protected void setNodeWeight(String id, double weight) { 566 NodeParticle node = (NodeParticle) nodes.getParticle(id); 567 568 if (node != null) 569 node.setWeight(weight); 570 } 571 572 protected void removeNode(String sourceId, String id) { 573 NodeParticle node = (NodeParticle) nodes.removeParticle(id); 574 575 if (node != null) { 576 node.removeNeighborEdges(); 577 } else { 578 System.err.printf( 579 "layout %s: cannot remove non existing node %s%n", 580 getLayoutAlgorithmName(), id); 581 } 582 } 583 584 protected void addEdge(String sourceId, String id, String from, String to, 585 boolean directed) { 586 NodeParticle n0 = (NodeParticle) nodes.getParticle(from); 587 NodeParticle n1 = (NodeParticle) nodes.getParticle(to); 588 589 if (n0 != null && n1 != null) { 590 EdgeSpring e = new EdgeSpring(id, n0, n1); 591 EdgeSpring o = edges.put(id, e); 592 593 if (o != null) { 594 // throw new SingletonException( 595 // "edge '"+id+"' already exists"); 596 System.err.printf("layout %s: edge '%s' already exists%n", 597 getLayoutAlgorithmName(), id); 598 } else { 599 n0.registerEdge(e); 600 n1.registerEdge(e); 601 } 602 603 chooseNodePosition(n0, n1); 604 } else { 605 if (n0 == null) 606 System.err 607 .printf("layout %s: node '%s' does not exist, cannot create edge %s%n", 608 getLayoutAlgorithmName(), from, id); 609 if (n1 == null) 610 System.err 611 .printf("layout %s: node '%s' does not exist, cannot create edge %s%n", 612 getLayoutAlgorithmName(), to, id); 613 } 614 } 615 616 /** 617 * Choose the best position for a node that was just connected by only one 618 * edge to a cluster of nodes. 619 * 620 * @param n0 621 * source node of the edge. 622 * @param n1 623 * target node of the edge. 624 */ 625 protected abstract void chooseNodePosition(NodeParticle n0, NodeParticle n1); 626 627 protected void addEdgeBreakPoint(String edgeId, int points) { 628 System.err.printf( 629 "layout %s: edge break points are not handled yet.%n", 630 getLayoutAlgorithmName()); 631 } 632 633 protected void ignoreEdge(String edgeId, boolean on) { 634 EdgeSpring edge = edges.get(edgeId); 635 636 if (edge != null) { 637 edge.ignored = on; 638 } 639 } 640 641 protected void setEdgeWeight(String id, double weight) { 642 EdgeSpring edge = edges.get(id); 643 644 if (edge != null) 645 edge.weight = weight; 646 } 647 648 protected void removeEdge(String sourceId, String id) { 649 EdgeSpring e = edges.remove(id); 650 651 if (e != null) { 652 e.node0.unregisterEdge(e); 653 e.node1.unregisterEdge(e); 654 } else { 655 System.err.printf( 656 "layout %s: cannot remove non existing edge %s%n", 657 getLayoutAlgorithmName(), id); 658 } 659 } 660 661 // Particle box listener 662 663 public void particleAdded(Object id, double x, double y, double z, 664 Object mark) { 665 } 666 667 public void particleAdded(Object id, double x, double y, double z) { 668 } 669 670 public void particleMarked(Object id, Object mark) { 671 } 672 673 public void particleMoved(Object id, double x, double y, double z) { 674 if ((time % sendMoveEventsEvery) == 0) { 675 Object xyz[] = new Object[3]; 676 xyz[0] = x; 677 xyz[1] = y; 678 xyz[2] = z; 679 680 sendNodeAttributeChanged(sourceId, (String) id, "xyz", xyz, xyz); 681 } 682 } 683 684 public void particleRemoved(Object id) { 685 } 686 687 public void stepFinished(int time) { 688 } 689 690 public void particleAttributeChanged(Object id, String attribute, 691 Object newValue, boolean removed) { 692 } 693 694 // SourceBase interface 695 696 public void edgeAdded(String graphId, long time, String edgeId, 697 String fromNodeId, String toNodeId, boolean directed) { 698 if (sinkTime.isNewEvent(graphId, time)) { 699 addEdge(graphId, edgeId, fromNodeId, toNodeId, directed); 700 sendEdgeAdded(graphId, time, edgeId, fromNodeId, toNodeId, directed); 701 } 702 } 703 704 public void nodeAdded(String graphId, long time, String nodeId) { 705 if (sinkTime.isNewEvent(graphId, time)) { 706//System.err.printf("## before (%.2f, %.2f -- %.2f %.2f) %n", lo.x, lo.y, hi.x, hi.y); 707 NodeParticle np = addNode(graphId, nodeId); 708 sendNodeAdded(graphId, time, nodeId); 709// org.miv.pherd.geom.Point3 p = np.getPosition(); 710 711//System.err.printf("-- node added at (%.2f, %.2f) inside (%.2f, %.2f -- %.2f %.2f) %n", p.x, p.y, lo.x, lo.y, hi.x, hi.y); 712// particleMoved(nodeId, p.x, p.y, p.z); 713 } 714 } 715 716 public void edgeRemoved(String graphId, long time, String edgeId) { 717 if (sinkTime.isNewEvent(graphId, time)) { 718 removeEdge(graphId, edgeId); 719 sendEdgeRemoved(graphId, time, edgeId); 720 } 721 } 722 723 public void nodeRemoved(String graphId, long time, String nodeId) { 724 if (sinkTime.isNewEvent(graphId, time)) { 725 removeNode(graphId, nodeId); 726 sendNodeRemoved(graphId, time, nodeId); 727 } 728 } 729 730 public void graphCleared(String graphId, long time) { 731 if (sinkTime.isNewEvent(graphId, time)) { 732 clear(); 733 sendGraphCleared(graphId, time); 734 } 735 } 736 737 public void stepBegins(String graphId, long time, double step) { 738 if (sinkTime.isNewEvent(graphId, time)) { 739 sendStepBegins(graphId, time, step); 740 } 741 } 742 743 public void graphAttributeAdded(String graphId, long time, 744 String attribute, Object value) { 745 if (sinkTime.isNewEvent(graphId, time)) { 746 graphAttributeChanged_(graphId, attribute, null, value); 747 sendGraphAttributeAdded(graphId, time, attribute, value); 748 } 749 } 750 751 public void graphAttributeChanged(String graphId, long time, 752 String attribute, Object oldValue, Object newValue) { 753 if (sinkTime.isNewEvent(graphId, time)) { 754 graphAttributeChanged_(graphId, attribute, oldValue, newValue); 755 sendGraphAttributeChanged(graphId, time, attribute, oldValue, 756 newValue); 757 } 758 } 759 760 protected void graphAttributeChanged_(String graphId, String attribute, 761 Object oldValue, Object newValue) { 762 if (attribute.equals("layout.force")) { 763 if (newValue instanceof Number) 764 setForce(((Number) newValue).doubleValue()); 765 // System.err.printf("layout.elasticBox.force: %f%n", 766 // ((Number) newValue).doubleValue()); 767 768 energies.clearEnergies(); 769 } else if (attribute.equals("layout.quality")) { 770 if (newValue instanceof Number) { 771 int q = ((Number) newValue).intValue(); 772 773 q = q > 4 ? 4 : q; 774 q = q < 0 ? 0 : q; 775 776 setQuality(q); 777 System.err.printf("layout.%s.quality: %d%n", getLayoutAlgorithmName(), q); 778 } 779 780 energies.clearEnergies(); 781 } else if(attribute.equals("layout.gravity")) { 782 if(newValue instanceof Number) { 783 double value = ((Number)newValue).doubleValue(); 784 setGravityFactor(value); 785 System.err.printf("layout.%s.gravity: %f%n", getLayoutAlgorithmName(), value); 786 } 787 } else if (attribute.equals("layout.exact-zone")) { 788 if (newValue instanceof Number) { 789 double factor = ((Number) newValue).doubleValue(); 790 791 factor = factor > 1 ? 1 : factor; 792 factor = factor < 0 ? 0 : factor; 793 794 viewZone = factor; 795 System.err.printf( 796 "layout.%s.exact-zone: %f of [0..1]%n", getLayoutAlgorithmName(), 797 viewZone); 798 799 energies.clearEnergies(); 800 } 801 } else if (attribute.equals("layout.output-stats")) { 802 if (newValue == null) 803 outputStats = false; 804 else 805 outputStats = true; 806 807 System.err.printf("layout.%s.output-stats: %b%n", getLayoutAlgorithmName(), 808 outputStats); 809 } else if (attribute.equals("layout.stabilization-limit")) { 810 if (newValue instanceof Number) { 811 stabilizationLimit = ((Number) newValue).doubleValue(); 812 if (stabilizationLimit > 1) 813 stabilizationLimit = 1; 814 else if (stabilizationLimit < 0) 815 stabilizationLimit = 0; 816 817 energies.clearEnergies(); 818 } 819 } 820 } 821 822 public void graphAttributeRemoved(String graphId, long time, 823 String attribute) { 824 if (sinkTime.isNewEvent(graphId, time)) { 825 sendGraphAttributeRemoved(graphId, time, attribute); 826 } 827 } 828 829 public void nodeAttributeAdded(String graphId, long time, String nodeId, 830 String attribute, Object value) { 831 if (sinkTime.isNewEvent(graphId, time)) { 832 nodeAttributeChanged_(graphId, nodeId, attribute, null, value); 833 sendNodeAttributeAdded(graphId, time, nodeId, attribute, value); 834 } 835 } 836 837 public void nodeAttributeChanged(String graphId, long time, String nodeId, 838 String attribute, Object oldValue, Object newValue) { 839 if (sinkTime.isNewEvent(graphId, time)) { 840 nodeAttributeChanged_(graphId, nodeId, attribute, oldValue, 841 newValue); 842 sendNodeAttributeChanged(graphId, time, nodeId, attribute, 843 oldValue, newValue); 844 } 845 } 846 847 protected void nodeAttributeChanged_(String graphId, String nodeId, 848 String attribute, Object oldValue, Object newValue) { 849 if (attribute.equals("layout.weight")) { 850 if (newValue instanceof Number) 851 setNodeWeight(nodeId, ((Number) newValue).doubleValue()); 852 else if (newValue == null) 853 setNodeWeight(nodeId, 1); 854 855 energies.clearEnergies(); 856 } else if (attribute.equals("layout.frozen")) { 857 freezeNode(nodeId, (newValue != null)); 858//System.err.printf("user froze node %s (%b)%n", nodeId, (newValue != null)); 859 } else if (attribute.equals("xyz") || attribute.equals("xy")) { 860 double xyz[] = new double[3]; 861 GraphPosLengthUtils.positionFromObject(newValue, xyz); 862 moveNode(nodeId, xyz[0], xyz[1], xyz[2]); 863//System.err.printf("user moved node %s to %f %f%n", nodeId, xyz[0], xyz[1]); 864 } else if (attribute.equals("x") && newValue instanceof Number) { 865 NodeParticle node = (NodeParticle) nodes.getParticle(nodeId); 866 if (node != null) { 867 moveNode(nodeId, ((Number) newValue).doubleValue(), 868 node.getPosition().y, node.getPosition().z); 869 } 870 } else if (attribute.equals("y") && newValue instanceof Number) { 871 NodeParticle node = (NodeParticle) nodes.getParticle(nodeId); 872 if (node != null) { 873 moveNode(nodeId, node.getPosition().x, 874 ((Number) newValue).doubleValue(), node.getPosition().z); 875 } 876 } 877 } 878 879 public void nodeAttributeRemoved(String graphId, long time, String nodeId, 880 String attribute) { 881 if (sinkTime.isNewEvent(graphId, time)) { 882 nodeAttributeChanged_(graphId, nodeId, attribute, null, null); 883 sendNodeAttributeRemoved(graphId, time, nodeId, attribute); 884 } 885 } 886 887 public void edgeAttributeAdded(String graphId, long time, String edgeId, 888 String attribute, Object value) { 889 if (sinkTime.isNewEvent(graphId, time)) { 890 edgeAttributeChanged_(graphId, edgeId, attribute, null, value); 891 sendEdgeAttributeAdded(graphId, time, edgeId, attribute, value); 892 } 893 } 894 895 public void edgeAttributeChanged(String graphId, long time, String edgeId, 896 String attribute, Object oldValue, Object newValue) { 897 if (sinkTime.isNewEvent(graphId, time)) { 898 edgeAttributeChanged_(graphId, edgeId, attribute, oldValue, 899 newValue); 900 sendEdgeAttributeChanged(graphId, time, edgeId, attribute, 901 oldValue, newValue); 902 } 903 } 904 905 protected void edgeAttributeChanged_(String graphId, String edgeId, 906 String attribute, Object oldValue, Object newValue) { 907 if (attribute.equals("layout.weight")) { 908 if (newValue instanceof Number) 909 setEdgeWeight(edgeId, ((Number) newValue).doubleValue()); 910 else if (newValue == null) 911 setEdgeWeight(edgeId, 1); 912 913 energies.clearEnergies(); 914 } else if (attribute.equals("layout.ignored")) { 915 if (newValue instanceof Boolean) 916 ignoreEdge(edgeId, (Boolean) newValue); 917 energies.clearEnergies(); 918 } 919 } 920 921 public void edgeAttributeRemoved(String graphId, long time, String edgeId, 922 String attribute) { 923 if (sinkTime.isNewEvent(graphId, time)) { 924 edgeAttributeChanged_(graphId, edgeId, attribute, null, null); 925 sendEdgeAttributeRemoved(attribute, time, edgeId, attribute); 926 } 927 } 928 929 /** 930 * Factory method to create node particles. 931 * 932 * @param id 933 * The identifier of the new node/particle. 934 * @return The new node/particle. 935 */ 936 public abstract NodeParticle newNodeParticle(String id); 937}