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