| AnimatedStructuralChangesDemo.java |
1 /****************************************************************************
2 * This demo file is part of yFiles for Java 2.14.
3 * Copyright (c) 2000-2017 by yWorks GmbH, Vor dem Kreuzberg 28,
4 * 72070 Tuebingen, Germany. All rights reserved.
5 *
6 * yFiles demo files exhibit yFiles for Java functionalities. Any redistribution
7 * of demo files in source code or binary form, with or without
8 * modification, is not permitted.
9 *
10 * Owners of a valid software license for a yFiles for Java version that this
11 * demo is shipped with are allowed to use the demo source code as basis
12 * for their own yFiles for Java powered applications. Use of such programs is
13 * governed by the rights and conditions as set out in the yFiles for Java
14 * license agreement.
15 *
16 * THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED
17 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
18 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
19 * NO EVENT SHALL yWorks BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
21 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
22 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
23 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
24 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 *
27 ***************************************************************************/
28 package demo.view.anim;
29
30 import demo.view.DemoBase;
31 import demo.view.DemoDefaults;
32
33 import y.anim.AnimationEvent;
34 import y.anim.AnimationFactory;
35 import y.anim.AnimationListener;
36 import y.anim.AnimationObject;
37 import y.anim.AnimationPlayer;
38 import y.anim.CompositeAnimationObject;
39 import y.base.DataMap;
40 import y.base.Edge;
41 import y.base.EdgeCursor;
42 import y.base.Node;
43 import y.base.NodeCursor;
44 import y.io.GraphMLIOHandler;
45 import y.layout.BufferedLayouter;
46 import y.layout.GraphLayout;
47 import y.layout.hierarchic.IncrementalHierarchicLayouter;
48 import y.layout.hierarchic.incremental.IncrementalHintsFactory;
49 import y.util.Comparators;
50 import y.util.Maps;
51 import y.view.EdgeRealizer;
52 import y.view.EditMode;
53 import y.view.Graph2D;
54 import y.view.Graph2DViewRepaintManager;
55 import y.view.LayoutMorpher;
56 import y.view.NodeRealizer;
57 import y.view.ViewAnimationFactory;
58
59 import javax.swing.JMenu;
60 import javax.swing.JMenuBar;
61 import javax.swing.JToolBar;
62 import java.awt.Dimension;
63 import java.awt.EventQueue;
64 import java.awt.event.ComponentAdapter;
65 import java.awt.event.ComponentEvent;
66 import java.io.IOException;
67 import java.net.URL;
68 import java.util.ArrayList;
69 import java.util.Collection;
70 import java.util.Comparator;
71 import java.util.HashSet;
72 import java.util.Iterator;
73 import java.util.Locale;
74 import java.util.Random;
75 import java.util.Set;
76 import java.util.WeakHashMap;
77
78 /**
79 * Demonstrates how to combine animation effects for structural graph changes
80 * with animated graph layout changes.
81 * The demonstrated effects will start automatically and loop until the user
82 * ends the demo.
83 *
84 * @see <a href="http://docs.yworks.com/yfiles/doc/api/index.html#/dguide/animation" target="_blank">Section Animations for Graph Elements</a> in the yFiles for Java Developer's Guide
85 */
86 public class AnimatedStructuralChangesDemo extends DemoBase {
87 /**
88 * Preferred duration for all animation effects.
89 */
90 private static final int PREFERRED_DURATION = 500;
91
92 /**
93 * Maximum edge count when randomizing the graph structure.
94 */
95 private static final int MAX_EDGE_COUNT = 75;
96
97 /**
98 * Maximum node count when randomizing the graph structure.
99 */
100 private static final int MAX_NODE_COUNT = 50;
101
102
103 private final Random random;
104 private final ViewAnimationFactory factory;
105 private final Graph2D graph;
106
107 private boolean disposed;
108
109 public AnimatedStructuralChangesDemo() {
110 random = new Random(42);
111 factory = new ViewAnimationFactory(new Graph2DViewRepaintManager(view));
112 graph = view.getGraph2D();
113 view.setPreferredSize(new Dimension(800, 600));
114 view.addComponentListener(new ComponentAdapter() {
115 public void componentResized(final ComponentEvent e) {
116 if (e.getSource() == view) {
117 view.removeComponentListener(this);
118
119 // finally view has been assigned a valid size which allows
120 // fitContent to work correctly
121 view.fitContent();
122
123 showInitialGraph();
124 }
125 }
126 });
127
128 configureRealizers();
129 prepareInitialGraph();
130 }
131
132 private void configureRealizers() {
133 // painting shadows is expensive and therefore not well suited for animations
134 DemoDefaults.registerDefaultNodeConfiguration(false);
135 DemoDefaults.configureDefaultRealizers(view);
136 }
137
138 /**
139 * Overridden to disable user interaction.
140 */
141 protected EditMode createEditMode() {
142 return null;
143 }
144
145 /**
146 * Overridden to disable user interaction.
147 */
148 protected JMenuBar createMenuBar() {
149 final JMenu file = new JMenu("File");
150 file.add(new ExitAction());
151
152 final JMenuBar jmb = new JMenuBar();
153 jmb.add(file);
154 return jmb;
155 }
156
157 /**
158 * Overridden to disable user interaction.
159 */
160 protected JToolBar createToolBar() {
161 return null;
162 }
163
164 private void prepareInitialGraph() {
165 // try to load an initial graph
166 final URL resource = getResource("resource/hierarchic.graphml");
167
168 if (resource != null) {
169 final GraphMLIOHandler ioh = new GraphMLIOHandler();
170 try {
171 ioh.read(graph, resource);
172 } catch (IOException ioe) {
173 System.err.println(ioe.getMessage());
174 graph.clear();
175 }
176 } else {
177 graph.clear();
178 }
179
180 DemoDefaults.applyRealizerDefaults(graph);
181
182 if (graph.nodeCount() > 0) {
183 graph.setDefaultNodeRealizer(graph.getRealizer(graph.firstNode()).createCopy());
184 }
185 // by default newly created nodes are invisible
186 // animation effects will make new nodes visible later
187 graph.getDefaultNodeRealizer().setVisible(false);
188
189 if (graph.edgeCount() > 0) {
190 graph.setDefaultEdgeRealizer(graph.getRealizer(graph.firstEdge()).createCopy());
191 }
192 // by default newly created edges are invisible
193 // animation effects will make new edges visible later
194 graph.getDefaultEdgeRealizer().setVisible(false);
195
196 // set all graph elements to invisible initially
197 // the first create animation will make these elements visible later on
198 for (NodeCursor nc = graph.nodes(); nc.ok(); nc.next()) {
199 graph.getRealizer(nc.node()).setVisible(false);
200 }
201
202 for (EdgeCursor ec = graph.edges(); ec.ok(); ec.next()) {
203 graph.getRealizer(ec.edge()).setVisible(false);
204 }
205 }
206
207 private void showInitialGraph() {
208 final ArrayList newNodes = new ArrayList(graph.nodeCount());
209 for (NodeCursor nc = graph.nodes(); nc.ok(); nc.next()) {
210 newNodes.add(nc.node());
211 }
212 final ArrayList newEdges = new ArrayList(graph.edgeCount());
213 for (EdgeCursor ec = graph.edges(); ec.ok(); ec.next()) {
214 newEdges.add(ec.edge());
215 }
216
217 final AnimationPlayer player = new AnimationPlayer(false);
218
219 // register the ViewAnimationFactory's repaint manager as animation
220 // listener to prevent repaints for the complete Graph2DView and
221 // thereby improving animation performance if possible
222 player.addAnimationListener(factory.getRepaintManager());
223
224 player.addAnimationListener(new Command() {
225 void execute() {
226 // start the main execution loop
227 AnimatedStructuralChangesDemo.this.execute();
228 }
229 });
230
231 // start the animation and idle for some time at the end
232 player.animate(AnimationFactory.createSequence(
233 createCreateAnimation(newNodes, newEdges),
234 AnimationFactory.createPause(PREFERRED_DURATION)));
235 }
236
237 public void dispose() {
238 disposed = true;
239 }
240
241 /**
242 * Randomizes the graph structure, calculates a new graph layout, and finally
243 * animates the structural and layout changes.
244 */
245 private void execute() {
246 if (disposed) {
247 return;
248 }
249
250 // determine nodes and edges that should be deleted
251 final HashSet nodesToBeDeleted = new HashSet();
252 final HashSet edgesToBeDeleted = new HashSet();
253 markNodesForDeletion(nodesToBeDeleted, edgesToBeDeleted);
254 markEdgesForDeletion(edgesToBeDeleted);
255
256 // temporarily remove the elements that will be deleted later on
257 // these elements are removed for two reasons:
258 // 1. to prevent new edges being created for nodes that are marked for
259 // deletion
260 // 2. to prevent these elements from being considered when calculating
261 // a new graph layout
262 for (Iterator it = edgesToBeDeleted.iterator(); it.hasNext();) {
263 graph.hide((Edge) it.next());
264 }
265 for (Iterator it = nodesToBeDeleted.iterator(); it.hasNext();) {
266 graph.hide((Node) it.next());
267 }
268
269 // create some new nodes and edges
270 final HashSet newNodes = new HashSet();
271 createNodes(newNodes);
272 final HashSet newEdges = new HashSet();
273 createEdges(newNodes, newEdges);
274
275 // calculate a new graph layout for the new graph structure
276 // i.e. all elements marked for deletion have been removed at this point
277 // and all new elements have been created already (so new elements will
278 // appear at the correct location later)
279 final GraphLayout gl = calcLayout(newNodes, newEdges);
280
281 // now reinsert the elements marked for deletion, so the animation effects
282 // will work properly
283 // the actual deletion will be done by the animation effect, see also
284 // the documentation for ViewAnimationFactory's APPLY_EFFECT
285 for (Iterator it = nodesToBeDeleted.iterator(); it.hasNext();) {
286 graph.unhide((Node) it.next());
287 }
288 for (Iterator it = edgesToBeDeleted.iterator(); it.hasNext();) {
289 graph.unhide((Edge) it.next());
290 }
291
292 // create a shared, non-blocking AnimationPlayer
293 // non-blocking, so a user can still interact with the SWING GUI
294 // (even if it is only to quit the demo)
295 final AnimationPlayer player = new AnimationPlayer(false);
296
297 // now chain several animation effects
298 // this is done because animations such as LayoutMorpher and
299 // ViewAnimation.extract/ViewAnimation.retract are rather expensive to
300 // create (which could severly hamper the animation frame rate) and
301 // more important these animations use the state of their targets
302 // at *instantiation* time
303
304 // triggers re-execution of this method at the end of the final animation
305 final Command loop = new Command() {
306 void execute() {
307 // cleanup
308 player.removeAnimationListener(this);
309 player.removeAnimationListener(factory.getRepaintManager());
310
311 // loop
312 AnimatedStructuralChangesDemo.this.execute();
313 }
314 };
315
316 // triggers creating new nodes by fade in and new edges by extract
317 final Command animateCreate = new Command() {
318 void execute() {
319 // cleanup
320 player.removeAnimationListener(this);
321 player.removeAnimationListener(view);
322
323 // register looping for execution
324 player.addAnimationListener(loop);
325
326 // register the ViewAnimationFactory's repaint manager as animation
327 // listener to prevent repaints for the complete Graph2DView and
328 // thereby improving animation performance if possible
329 player.addAnimationListener(factory.getRepaintManager());
330
331 // start the animation and idle for some time at the end
332 player.animate(AnimationFactory.createSequence(
333 createCreateAnimation(newNodes, newEdges),
334 AnimationFactory.createPause(PREFERRED_DURATION)));
335 }
336 };
337
338 // triggers applying the new graph layout in an animated fashion
339 final Command animateMorphing = new Command() {
340 void execute() {
341 // cleanup
342 player.removeAnimationListener(this);
343 player.removeAnimationListener(factory.getRepaintManager());
344
345 // register the next animation effect for execution
346 player.addAnimationListener(animateCreate);
347
348 // register the complete Graph2DView as animation listener because
349 // LayoutMorpher does not support repaint managers
350 player.addAnimationListener(view);
351
352 // start the animation
353 player.animate(createMorphingAnimation(gl));
354 }
355 };
356
357 // triggers deleting marked elements
358 final Command animateDelete = new Command() {
359 void execute() {
360 // register the next animation effect for execution
361 player.addAnimationListener(animateMorphing);
362
363 // register the ViewAnimationFactory's repaint manager as animation
364 // listener to prevent repaints for the complete Graph2DView and
365 // thereby improving animation performance if possible
366 player.addAnimationListener(factory.getRepaintManager());
367
368 // start the animation
369 player.animate(createDeleteAnimation(nodesToBeDeleted, edgesToBeDeleted));
370 }
371 };
372
373 animateDelete.execute();
374 }
375
376 /*
377 * #####################################################################
378 * methods for randomized structural changes
379 * #####################################################################
380 */
381
382 /**
383 * Randomly determine edges to be deleted from the graph.
384 * @param edgesToBeDeleted will store the edges to be deleted.
385 */
386 private void markEdgesForDeletion(
387 final Set edgesToBeDeleted
388 ) {
389 for (EdgeCursor ec = graph.edges();
390 ec.ok() && graph.edgeCount() - edgesToBeDeleted.size() > 4;
391 ec.next()) {
392 if (!edgesToBeDeleted.contains(ec.edge()) && random.nextDouble() < 0.05) {
393 edgesToBeDeleted.add(ec.edge());
394 }
395 }
396 }
397
398 /**
399 * Randomly determines nodes to be deleted from the graph.
400 * @param nodesToBeDeleted will store the nodes to be deleted.
401 * @param edgesToBeDeleted will store all edges incident to nodes to be
402 * deleted. (When removing nodes from a graph, incident edges are
403 * automatically removed, too. However, by collecting these edges, they can
404 * be deleted in an automated fashion.)
405 */
406 private void markNodesForDeletion(
407 final Set nodesToBeDeleted,
408 final Set edgesToBeDeleted
409 ) {
410 for (NodeCursor nc = graph.nodes();
411 nc.ok() &&
412 graph.nodeCount() - nodesToBeDeleted.size() > 4 &&
413 graph.edgeCount() - edgesToBeDeleted.size() > 4;
414 nc.next()) {
415 if (random.nextDouble() < 0.05) {
416 nodesToBeDeleted.add(nc.node());
417 for (EdgeCursor ec = nc.node().edges(); ec.ok(); ec.next()) {
418 edgesToBeDeleted.add(ec.edge());
419 }
420 }
421 }
422 }
423
424 /**
425 * Creates a random number of new nodes.
426 * @param newNodes will store the newly created nodes.
427 */
428 private void createNodes(
429 final Set newNodes
430 ) {
431 if (graph.nodeCount() < MAX_NODE_COUNT + 1) {
432 for (int i = 0, n = random.nextInt(MAX_NODE_COUNT + 1 - graph.nodeCount()); i < n; ++i) {
433 final Node node = graph.createNode();
434 newNodes.add(node);
435 }
436 }
437 }
438
439 /**
440 * Creates a random number of new edges between randomly chosen new nodes.
441 * New edges are created preferably between an old node and a new node.
442 * Nodes are considered to be <em>new</em>, iff <code>newNodes.contains</code>
443 * returns <code>true</code> and to be <em>old</em> otherwise.
444 * <p>
445 * Note, the implementation of this method relies on the fact that it is
446 * called right after {@link #createNodes(java.util.Set)}.
447 * @param newNodes nodes marked as new.
448 * @param newEdges will store the newly created edges.
449 */
450 private void createEdges(
451 final HashSet newNodes,
452 final HashSet newEdges
453 ) {
454 if (graph.edgeCount() < MAX_EDGE_COUNT + 1) {
455 final Node[] nodes = graph.getNodeArray();
456 final int newCount = newNodes.size();
457 final int oldCount = nodes.length - newCount;
458
459 if (newCount > 1 && oldCount > 1) {
460 // sort old nodes from upper left to lower right
461 // this will result in new edges between old nodes being in hierarchic
462 // flow direction
463 Comparators.sort(nodes, 0, oldCount, new Comparator() {
464 public int compare(final Object n1, final Object n2) {
465 final double dy = graph.getCenterY((Node) n1) - graph.getCenterY((Node) n2);
466 if (dy < 0) {
467 return -1;
468 } else if (dy > 0) {
469 return 1;
470 } else {
471 final double dx = graph.getCenterX((Node) n1) - graph.getCenterX((Node) n2);
472 if (dx < 0) {
473 return -1;
474 } else if (dx > 0) {
475 return 1;
476 } else {
477 return 0;
478 }
479 }
480 }
481 });
482
483 for (int i = 0, n = random.nextInt(MAX_EDGE_COUNT + 1 - graph.edgeCount()); i < n; ++i) {
484 final double d = random.nextDouble();
485 final Edge edge;
486 if (d < 0.1) {
487 // create an edge between two old nodes
488 final int n1 = random.nextInt(oldCount);
489 final int n2 = n1 + random.nextInt(oldCount - n1);
490 edge = n1 != n2 ? graph.createEdge(nodes[n1], nodes[n2]) : null;
491 } else if (d < 0.5) {
492 // create an edge between an old and a new node
493 edge = graph.createEdge(nodes[random.nextInt(oldCount)], nodes[oldCount + random.nextInt(newCount)]);
494 } else if (d < 0.9) {
495 // create an edge between a new and an old node
496 edge = graph.createEdge(nodes[oldCount + random.nextInt(newCount)], nodes[random.nextInt(oldCount)]);
497 } else {
498 // create an edge between two new nodes
499 final int n1 = oldCount + random.nextInt(newCount);
500 final int n2 = oldCount + random.nextInt(newCount);
501 edge = n1 != n2 ? graph.createEdge(nodes[n1], nodes[n2]) : null;
502 }
503 if (edge != null) {
504 newEdges.add(edge);
505 }
506 }
507 } else if (oldCount > 1) {
508 // create edges between old nodes only (there are no new nodes)
509 for (int i = 0, n = random.nextInt(MAX_EDGE_COUNT + 1 - graph.edgeCount()); i < n; ++i) {
510 final int n1 = random.nextInt(oldCount);
511 final int n2 = n1 + random.nextInt(oldCount - n1);
512 if (n1 != n2) {
513 newEdges.add(graph.createEdge(nodes[n1], nodes[n2]));
514 }
515 }
516 } else if (newCount > 1) {
517 // create edges between new nodes only (there are no old nodes)
518 for (int i = 0, n = random.nextInt(MAX_EDGE_COUNT + 1 - graph.edgeCount()); i < n; ++i) {
519 final int n1 = random.nextInt(newCount);
520 final int n2 = random.nextInt(newCount);
521 if (n1 != n2) {
522 newEdges.add(graph.createEdge(nodes[n1], nodes[n2]));
523 }
524 }
525 }
526 }
527 }
528
529 /*
530 * #####################################################################
531 * factory methods for animations
532 * #####################################################################
533 */
534
535 /**
536 * Creates an animation for fading in new nodes and extracting new edges.
537 * As a side effect, this animation will result in the new nodes and new
538 * edges being visible.
539 * @param newNodes the nodes that should be faded in.
540 * @param newEdges the edges that should be extracted.
541 * @return an animation for fading in new nodes and extracting new edges.
542 */
543 private AnimationObject createCreateAnimation(
544 final Collection newNodes,
545 final Collection newEdges
546 ) {
547 // create fade in animations for the new nodes and set them up to
548 // play simultaneously
549 final CompositeAnimationObject addNodes = AnimationFactory.createConcurrency();
550 for (Iterator it = newNodes.iterator(); it.hasNext();) {
551 final NodeRealizer nr = graph.getRealizer((Node) it.next());
552 addNodes.addAnimation(factory.fadeIn(nr, PREFERRED_DURATION * 2));
553 }
554
555 // create extract animations for the new edges and set them up to
556 // play simultaneously
557 final CompositeAnimationObject addEdges = AnimationFactory.createConcurrency();
558 for (Iterator it = newEdges.iterator(); it.hasNext();) {
559 final EdgeRealizer er = graph.getRealizer((Edge) it.next());
560 addEdges.addAnimation(factory.extract(er, PREFERRED_DURATION));
561 }
562
563 // create an animation that will first fade in nodes and then extract edges
564 //
565 // note that initAnimation for *both* addNodes and addEdges will happen
566 // before both addNodes and addEdges are played and disposeAnimation
567 // for *both* addNodes and addEdges will happen after addNodes and
568 // addEdges are played
569 // see also the API documentation for createSequence
570 return AnimationFactory.createSequence(addNodes, addEdges);
571 }
572
573 /**
574 * Creates an animation that applies the specified graph layout to the
575 * graph structure.
576 * <p>
577 * Note that the graph may not be structurally altered in between creating
578 * and disposing (at the end of playing) of it.
579 * </p>
580 * @param gl the new graph layout to be applied in an animated fashion.
581 * @return an animation that applies the specified graph layout to the
582 * graph structure.
583 */
584 private AnimationObject createMorphingAnimation(
585 final GraphLayout gl
586 ) {
587 final LayoutMorpher morphing = new LayoutMorpher(view, gl);
588 morphing.setPreferredDuration(PREFERRED_DURATION);
589 morphing.setSmoothViewTransform(true);
590 return AnimationFactory.createEasedAnimation(morphing);
591 }
592
593 /**
594 * Creates an animation for retracting edges and fading out nodes.
595 * As a side effect, this animation will result in said edges and nodes being
596 * removed from the graph.
597 * @param nodesToBeDeleted the nodes to fade out
598 * @param edgesToBeDeleted the edges to retract
599 * @return an animation for retracting edges and fading out nodes.
600 */
601 private AnimationObject createDeleteAnimation(
602 final Set nodesToBeDeleted,
603 final Set edgesToBeDeleted
604 ) {
605 // create retract animations for the edges and set them up to play
606 // simultaneously
607 // note, the specified APPLY_EFFECT will result in the edges being actually
608 // removed at the end of the animation
609 final CompositeAnimationObject deleteEdges = AnimationFactory.createConcurrency();
610 for (Iterator it = edgesToBeDeleted.iterator(); it.hasNext();) {
611 final EdgeRealizer er = graph.getRealizer((Edge) it.next());
612 deleteEdges.addAnimation(factory.retract(
613 er, ViewAnimationFactory.APPLY_EFFECT, PREFERRED_DURATION));
614 }
615
616 // create fade out animations for the nodes and set them up to play
617 // simultaneously
618 // note, the specified APPLY_EFFECT will result in the nodes being actually
619 // removed at the end of the animation
620 final CompositeAnimationObject deleteNodes = AnimationFactory.createConcurrency();
621 for (Iterator it = nodesToBeDeleted.iterator(); it.hasNext();) {
622 final NodeRealizer nr = graph.getRealizer((Node) it.next());
623 deleteNodes.addAnimation(factory.fadeOut(
624 nr, ViewAnimationFactory.APPLY_EFFECT, PREFERRED_DURATION));
625 }
626
627 // create an animation that will first retract edges and then fade out nodes
628 //
629 // note that initAnimation for *both* deleteEdges and deleteNodes will
630 // happen before both deleteEdges and deleteNodes are played and
631 // disposeAnimation for *both* deleteEdges and deleteNodes will happen
632 // after deleteEdges and deleteNodes are played
633 // see also the API documentation for createSequence
634 return AnimationFactory.createSequence(deleteEdges, deleteNodes);
635 }
636
637
638 /**
639 * Calculates a new hierarchic layout.
640 * @param newNodes nodes to be incrementally inserted into the existing
641 * layout.
642 * @param newEdges edges to be incrementally inserted into the existing
643 * layout.
644 * @return a new hierarchic layout.
645 */
646 private GraphLayout calcLayout(
647 final Set newNodes,
648 final Set newEdges
649 ) {
650 final DataMap hints = Maps.createDataMap(new WeakHashMap());
651 final IncrementalHierarchicLayouter ihl = new IncrementalHierarchicLayouter();
652 ihl.setLayoutMode(IncrementalHierarchicLayouter.LAYOUT_MODE_INCREMENTAL);
653 ihl.setOrthogonallyRouted(true);
654 final IncrementalHintsFactory hf = ihl.createIncrementalHintsFactory();
655 for (Iterator it = newNodes.iterator(); it.hasNext();) {
656 final Object node = it.next();
657 hints.set(node, hf.createLayerIncrementallyHint(node));
658 }
659 for (Iterator it = newEdges.iterator(); it.hasNext();) {
660 final Object edge = it.next();
661 hints.set(edge, hf.createSequenceIncrementallyHint(edge));
662 if (((Edge) edge).source().degree() == 1) {
663 final Node node = ((Edge) edge).source();
664 hints.set(node, hf.createLayerIncrementallyHint(node));
665 }
666 if (((Edge) edge).target().degree() == 1) {
667 final Node node = ((Edge) edge).target();
668 hints.set(node, hf.createLayerIncrementallyHint(node));
669 }
670 }
671 graph.addDataProvider(IncrementalHierarchicLayouter.INCREMENTAL_HINTS_DPKEY, hints);
672 try {
673 return (new BufferedLayouter(ihl)).calcLayout(graph);
674 } finally {
675 graph.removeDataProvider(IncrementalHierarchicLayouter.INCREMENTAL_HINTS_DPKEY);
676 }
677 }
678
679
680 public static void main(String[] args) {
681 EventQueue.invokeLater(new Runnable() {
682 public void run() {
683 Locale.setDefault(Locale.ENGLISH);
684 initLnF();
685 (new AnimatedStructuralChangesDemo()).start();
686 }
687 });
688 }
689
690
691 private abstract static class Command implements AnimationListener {
692 public void animationPerformed(final AnimationEvent e) {
693 if (e.getHint() == AnimationEvent.END) {
694 execute();
695 }
696 }
697
698 abstract void execute();
699 }
700 }
701