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