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 demo.view.DemoBase;
17  import y.algo.Bfs;
18  import y.anim.AnimationFactory;
19  import y.anim.AnimationObject;
20  import y.anim.AnimationPlayer;
21  import y.base.Node;
22  import y.base.NodeCursor;
23  import y.base.NodeList;
24  import y.base.NodeMap;
25  import y.geom.YPoint;
26  import y.layout.CopiedLayoutGraph;
27  import y.layout.LayoutTool;
28  import y.layout.organic.InteractiveOrganicLayouter;
29  import y.util.DefaultMutableValue2D;
30  import y.util.GraphHider;
31  import y.view.DefaultGraph2DRenderer;
32  import y.view.EditMode;
33  import y.view.Graph2DViewRepaintManager;
34  import y.view.NodeRealizer;
35  import y.view.TooltipMode;
36  import y.view.ViewAnimationFactory;
37  
38  import javax.swing.Action;
39  import javax.swing.SwingUtilities;
40  import java.awt.event.ActionEvent;
41  import java.awt.EventQueue;
42  import java.util.Locale;
43  
44  /**
45   * This demo shows how to interactively navigate through a large
46   * graph by showing only the neighbourhood of a focused node (proximity browsing).
47   * To focus another node the user can simply click it.
48   * By selecting a new focus node the visible part of the graph will be automatically adjusted.
49   * <br>
50   * In this demo the layout of the displayed subgraph is
51   * controlled by {@link y.layout.organic.InteractiveOrganicLayouter}. This layout variant
52   * allows to automatically layout the graph and manually change its node positions
53   * at the same time.
54   * <br>
55   * The {@link demo.layout.organic.AnimatedNavigationDemo} extends the functionality of this demo
56   * and adds animation support and more.
57   *
58   * @see <a href="http://docs.yworks.com/yfiles/doc/developers-guide/interactive_organic_layouter.html">Section Interactive Organic Layout</a> in the yFiles for Java Developer's Guide
59   */
60  public class NavigationDemo extends DemoBase {
61    protected static final long PREFERRED_DURATION = 1000;
62  
63    /**
64     * The layouter runs in its own thread.
65     */
66    protected InteractiveOrganicLayouter layouter;
67    /**
68     * The actual focused node
69     */
70    protected Node centerNode;
71  
72    protected GraphHider graphHider;
73  
74    /**
75     * The animationPlayer is used for camera movements and to update the positions.
76     */
77    protected AnimationPlayer animationPlayer;
78    protected ViewAnimationFactory factory;
79    private Thread layoutThread;
80  
81    public static void main(String[] args) {
82      EventQueue.invokeLater(new Runnable() {
83        public void run() {
84          Locale.setDefault(Locale.ENGLISH);
85          initLnF();
86          (new NavigationDemo()).start("Navigation Demo");
87        }
88      });
89    }
90  
91    public NavigationDemo() {
92      
93      
94      SwingUtilities.invokeLater(new Runnable() {
95        public void run() {
96          moveFirstNodeToCenter();        
97        }
98      });
99      
100   }
101 
102   protected void initialize() {
103     ((DefaultGraph2DRenderer) view.getGraph2DRenderer()).setDrawEdgesFirst(true);
104     view.setPaintDetailThreshold(0.0);
105     Graph2DViewRepaintManager repaintManager = new Graph2DViewRepaintManager(view);
106     factory = new ViewAnimationFactory(repaintManager);
107     factory.setQuality(ViewAnimationFactory.HIGH_PERFORMANCE);
108     animationPlayer = factory.createConfiguredPlayer();
109     animationPlayer.setFps(25);
110 
111     graphHider = new GraphHider(view.getGraph2D());
112     graphHider.setFireGraphEventsEnabled(true);
113 
114     loadInitialGraph();
115 
116     initLayouter();
117     initUpdater(repaintManager);
118   }
119 
120   public void dispose() {
121     if (animationPlayer != null) {
122       animationPlayer.stop();
123     }
124     if (layouter != null) {
125       layouter.stop();
126     }
127     if (layoutThread != null) {
128       layoutThread.interrupt();
129     }
130   }
131 
132   protected EditMode createEditMode() {
133     EditMode editMode = new EditMode() {
134       protected void nodeClicked(Node v) {
135         moveToCenter(v, true);
136       }
137     };
138     editMode.allowBendCreation(false);
139     editMode.allowEdgeCreation(false);
140     editMode.allowMoveLabels(false);
141     editMode.allowMovePorts(false);
142     editMode.allowNodeCreation(false);
143     editMode.allowNodeEditing(false);
144     editMode.allowResizeNodes(false);
145     editMode.setMoveSelectionMode(new InteractiveMoveSelectionMode(layouter));
146     return editMode;
147   }
148 
149   /**
150    * Overwritten to disable tooltips.
151    */
152   protected TooltipMode createTooltipMode() {
153     return null;
154   }
155 
156   protected void moveFirstNodeToCenter() {
157     if (view.getGraph2D().nodeCount() > 0) {
158       moveToCenter(view.getGraph2D().firstNode(), false);
159     }
160   }
161 
162   /**
163    * Creates and starts an animation object that updates the positions of the nodes.
164    * {@link #updatePositions()}
165    * @param repaintManager
166    */
167   protected void initUpdater(final Graph2DViewRepaintManager repaintManager) {
168     // use a tweaked AnimationObject as javax.swing.Timer replacement
169     final AnimationObject updater = new AnimationObject() {
170       public long preferredDuration() {
171         return Long.MAX_VALUE;
172       }
173 
174       public void calcFrame(double time) {
175         if (updatePositions()) {
176           repaintManager.invalidate();
177         }
178       }
179 
180       public void initAnimation() {
181       }
182 
183       public void disposeAnimation() {
184       }
185     };
186     animationPlayer.animate(updater);
187   }
188 
189   /**
190    * Loads the initial graph that is used in this demo.
191    */
192   protected void loadInitialGraph() {
193     loadGraph("resource/peopleNav.graphml");
194     LayoutTool.resetPaths(view.getGraph2D());
195     view.setZoom(0.80);
196     view.updateView();
197   }
198 
199   /**
200    * Initializes the {@link y.layout.organic.InteractiveOrganicLayouter} and starts a thread for the
201    * layouter.
202    */
203   protected void initLayouter() {
204     layouter = new InteractiveOrganicLayouter();
205 
206     //After two seconds the layouter will stop.
207     layouter.setMaxTime(2000);
208 
209     // propagate changes
210     //Use an instance of CopiedLayoutGraph to avoid race conditions with the layout thread
211     layoutThread = layouter.startLayout(new CopiedLayoutGraph(view.getGraph2D()));
212     layoutThread.setPriority(Thread.MIN_PRIORITY);
213   }
214 
215   /**
216    * This method is called by the pseudo AnimationObject created in {@link #initUpdater(y.view.Graph2DViewRepaintManager)}.
217    * It copies the information from the internal data structure of the layouter to the realizers of the nodes.<br>
218    *
219    * For "smooth movement" only a part of the delta between the position the layouter has calculated and the actual
220    * displayed position, is moved.
221    *
222    * @return whether the max movement is bigger than 0.
223    */
224   protected boolean updatePositions() {
225     if (layouter == null || !layouter.isRunning()) {
226       return false;
227     }
228     double maxMovement = layouter.commitPositionsSmoothly(50, 0.15);
229     return maxMovement > 0;
230   }
231 
232   /**
233    * This method is called whenever a user clicks at a node.
234    * The new node is "centered" and the corresponding sector of the graph is displayed.
235    *
236    * @param newCenterNode
237    */
238   protected void moveToCenter(final Node newCenterNode, boolean animated) {
239     //The structure updater allows synchronized write access on the graph structure that is layoutet.
240     //So it is possible to add/remove nodes and edges or change values (e.g. the position) of the existing nodes.
241     //The changes are scheduled and commited later.
242     if (centerNode != null) {
243       //Make the old centered node movable
244       layouter.setInertia(centerNode, 0);
245     }
246     centerNode = newCenterNode;
247 
248     //the new centered node is "pinned" It will no longer be moved by the layouter
249     layouter.setInertia(newCenterNode, 1);
250 
251     NodeList hiddenNodes = new NodeList(graphHider.hiddenNodes());
252 
253     graphHider.unhideAll();
254 
255     NodeList toHide = new NodeList(view.getGraph2D().nodes());
256     NodeMap nodeMap = view.getGraph2D().createNodeMap();
257     NodeList[] layers = Bfs.getLayers(view.getGraph2D(), new NodeList(centerNode), false, nodeMap, 3);
258     view.getGraph2D().disposeNodeMap(nodeMap);
259     for (int i = 0; i < layers.length; i++) {
260       NodeList layer = layers[i];
261       toHide.removeAll(layer);
262     }
263 
264     graphHider.hide(toHide);
265 
266     // use "smart" initial placement for new elements
267     double centerX = view.getGraph2D().getCenterX(newCenterNode);
268     double centerY = view.getGraph2D().getCenterY(newCenterNode);
269     for (NodeCursor nc = hiddenNodes.nodes(); nc.ok(); nc.next()) {
270       if (view.getGraph2D().contains(nc.node())) {
271         view.getGraph2D().setCenter(nc.node(), centerX, centerY);
272         layouter.setCenter(nc.node(), centerX, centerY);
273       }
274     }
275 
276     layouter.wakeUp();
277 
278     //The camera movement.
279     double x;
280     double y;
281     YPoint point = layouter.getCenter(newCenterNode);
282     if (point != null) {
283       x = point.getX();
284       y = point.getY();
285     } else {
286       NodeRealizer realizer = view.getGraph2D().getRealizer(newCenterNode);
287       x = realizer.getX();
288       y = realizer.getY();
289     }
290 
291     if (animated) {
292       //An AnimationObject controlling the movement of the camera is created
293       AnimationObject animationObject = factory.moveCamera(DefaultMutableValue2D.create(x, y), PREFERRED_DURATION);
294       AnimationObject easedAnimation = AnimationFactory.createEasedAnimation(animationObject, 0.15, 0.25);
295       animationPlayer.animate(easedAnimation);
296     } else {
297       view.setCenter(x, y);
298     }
299 
300     //Now synchronize the structure updates with the copied graph that is layouted.
301     layouter.syncStructure();
302     layouter.wakeUp();
303   }
304 
305   /**
306    * Tells the layouter to update the position of the given node
307    */
308   protected void setPosition(Node node, double x, double y) {
309     if (layouter == null || !layouter.isRunning()) {
310       return;
311     }
312     layouter.setCenter(node, x, y);
313   }
314 
315 
316   protected Action createLoadAction() {
317     return new DemoBase.LoadAction() {
318       public void actionPerformed(ActionEvent e) {
319         layouter.stop();
320         centerNode = null;
321         super.actionPerformed(e);
322         graphHider = new GraphHider(view.getGraph2D());
323         LayoutTool.resetPaths(view.getGraph2D());
324 
325         initLayouter();
326 
327         moveFirstNodeToCenter();
328       }
329     };
330   }
331 
332   protected boolean isDeletionEnabled() {
333     return false;
334   }
335 }