1
14 package demo.view.anim;
15
16 import demo.view.DemoBase;
17 import demo.view.DemoDefaults;
18
19 import y.anim.AnimationEvent;
20 import y.anim.AnimationFactory;
21 import y.anim.AnimationListener;
22 import y.anim.AnimationObject;
23 import y.anim.AnimationPlayer;
24 import y.anim.CompositeAnimationObject;
25 import y.base.DataMap;
26 import y.base.Edge;
27 import y.base.EdgeCursor;
28 import y.base.Node;
29 import y.base.NodeCursor;
30 import y.io.GraphMLIOHandler;
31 import y.layout.BufferedLayouter;
32 import y.layout.GraphLayout;
33 import y.layout.hierarchic.IncrementalHierarchicLayouter;
34 import y.layout.hierarchic.incremental.IncrementalHintsFactory;
35 import y.util.Comparators;
36 import y.util.Maps;
37 import y.view.EdgeRealizer;
38 import y.view.EditMode;
39 import y.view.Graph2D;
40 import y.view.Graph2DViewRepaintManager;
41 import y.view.LayoutMorpher;
42 import y.view.NodeRealizer;
43 import y.view.ViewAnimationFactory;
44
45 import javax.swing.JMenu;
46 import javax.swing.JMenuBar;
47 import javax.swing.JToolBar;
48 import java.awt.Dimension;
49 import java.awt.EventQueue;
50 import java.awt.event.ComponentAdapter;
51 import java.awt.event.ComponentEvent;
52 import java.io.IOException;
53 import java.net.URL;
54 import java.util.ArrayList;
55 import java.util.Collection;
56 import java.util.Comparator;
57 import java.util.HashSet;
58 import java.util.Iterator;
59 import java.util.Locale;
60 import java.util.Random;
61 import java.util.Set;
62 import java.util.WeakHashMap;
63
64
72 public class AnimatedStructuralChangesDemo extends DemoBase {
73
76 private static final int PREFERRED_DURATION = 500;
77
78
81 private static final int MAX_EDGE_COUNT = 75;
82
83
86 private static final int MAX_NODE_COUNT = 50;
87
88
89 private final Random random;
90 private final ViewAnimationFactory factory;
91 private final Graph2D graph;
92
93 private boolean disposed;
94
95 public AnimatedStructuralChangesDemo() {
96 random = new Random(42);
97 factory = new ViewAnimationFactory(new Graph2DViewRepaintManager(view));
98 graph = view.getGraph2D();
99 view.setPreferredSize(new Dimension(800, 600));
100 view.addComponentListener(new ComponentAdapter() {
101 public void componentResized(final ComponentEvent e) {
102 if (e.getSource() == view) {
103 view.removeComponentListener(this);
104
105 view.fitContent();
108
109 showInitialGraph();
110 }
111 }
112 });
113
114 configureRealizers();
115 prepareInitialGraph();
116 }
117
118 private void configureRealizers() {
119 DemoDefaults.registerDefaultNodeConfiguration(false);
121 DemoDefaults.configureDefaultRealizers(view);
122 }
123
124
127 protected EditMode createEditMode() {
128 return null;
129 }
130
131
134 protected JMenuBar createMenuBar() {
135 final JMenu file = new JMenu("File");
136 file.add(new ExitAction());
137
138 final JMenuBar jmb = new JMenuBar();
139 jmb.add(file);
140 return jmb;
141 }
142
143
146 protected JToolBar createToolBar() {
147 return null;
148 }
149
150 private void prepareInitialGraph() {
151 final URL resource = getClass().getResource("resource/hierarchic.graphml");
153
154 if (resource != null) {
155 final GraphMLIOHandler ioh = new GraphMLIOHandler();
156 try {
157 ioh.read(graph, resource);
158 } catch (IOException ioe) {
159 System.err.println(ioe.getMessage());
160 graph.clear();
161 }
162 } else {
163 System.err.println("Could not load \"resource/hierarchic.graphml\".");
164 graph.clear();
165 }
166
167 DemoDefaults.applyRealizerDefaults(graph);
168
169 if (graph.nodeCount() > 0) {
170 graph.setDefaultNodeRealizer(graph.getRealizer(graph.firstNode()).createCopy());
171 }
172 graph.getDefaultNodeRealizer().setVisible(false);
175
176 if (graph.edgeCount() > 0) {
177 graph.setDefaultEdgeRealizer(graph.getRealizer(graph.firstEdge()).createCopy());
178 }
179 graph.getDefaultEdgeRealizer().setVisible(false);
182
183 for (NodeCursor nc = graph.nodes(); nc.ok(); nc.next()) {
186 graph.getRealizer(nc.node()).setVisible(false);
187 }
188
189 for (EdgeCursor ec = graph.edges(); ec.ok(); ec.next()) {
190 graph.getRealizer(ec.edge()).setVisible(false);
191 }
192 }
193
194 private void showInitialGraph() {
195 final ArrayList newNodes = new ArrayList(graph.nodeCount());
196 for (NodeCursor nc = graph.nodes(); nc.ok(); nc.next()) {
197 newNodes.add(nc.node());
198 }
199 final ArrayList newEdges = new ArrayList(graph.edgeCount());
200 for (EdgeCursor ec = graph.edges(); ec.ok(); ec.next()) {
201 newEdges.add(ec.edge());
202 }
203
204 final AnimationPlayer player = new AnimationPlayer(false);
205
206 player.addAnimationListener(factory.getRepaintManager());
210
211 player.addAnimationListener(new Command() {
212 void execute() {
213 AnimatedStructuralChangesDemo.this.execute();
215 }
216 });
217
218 player.animate(AnimationFactory.createSequence(
220 createCreateAnimation(newNodes, newEdges),
221 AnimationFactory.createPause(PREFERRED_DURATION)));
222 }
223
224 public void dispose() {
225 disposed = true;
226 }
227
228
232 private void execute() {
233 if (disposed) {
234 return;
235 }
236
237 final HashSet nodesToBeDeleted = new HashSet();
239 final HashSet edgesToBeDeleted = new HashSet();
240 markNodesForDeletion(nodesToBeDeleted, edgesToBeDeleted);
241 markEdgesForDeletion(edgesToBeDeleted);
242
243 for (Iterator it = edgesToBeDeleted.iterator(); it.hasNext();) {
250 graph.hide((Edge) it.next());
251 }
252 for (Iterator it = nodesToBeDeleted.iterator(); it.hasNext();) {
253 graph.hide((Node) it.next());
254 }
255
256 final HashSet newNodes = new HashSet();
258 createNodes(newNodes);
259 final HashSet newEdges = new HashSet();
260 createEdges(newNodes, newEdges);
261
262 final GraphLayout gl = calcLayout(newNodes, newEdges);
267
268 for (Iterator it = nodesToBeDeleted.iterator(); it.hasNext();) {
273 graph.unhide((Node) it.next());
274 }
275 for (Iterator it = edgesToBeDeleted.iterator(); it.hasNext();) {
276 graph.unhide((Edge) it.next());
277 }
278
279 final AnimationPlayer player = new AnimationPlayer(false);
283
284
291 final Command loop = new Command() {
293 void execute() {
294 player.removeAnimationListener(this);
296 player.removeAnimationListener(factory.getRepaintManager());
297
298 AnimatedStructuralChangesDemo.this.execute();
300 }
301 };
302
303 final Command animateCreate = new Command() {
305 void execute() {
306 player.removeAnimationListener(this);
308 player.removeAnimationListener(view);
309
310 player.addAnimationListener(loop);
312
313 player.addAnimationListener(factory.getRepaintManager());
317
318 player.animate(AnimationFactory.createSequence(
320 createCreateAnimation(newNodes, newEdges),
321 AnimationFactory.createPause(PREFERRED_DURATION)));
322 }
323 };
324
325 final Command animateMorphing = new Command() {
327 void execute() {
328 player.removeAnimationListener(this);
330 player.removeAnimationListener(factory.getRepaintManager());
331
332 player.addAnimationListener(animateCreate);
334
335 player.addAnimationListener(view);
338
339 player.animate(createMorphingAnimation(gl));
341 }
342 };
343
344 final Command animateDelete = new Command() {
346 void execute() {
347 player.addAnimationListener(animateMorphing);
349
350 player.addAnimationListener(factory.getRepaintManager());
354
355 player.animate(createDeleteAnimation(nodesToBeDeleted, edgesToBeDeleted));
357 }
358 };
359
360 animateDelete.execute();
361 }
362
363
368
369
373 private void markEdgesForDeletion(
374 final Set edgesToBeDeleted
375 ) {
376 for (EdgeCursor ec = graph.edges();
377 ec.ok() && graph.edgeCount() - edgesToBeDeleted.size() > 4;
378 ec.next()) {
379 if (!edgesToBeDeleted.contains(ec.edge()) && random.nextDouble() < 0.05) {
380 edgesToBeDeleted.add(ec.edge());
381 }
382 }
383 }
384
385
393 private void markNodesForDeletion(
394 final Set nodesToBeDeleted,
395 final Set edgesToBeDeleted
396 ) {
397 for (NodeCursor nc = graph.nodes();
398 nc.ok() &&
399 graph.nodeCount() - nodesToBeDeleted.size() > 4 &&
400 graph.edgeCount() - edgesToBeDeleted.size() > 4;
401 nc.next()) {
402 if (random.nextDouble() < 0.05) {
403 nodesToBeDeleted.add(nc.node());
404 for (EdgeCursor ec = nc.node().edges(); ec.ok(); ec.next()) {
405 edgesToBeDeleted.add(ec.edge());
406 }
407 }
408 }
409 }
410
411
415 private void createNodes(
416 final Set newNodes
417 ) {
418 if (graph.nodeCount() < MAX_NODE_COUNT + 1) {
419 for (int i = 0, n = random.nextInt(MAX_NODE_COUNT + 1 - graph.nodeCount()); i < n; ++i) {
420 final Node node = graph.createNode();
421 newNodes.add(node);
422 }
423 }
424 }
425
426
437 private void createEdges(
438 final HashSet newNodes,
439 final HashSet newEdges
440 ) {
441 if (graph.edgeCount() < MAX_EDGE_COUNT + 1) {
442 final Node[] nodes = graph.getNodeArray();
443 final int newCount = newNodes.size();
444 final int oldCount = nodes.length - newCount;
445
446 if (newCount > 1 && oldCount > 1) {
447 Comparators.sort(nodes, 0, oldCount, new Comparator() {
451 public int compare(final Object n1, final Object n2) {
452 final double dy = graph.getCenterY((Node) n1) - graph.getCenterY((Node) n2);
453 if (dy < 0) {
454 return -1;
455 } else if (dy > 0) {
456 return 1;
457 } else {
458 final double dx = graph.getCenterX((Node) n1) - graph.getCenterX((Node) n2);
459 if (dx < 0) {
460 return -1;
461 } else if (dx > 0) {
462 return 1;
463 } else {
464 return 0;
465 }
466 }
467 }
468 });
469
470 for (int i = 0, n = random.nextInt(MAX_EDGE_COUNT + 1 - graph.edgeCount()); i < n; ++i) {
471 final double d = random.nextDouble();
472 final Edge edge;
473 if (d < 0.1) {
474 final int n1 = random.nextInt(oldCount);
476 final int n2 = n1 + random.nextInt(oldCount - n1);
477 edge = n1 != n2 ? graph.createEdge(nodes[n1], nodes[n2]) : null;
478 } else if (d < 0.5) {
479 edge = graph.createEdge(nodes[random.nextInt(oldCount)], nodes[oldCount + random.nextInt(newCount)]);
481 } else if (d < 0.9) {
482 edge = graph.createEdge(nodes[oldCount + random.nextInt(newCount)], nodes[random.nextInt(oldCount)]);
484 } else {
485 final int n1 = oldCount + random.nextInt(newCount);
487 final int n2 = oldCount + random.nextInt(newCount);
488 edge = n1 != n2 ? graph.createEdge(nodes[n1], nodes[n2]) : null;
489 }
490 if (edge != null) {
491 newEdges.add(edge);
492 }
493 }
494 } else if (oldCount > 1) {
495 for (int i = 0, n = random.nextInt(MAX_EDGE_COUNT + 1 - graph.edgeCount()); i < n; ++i) {
497 final int n1 = random.nextInt(oldCount);
498 final int n2 = n1 + random.nextInt(oldCount - n1);
499 if (n1 != n2) {
500 newEdges.add(graph.createEdge(nodes[n1], nodes[n2]));
501 }
502 }
503 } else if (newCount > 1) {
504 for (int i = 0, n = random.nextInt(MAX_EDGE_COUNT + 1 - graph.edgeCount()); i < n; ++i) {
506 final int n1 = random.nextInt(newCount);
507 final int n2 = random.nextInt(newCount);
508 if (n1 != n2) {
509 newEdges.add(graph.createEdge(nodes[n1], nodes[n2]));
510 }
511 }
512 }
513 }
514 }
515
516
521
522
530 private AnimationObject createCreateAnimation(
531 final Collection newNodes,
532 final Collection newEdges
533 ) {
534 final CompositeAnimationObject addNodes = AnimationFactory.createConcurrency();
537 for (Iterator it = newNodes.iterator(); it.hasNext();) {
538 final NodeRealizer nr = graph.getRealizer((Node) it.next());
539 addNodes.addAnimation(factory.fadeIn(nr, PREFERRED_DURATION * 2));
540 }
541
542 final CompositeAnimationObject addEdges = AnimationFactory.createConcurrency();
545 for (Iterator it = newEdges.iterator(); it.hasNext();) {
546 final EdgeRealizer er = graph.getRealizer((Edge) it.next());
547 addEdges.addAnimation(factory.extract(er, PREFERRED_DURATION));
548 }
549
550 return AnimationFactory.createSequence(addNodes, addEdges);
558 }
559
560
571 private AnimationObject createMorphingAnimation(
572 final GraphLayout gl
573 ) {
574 final LayoutMorpher morphing = new LayoutMorpher(view, gl) {
575 public void disposeAnimation() {
576 super.disposeAnimation();
577 view.fitContent();
578 }
579 };
580 morphing.setPreferredDuration(PREFERRED_DURATION);
581 morphing.setSmoothViewTransform(true);
582 return AnimationFactory.createEasedAnimation(morphing);
583 }
584
585
593 private AnimationObject createDeleteAnimation(
594 final Set nodesToBeDeleted,
595 final Set edgesToBeDeleted
596 ) {
597 final CompositeAnimationObject deleteEdges = AnimationFactory.createConcurrency();
602 for (Iterator it = edgesToBeDeleted.iterator(); it.hasNext();) {
603 final EdgeRealizer er = graph.getRealizer((Edge) it.next());
604 deleteEdges.addAnimation(factory.retract(
605 er, ViewAnimationFactory.APPLY_EFFECT, PREFERRED_DURATION));
606 }
607
608 final CompositeAnimationObject deleteNodes = AnimationFactory.createConcurrency();
613 for (Iterator it = nodesToBeDeleted.iterator(); it.hasNext();) {
614 final NodeRealizer nr = graph.getRealizer((Node) it.next());
615 deleteNodes.addAnimation(factory.fadeOut(
616 nr, ViewAnimationFactory.APPLY_EFFECT, PREFERRED_DURATION));
617 }
618
619 return AnimationFactory.createSequence(deleteEdges, deleteNodes);
627 }
628
629
630
638 private GraphLayout calcLayout(
639 final Set newNodes,
640 final Set newEdges
641 ) {
642 final DataMap hints = Maps.createDataMap(new WeakHashMap());
643 final IncrementalHierarchicLayouter ihl = new IncrementalHierarchicLayouter();
644 ihl.setLayoutMode(IncrementalHierarchicLayouter.LAYOUT_MODE_INCREMENTAL);
645 ihl.setOrthogonallyRouted(true);
646 final IncrementalHintsFactory hf = ihl.createIncrementalHintsFactory();
647 for (Iterator it = newNodes.iterator(); it.hasNext();) {
648 final Object node = it.next();
649 hints.set(node, hf.createLayerIncrementallyHint(node));
650 }
651 for (Iterator it = newEdges.iterator(); it.hasNext();) {
652 final Object edge = it.next();
653 hints.set(edge, hf.createSequenceIncrementallyHint(edge));
654 if (((Edge) edge).source().degree() == 1) {
655 final Node node = ((Edge) edge).source();
656 hints.set(node, hf.createLayerIncrementallyHint(node));
657 }
658 if (((Edge) edge).target().degree() == 1) {
659 final Node node = ((Edge) edge).target();
660 hints.set(node, hf.createLayerIncrementallyHint(node));
661 }
662 }
663 graph.addDataProvider(IncrementalHierarchicLayouter.INCREMENTAL_HINTS_DPKEY, hints);
664 try {
665 return (new BufferedLayouter(ihl)).calcLayout(graph);
666 } finally {
667 graph.removeDataProvider(IncrementalHierarchicLayouter.INCREMENTAL_HINTS_DPKEY);
668 }
669 }
670
671
672 public static void main(String[] args) {
673 EventQueue.invokeLater(new Runnable() {
674 public void run() {
675 Locale.setDefault(Locale.ENGLISH);
676 initLnF();
677 (new AnimatedStructuralChangesDemo()).start();
678 }
679 });
680 }
681
682
683 private abstract static class Command implements AnimationListener {
684 public void animationPerformed(final AnimationEvent e) {
685 if (e.getHint() == AnimationEvent.END) {
686 execute();
687 }
688 }
689
690 abstract void execute();
691 }
692 }
693