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