1   /****************************************************************************
2    **
3    ** This file is part of yFiles-2.9. 
4    ** 
5    ** yWorks proprietary/confidential. Use is subject to license terms.
6    **
7    ** Redistribution of this file or of an unauthorized byte-code version
8    ** of this file is strictly forbidden.
9    **
10   ** Copyright (c) 2000-2011 by yWorks GmbH, Vor dem Kreuzberg 28, 
11   ** 72070 Tuebingen, Germany. All rights reserved.
12   **
13   ***************************************************************************/
14  package demo.layout.organic;
15  
16  import y.algo.Bfs;
17  import y.anim.AnimationFactory;
18  import y.anim.AnimationObject;
19  import y.base.Edge;
20  import y.base.EdgeCursor;
21  import y.base.EdgeList;
22  import y.base.EdgeMap;
23  import y.base.ListCell;
24  import y.base.Node;
25  import y.base.NodeCursor;
26  import y.base.NodeList;
27  import y.base.NodeMap;
28  import y.base.YList;
29  import y.geom.YPoint;
30  import y.util.DefaultMutableValue2D;
31  import y.util.Maps;
32  import y.view.Drawable;
33  import y.view.EdgeRealizer;
34  import y.view.Graph2D;
35  import y.view.NodeRealizer;
36  import y.view.ViewAnimationFactory;
37  
38  import javax.swing.SwingUtilities;
39  import java.util.Locale;
40  
41  /**
42   * This demo is an extension of {@link demo.layout.organic.NavigationDemo}.
43   * <br>
44   * In this demo, changing the visible subgraph is now accompanied by animated
45   * fade-in and fade-out effects for appearing and disappearing nodes and edges.
46   * Additionally, the initial positions of nodes that newly appear in the graph
47   * are set to the position of their already visible parent node.
48   *
49   * @see <a href="http://docs.yworks.com/yfiles/doc/developers-guide/interactive_organic_layouter.html#interactive_organic_layouter">Section Interactive Organic Layout</a> in the yFiles for Java Developer's Guide
50   */
51  public class AnimatedNavigationDemo extends NavigationDemo {
52    private NodeMap hiddenNodesMap;
53    private EdgeMap hiddenEdgesMap;
54  
55    public static void main(String[] args) {
56      SwingUtilities.invokeLater(new Runnable() {
57        public void run() {
58          Locale.setDefault(Locale.ENGLISH);
59          initLnF();
60          final AnimatedNavigationDemo navigationDemo = new AnimatedNavigationDemo();
61          navigationDemo.start("Animated Navigation Demo");
62          navigationDemo.moveFirstNodeToCenter();
63        }
64      });
65    }
66  
67    public AnimatedNavigationDemo() {
68      // remember which nodes are hidden
69      hiddenNodesMap = view.getGraph2D().createNodeMap();
70      hiddenEdgesMap = view.getGraph2D().createEdgeMap();
71    }
72  
73    /**
74     * This list contains the history of the center nodes.
75     */
76    private YList history = new YList();
77  
78    protected void moveToCenter(final Node newCenterNode, boolean animated) {
79      if (!SwingUtilities.isEventDispatchThread()) {
80        throw new IllegalStateException("not in dispatch thread");
81      }
82      this.centerNode = newCenterNode;
83  
84      // The new centered node is "pinned" It will no longer be moved by the layouter.
85      layouter.setInertia(newCenterNode, 1);
86      if (history.size() < 4) {
87        history.addFirst(newCenterNode);
88      } else {
89        ListCell lastCell = history.lastCell();
90        // The "older" centered nodes are moveable again.
91        layouter.setInertia((Node) lastCell.getInfo(), 0);
92        history.removeCell(lastCell);
93        lastCell.setInfo(newCenterNode);
94        history.addFirstCell(lastCell);
95      }
96  
97      //The elements that will change state
98      NodeList fadeInNodes = new NodeList();
99      NodeList fadeOutNodes = new NodeList();
100     EdgeList fadeInEdges = new EdgeList();
101     EdgeList fadeOutEdges = new EdgeList();
102 
103     // the elements that will be hidden finally
104     final EdgeList hiddenEdges = new EdgeList();
105     final NodeList hiddenNodes = new NodeList();
106 
107     final Graph2D graph = view.getGraph2D();
108 
109     // unhide the whole graph to perform the calculation
110     graphHider.unhideAll();
111 
112     // do a Bfs run
113     // prepare a NodeMap
114     final int[] data = new int[graph.N()];
115     NodeMap layerMap = Maps.createIndexNodeMap(data);
116     // calculate the first 4 layers
117     NodeList[] layerLists = Bfs.getLayers(graph, new NodeList(newCenterNode), false, layerMap, 4);
118 
119     // get the new nodes to display
120     for (int i = 0; i < Math.min(layerLists.length, 3); i++) {
121       for (NodeCursor nc = layerLists[i].nodes(); nc.ok(); nc.next()) {
122         final Node node = nc.node();
123         final boolean wasHidden = hiddenNodesMap.getBool(node);
124         if (wasHidden) {
125           fadeInNodes.add(node);
126         }
127       }
128     }
129 
130     // update the visibility marker and add the nodes to hide
131     for (NodeCursor nc = graph.nodes(); nc.ok(); nc.next()) {
132       final Node node = nc.node();
133       final boolean wasHidden = hiddenNodesMap.getBool(node);
134       final int layer = layerMap.getInt(node);
135       if (layer >= 0 && layer < 3) {
136         // should be visible
137         if (wasHidden) {
138           hiddenNodesMap.setBool(node, false);
139         }
140       } else {
141         hiddenNodes.add(node);
142         // should be invisible
143         if (!wasHidden) {
144           fadeOutNodes.add(node);
145           hiddenNodesMap.setBool(node, true);
146         }
147       }
148     }
149 
150     // update the visibility of the edges and sort out which ones to hide and which ones to insert
151     for (EdgeCursor ec = graph.edges(); ec.ok(); ec.next()) {
152       final Edge edge = ec.edge();
153       final boolean wasHidden = hiddenEdgesMap.getBool(edge);
154       final boolean shouldBeHidden = hiddenNodesMap.getBool(edge.source()) || hiddenNodesMap.getBool(edge.target());
155       if (shouldBeHidden) {
156         hiddenEdges.add(edge);
157         if (!wasHidden) {
158           fadeOutEdges.add(edge);
159           hiddenEdgesMap.setBool(edge, true);
160         }
161       } else {
162         if (wasHidden) {
163           fadeInEdges.add(edge);
164           hiddenEdgesMap.setBool(edge, false);
165         }
166       }
167     }
168 
169     //calculate the camera movement.
170     double x;
171     double y;
172 
173     //If we have valid informations from the layouter, use them.
174     YPoint location = layouter.getCenter(newCenterNode);
175     if (location != null) {
176       x = location.getX();
177       y = location.getY();
178     } else { //Fallback: Use the informations from the realizers (e.g. at the beginning)
179       NodeRealizer realizer = view.getGraph2D().getRealizer(newCenterNode);
180       x = realizer.getX();
181       y = realizer.getY();
182     }
183 
184     if (animated) {
185       // now perform the animations
186       // ... for camera
187       animateCamera(x, y);
188 
189       // for the elements that go away....
190       fadeOutEdges(fadeOutEdges);
191       fadeOutNodes(fadeOutNodes);
192 
193       // and for the new elements
194       fadeInEdges(fadeInEdges);
195       fadeInNodes(graph, layerMap, fadeInNodes);
196     } else {
197       view.setCenter(x, y);
198     }
199 
200     //Hide the edges that have been marked
201     graphHider.hide(hiddenEdges);
202 
203     //Now hide the nodes (and edges) that shall be hidden
204     graphHider.hide(hiddenNodes);
205 
206     // make sure the layout algorithm starts its work
207     this.layouter.syncStructure();
208     this.layouter.wakeUp();
209   }
210 
211   private void animateCamera(double x, double y) {
212     //An AnimationObject representing the movement of the camera is created
213     AnimationObject animationObject = factory.moveCamera(DefaultMutableValue2D.create(x, y), PREFERRED_DURATION);
214     //The movement is eased (in and out)
215     AnimationObject easedAnimation = AnimationFactory.createEasedAnimation(animationObject, 0.15, 0.25);
216     animationPlayer.animate(easedAnimation);
217   }
218 
219   private void fadeOutNodes(NodeList fadeOutNodes) {
220     for (NodeCursor nc = fadeOutNodes.nodes(); nc.ok(); nc.next()) {
221       final Node node = nc.node();
222       //Fade out visible node
223       NodeRealizer realizer = view.getGraph2D().getRealizer(node);
224       final Drawable nodeDrawable = ViewAnimationFactory.createDrawable(realizer);
225       animationPlayer.animate(factory.fadeOut(nodeDrawable, PREFERRED_DURATION));
226     }
227   }
228 
229   private void fadeInNodes(Graph2D graph, NodeMap layerMap, NodeList fadeInNodes) {
230     for (NodeCursor nc = fadeInNodes.nodes(); nc.ok(); nc.next()) {
231       final Node node = nc.node();
232 
233       // calculate the new position for the node
234       final int myLayer = layerMap.getInt(node);
235       double posX = 0;
236       double posY = 0;
237 
238       // determine the barycenter of all parent neighbour nodes
239       int count = 0;
240       for (NodeCursor nc2 = node.neighbors(); nc2.ok(); nc2.next()) {
241         final Node neighbour = nc2.node();
242         if (layerMap.getInt(neighbour) < myLayer) {
243           count++;
244           posX += graph.getCenterX(neighbour);
245           posY += graph.getCenterY(neighbour);
246         }
247       }
248 
249       // get the realizer
250       NodeRealizer nodeRealizer = view.getGraph2D().getRealizer(node);
251 
252       // update its position
253       if (count > 0) {
254         posX /= count;
255         posY /= count;
256         //copy the coords of the parent...
257         //.. to the realizer...
258         nodeRealizer.setCenter(posX, posY);
259         //... and to the data structure
260         layouter.setCenter(node, posX, posY);
261       }
262 
263       //Fade the node in
264       animationPlayer.animate(factory.fadeIn(nodeRealizer, PREFERRED_DURATION)); //<-------- FADE IN
265     }
266   }
267 
268   private void fadeOutEdges(EdgeList fadeOutEdges) {
269     for (EdgeCursor ec = fadeOutEdges.edges(); ec.ok(); ec.next()) {
270       final Edge edge = ec.edge();
271       EdgeRealizer realizer = view.getGraph2D().getRealizer(edge);
272       final Drawable edgeDrawable = ViewAnimationFactory.createDrawable(realizer);
273       animationPlayer.animate(factory.fadeOut(edgeDrawable, PREFERRED_DURATION));//<--------   FADE OUT
274     }
275   }
276 
277   private void fadeInEdges(EdgeList fadeInEdges) {
278     for (EdgeCursor ec = fadeInEdges.edges(); ec.ok(); ec.next()) {
279       final Edge edge = ec.edge();
280       EdgeRealizer edgeRealizer = view.getGraph2D().getRealizer(edge);
281       animationPlayer.animate(factory.fadeIn(edgeRealizer, PREFERRED_DURATION));  //<-------- FADE IN
282     }
283   }
284 }
285