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