1
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
86 public class AnimatedStructuralChangesDemo extends DemoBase {
87
90 private static final int PREFERRED_DURATION = 500;
91
92
95 private static final int MAX_EDGE_COUNT = 75;
96
97
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 view.fitContent();
122
123 showInitialGraph();
124 }
125 }
126 });
127
128 configureRealizers();
129 prepareInitialGraph();
130 }
131
132 private void configureRealizers() {
133 DemoDefaults.registerDefaultNodeConfiguration(false);
135 DemoDefaults.configureDefaultRealizers(view);
136 }
137
138
141 protected EditMode createEditMode() {
142 return null;
143 }
144
145
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
160 protected JToolBar createToolBar() {
161 return null;
162 }
163
164 private void prepareInitialGraph() {
165 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 graph.getDefaultNodeRealizer().setVisible(false);
188
189 if (graph.edgeCount() > 0) {
190 graph.setDefaultEdgeRealizer(graph.getRealizer(graph.firstEdge()).createCopy());
191 }
192 graph.getDefaultEdgeRealizer().setVisible(false);
195
196 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 player.addAnimationListener(factory.getRepaintManager());
223
224 player.addAnimationListener(new Command() {
225 void execute() {
226 AnimatedStructuralChangesDemo.this.execute();
228 }
229 });
230
231 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
245 private void execute() {
246 if (disposed) {
247 return;
248 }
249
250 final HashSet nodesToBeDeleted = new HashSet();
252 final HashSet edgesToBeDeleted = new HashSet();
253 markNodesForDeletion(nodesToBeDeleted, edgesToBeDeleted);
254 markEdgesForDeletion(edgesToBeDeleted);
255
256 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 final HashSet newNodes = new HashSet();
271 createNodes(newNodes);
272 final HashSet newEdges = new HashSet();
273 createEdges(newNodes, newEdges);
274
275 final GraphLayout gl = calcLayout(newNodes, newEdges);
280
281 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 final AnimationPlayer player = new AnimationPlayer(false);
296
297
304 final Command loop = new Command() {
306 void execute() {
307 player.removeAnimationListener(this);
309 player.removeAnimationListener(factory.getRepaintManager());
310
311 AnimatedStructuralChangesDemo.this.execute();
313 }
314 };
315
316 final Command animateCreate = new Command() {
318 void execute() {
319 player.removeAnimationListener(this);
321 player.removeAnimationListener(view);
322
323 player.addAnimationListener(loop);
325
326 player.addAnimationListener(factory.getRepaintManager());
330
331 player.animate(AnimationFactory.createSequence(
333 createCreateAnimation(newNodes, newEdges),
334 AnimationFactory.createPause(PREFERRED_DURATION)));
335 }
336 };
337
338 final Command animateMorphing = new Command() {
340 void execute() {
341 player.removeAnimationListener(this);
343 player.removeAnimationListener(factory.getRepaintManager());
344
345 player.addAnimationListener(animateCreate);
347
348 player.addAnimationListener(view);
351
352 player.animate(createMorphingAnimation(gl));
354 }
355 };
356
357 final Command animateDelete = new Command() {
359 void execute() {
360 player.addAnimationListener(animateMorphing);
362
363 player.addAnimationListener(factory.getRepaintManager());
367
368 player.animate(createDeleteAnimation(nodesToBeDeleted, edgesToBeDeleted));
370 }
371 };
372
373 animateDelete.execute();
374 }
375
376
381
382
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
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
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
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 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 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 edge = graph.createEdge(nodes[random.nextInt(oldCount)], nodes[oldCount + random.nextInt(newCount)]);
494 } else if (d < 0.9) {
495 edge = graph.createEdge(nodes[oldCount + random.nextInt(newCount)], nodes[random.nextInt(oldCount)]);
497 } else {
498 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 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 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
534
535
543 private AnimationObject createCreateAnimation(
544 final Collection newNodes,
545 final Collection newEdges
546 ) {
547 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 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 return AnimationFactory.createSequence(addNodes, addEdges);
571 }
572
573
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
601 private AnimationObject createDeleteAnimation(
602 final Set nodesToBeDeleted,
603 final Set edgesToBeDeleted
604 ) {
605 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 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 return AnimationFactory.createSequence(deleteEdges, deleteNodes);
635 }
636
637
638
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