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;
29  
30  import demo.view.DemoBase;
31  import demo.view.DemoDefaults;
32  import y.algo.GraphConnectivity;
33  import y.algo.AbortHandler;
34  import y.algo.AlgorithmAbortedException;
35  import y.base.Node;
36  import y.base.NodeCursor;
37  import y.base.NodeList;
38  import y.layout.BufferedLayouter;
39  import y.layout.Layouter;
40  import y.layout.hierarchic.IncrementalHierarchicLayouter;
41  import y.layout.organic.SmartOrganicLayouter;
42  import y.layout.orthogonal.OrthogonalLayouter;
43  import y.layout.random.RandomLayouter;
44  import y.view.Graph2D;
45  import y.view.Graph2DLayoutExecutor;
46  
47  import javax.swing.AbstractAction;
48  import javax.swing.Action;
49  import javax.swing.BorderFactory;
50  import javax.swing.Box;
51  import javax.swing.JButton;
52  import javax.swing.JComboBox;
53  import javax.swing.JDialog;
54  import javax.swing.JLabel;
55  import javax.swing.JOptionPane;
56  import javax.swing.JPanel;
57  import javax.swing.JProgressBar;
58  import javax.swing.JRootPane;
59  import javax.swing.JToolBar;
60  import javax.swing.SwingUtilities;
61  import java.awt.BorderLayout;
62  import java.awt.Dimension;
63  import java.awt.EventQueue;
64  import java.awt.event.ActionEvent;
65  import java.util.Arrays;
66  import java.util.Comparator;
67  import java.util.Locale;
68  import java.util.Random;
69  
70  /**
71   * Demonstrates how the {@link Graph2DLayoutExecutor} can be used to apply
72   * layout algorithms to a {@link Graph2D}.
73   *
74   * @see <a href="http://docs.yworks.com/yfiles/doc/api/index.html#/dguide/viewer_layout#cls_Graph2DLayoutExecutor" target="_blank">Section Class Graph2DLayoutExecutor</a> in the yFiles for Java Developer's Guide
75   */
76  public class Graph2DLayoutExecutorDemo extends DemoBase
77  {
78    // the label that shows some status (if non-blocking)
79    private JLabel statusLabel;
80    // the progress bar that shows indeterminate progress (if non-blocking)
81    private JProgressBar progressBar = new JProgressBar();
82  
83    // the types of execution
84    private JComboBox layoutExecutionTypeBox;
85  
86    // the type of layouter
87    private JComboBox layouterBox;
88  
89    public Graph2DLayoutExecutorDemo() {
90      //build sample graph
91      buildGraph( view.getGraph2D() );
92  
93      view.setViewPoint2D(-200.0, -200.0);
94    }
95  
96    protected void configureDefaultRealizers() {
97      // painting shadows is expensive and not well suited for graphs with many
98      // nodes such as this demo's sample graph
99      DemoDefaults.registerDefaultNodeConfiguration(false);
100     DemoDefaults.configureDefaultRealizers(view);
101   }
102 
103   /**
104    * Overwritten to add the status label and the progress bar.
105    */
106   public void addContentTo(JRootPane rootPane) {
107     this.statusLabel = new JLabel("Status");
108     final Dimension minimumSize = this.statusLabel.getMinimumSize();
109     this.statusLabel.setMinimumSize(new Dimension(Math.max(200, minimumSize.width), minimumSize.height));
110     final JPanel panel = new JPanel();
111     panel.add(this.statusLabel, BorderLayout.LINE_START);
112     this.progressBar.setMaximum(100);
113     this.progressBar.setMinimum(0);
114     this.progressBar.setValue(0);
115     panel.add(progressBar, BorderLayout.CENTER);
116     getContentPane().add(panel, BorderLayout.SOUTH);
117     super.addContentTo(rootPane);
118   }
119 
120   /** Creates a relatively large random graph to give the layout algorithms something to chew. */
121   void buildGraph(Graph2D graph) {
122     graph.clear();
123     Node[] nodes = new Node[800];
124     for(int i = 0; i < nodes.length; i++)
125     {
126       nodes[i] = graph.createNode();
127     }
128     Random random = new Random(0L);
129     for ( int i = 0; i < nodes.length; i++ ) {
130 
131       int edgeCount;
132 
133       if (random.nextInt(10) == 0) {
134         edgeCount = 4 + random.nextInt(5);
135       } else {
136         edgeCount = random.nextInt(3);
137       }
138 
139       for ( int j = 0; j < edgeCount; j++ ) {
140         graph.createEdge( nodes[ i ], nodes[ random.nextInt(nodes.length) ] );
141       }
142     }
143 
144     // remove all components except the largest one
145     final NodeList[] components = GraphConnectivity.connectedComponents(graph);
146     Arrays.sort(components, new Comparator() {
147       public int compare(final Object o1, final Object o2) {
148         return ((NodeList) o2).size() - ((NodeList) o1).size();
149       }
150     });
151     for (int i = components.length -1; i > 0; i--) {
152       for (NodeCursor nc = components[i].nodes(); nc.ok(); nc.next()) {
153         graph.removeNode(nc.node());
154       }
155     }
156 
157     for (NodeCursor nc = graph.nodes(); nc.ok(); nc.next()) {
158       final Node node = nc.node();
159       graph.getRealizer(node).setLabelText(Integer.toString(node.index()));
160     }
161 
162     (new BufferedLayouter(new RandomLayouter())).doLayout(graph);
163   }
164 
165   /**
166    * Adds an extra layout action to the toolbar
167    */
168   protected JToolBar createToolBar() {
169     final Action layoutAction = new AbstractAction(
170             "Layout", SHARED_LAYOUT_ICON) {
171       public void actionPerformed(ActionEvent e) {
172         applyLayout();
173       }
174     };
175 
176     // chooser for the layouter
177     layouterBox = new JComboBox(new Object[]{"Hierarchic", "Organic", "Orthogonal"});
178     layouterBox.setMaximumSize(layouterBox.getPreferredSize());
179     layouterBox.setSelectedIndex(0);
180 
181     // chooser for the execution type.
182     layoutExecutionTypeBox = new JComboBox(
183       new Object[]{"Animated", "AnimatedThreaded", "Buffered", "Threaded", "Unbuffered", "AnimatedInOwnThread"});
184     layoutExecutionTypeBox.setMaximumSize(layoutExecutionTypeBox.getPreferredSize());
185     layoutExecutionTypeBox.setSelectedIndex(1);
186 
187     final JToolBar toolBar = super.createToolBar();
188     toolBar.addSeparator();
189     toolBar.add(createActionControl(layoutAction));
190     toolBar.addSeparator(TOOLBAR_SMALL_SEPARATOR);
191     toolBar.add(layouterBox);
192     toolBar.addSeparator(TOOLBAR_SMALL_SEPARATOR);
193     toolBar.add(layoutExecutionTypeBox);
194 
195     return toolBar;
196   }
197 
198   /**
199    * Configures and invokes a layout algorithm
200    */
201   void applyLayout() {
202     Layouter layouter = createLayouter();
203     switch (layoutExecutionTypeBox.getSelectedIndex()) {
204       case 0:
205         applyLayoutAnimated(layouter);
206         break;
207       case 1:
208         applyLayoutAnimatedThreaded(layouter);
209         break;
210       case 2:
211         applyLayoutBuffered(layouter);
212         break;
213       case 3:
214         applyLayoutThreaded(layouter);
215         break;
216       case 4:
217         applyLayoutUnbuffered(layouter);
218         break;
219       case 5:
220         applyLayoutAnimatedInOwnThread(layouter);
221         break;
222     }
223   }
224 
225   /**
226    * Creates and returns a Layouter instance according to the given layout options.
227    */
228   Layouter createLayouter() {
229     switch (layouterBox.getSelectedIndex()) {
230       default:
231       case 0:
232         return new IncrementalHierarchicLayouter();
233       case 1:
234         final SmartOrganicLayouter organicLayouter = new SmartOrganicLayouter();
235         organicLayouter.setQualityTimeRatio(1.0);
236         organicLayouter.setMaximumDuration(2L * 60L * 1000L);
237         organicLayouter.setMultiThreadingAllowed(true);
238         return organicLayouter;
239       case 2:
240         return new OrthogonalLayouter();
241     }
242   }
243 
244   /**
245    * Applies the given layout algorithm to the graph
246    * This is done in a separate Thread asynchronously.
247    * Although the view and UI is responsive direct mouse and keyboard input is blocked.
248    * The layout process can be canceled and even killed through a dialog that is spawned.
249    */
250   void applyLayoutAnimatedThreaded(final Layouter layouter) {
251     this.progressBar.setIndeterminate(true);
252     final Graph2DLayoutExecutor layoutExecutor = new Graph2DLayoutExecutor(Graph2DLayoutExecutor.ANIMATED_THREADED);
253     // set a slow animation, so that the animation can easily be canceled.
254     layoutExecutor.getLayoutMorpher().setPreferredDuration(3000L);
255     layoutExecutor.getLayoutMorpher().setEasedExecution(true);
256     layoutExecutor.getLayoutMorpher().setSmoothViewTransform(true);
257     // lock the view so that the graph cannot be edited.
258     layoutExecutor.setLockingView(true);
259 
260     final AbortHandler handler = AbortHandler.createForGraph(view.getGraph2D());
261     // This might be an "old" handler instance from a previous call
262     // to applyLayoutAnimatedThreaded, applyLayoutAnimatedInOwnThread, or
263     // applyLayoutThreaded. Resetting the handler prevents an an undesired early
264     // termination of the new layout calculation due to previous stop or cancel
265     // requests.
266     handler.reset();
267 
268     final JDialog dialog = newCancelDialog(handler, layouter.getClass().getName());
269 
270     // the following method will return immediately and the layout and animation is performed in a new thread
271     // asynchronously.
272     final Graph2DLayoutExecutor.LayoutThreadHandle handle = layoutExecutor.doLayout(view, layouter, new Runnable() {
273       public void run() {
274         dialog.dispose();
275         progressBar.setIndeterminate(false);
276         statusLabel.setText("Layout Done");
277       }
278     }, new ExceptionHandler());
279 
280     // this is visible because the layout is not blocking (this) EDT
281     this.statusLabel.setText("Layout is running");
282 
283 
284     if (handle.isRunning()) {
285       dialog.setVisible(true);
286     }
287   }
288 
289   /**
290    * Applies the given layout algorithm to the graph
291    * This is done synchronously blocking the calling Thread, thus leaving the view unresponsive during the layout.
292    */
293   void applyLayoutBuffered(final Layouter layouter){
294     final AbortHandler handler = AbortHandler.createForGraph(view.getGraph2D());
295     // This might be an "old" handler instance from a previous call
296     // to applyLayoutAnimatedThreaded, applyLayoutAnimatedInOwnThread, or
297     // applyLayoutThreaded. Resetting the handler prevents an an undesired early
298     // termination of the new layout calculation due to previous stop or cancel
299     // requests.
300     handler.reset();
301 
302     final Graph2DLayoutExecutor layoutExecutor = new Graph2DLayoutExecutor(Graph2DLayoutExecutor.BUFFERED);
303     layoutExecutor.doLayout(view, layouter);
304   }
305 
306   /**
307    * Applies the given layout algorithm to the graph in an animated fashion.
308    * This is done synchronously blocking the calling Thread, thus leaving the view unresponsive during the layout
309    * and animation.
310    */
311   void applyLayoutAnimated(final Layouter layouter){
312     // this won't be visible to the user because the EDT is blocked.
313     statusLabel.setText("Starting Animated Blocking Layout");
314     progressBar.setIndeterminate(true);
315     
316     final AbortHandler handler = AbortHandler.createForGraph(view.getGraph2D());
317     // This might be an "old" handler instance from a previous call
318     // to applyLayoutAnimatedThreaded, applyLayoutAnimatedInOwnThread, or
319     // applyLayoutThreaded. Resetting the handler prevents an an undesired early
320     // termination of the new layout calculation due to previous stop or cancel
321     // requests.
322     handler.reset();
323     
324     try {
325       final Graph2DLayoutExecutor layoutExecutor = new Graph2DLayoutExecutor(Graph2DLayoutExecutor.ANIMATED);
326       layoutExecutor.doLayout(view, layouter);
327     } finally {
328       progressBar.setIndeterminate(false);
329       statusLabel.setText("Animated Blocking Layout Done.");
330     }
331   }
332 
333   /**
334    * Applies the given layout algorithm to the graph in an animated fashion using a blocking call
335    * from a separate newly spawned thread.
336    * This leaves the view responsive, but the view is still editable during the layout.
337    */
338   void applyLayoutAnimatedInOwnThread(final Layouter layouter){
339     statusLabel.setText("Starting own layout thread.");
340     progressBar.setIndeterminate(true);
341     final Graph2DLayoutExecutor layoutExecutor = new Graph2DLayoutExecutor(Graph2DLayoutExecutor.ANIMATED);
342 
343     final AbortHandler handler = AbortHandler.createForGraph(view.getGraph2D());
344     // This might be an "old" handler instance from a previous call
345     // to applyLayoutAnimatedThreaded, applyLayoutAnimatedInOwnThread, or
346     // applyLayoutThreaded. Resetting the handler prevents an an undesired early
347     // termination of the new layout calculation due to previous stop or cancel
348     // requests.
349     handler.reset();
350 
351     final JDialog dialog = newCancelDialog(handler, layouter.getClass().getName());
352 
353     new Thread(new Runnable() {
354       public void run() {
355         try {
356           layoutExecutor.doLayout(view, layouter, null, new ExceptionHandler());
357         } finally {
358           SwingUtilities.invokeLater(new Runnable() {
359             public void run() {
360               dialog.dispose();
361               statusLabel.setText("Layout Thread Finished.");
362               progressBar.setIndeterminate(false);
363             }
364           });
365         }
366       }
367     }).start();
368 
369     dialog.setVisible(true);
370   }
371 
372   /**
373    * Runs the layout in a separate thread, leaving the view responsive
374    * but the view is still editable during the layout.
375    */
376   void applyLayoutThreaded(final Layouter layouter){
377     statusLabel.setText("Starting threaded layout");
378     progressBar.setIndeterminate(true);
379     final Graph2DLayoutExecutor layoutExecutor = new Graph2DLayoutExecutor(Graph2DLayoutExecutor.THREADED);
380 
381     final AbortHandler handler = AbortHandler.createForGraph(view.getGraph2D());
382     // This might be an "old" handler instance from a previous call
383     // to applyLayoutAnimatedThreaded, applyLayoutAnimatedInOwnThread, or
384     // applyLayoutThreaded. Resetting the handler prevents an an undesired early
385     // termination of the new layout calculation due to previous stop or cancel
386     // requests.
387     handler.reset();
388 
389     final JDialog dialog = newCancelDialog(handler, layouter.getClass().getName());
390 
391     layoutExecutor.doLayout(view, layouter, new Runnable() {
392       public void run() {
393         dialog.dispose();
394         statusLabel.setText("Layout Returned");
395         progressBar.setIndeterminate(false);
396       }
397     }, new ExceptionHandler());
398     statusLabel.setText("Return from doLayout()");
399 
400     dialog.setVisible(true);
401   }
402 
403   void applyLayoutUnbuffered(final Layouter layouter) {
404     final AbortHandler handler = AbortHandler.createForGraph(view.getGraph2D());
405     // This might be an "old" handler instance from a previous call
406     // to applyLayoutAnimatedThreaded, applyLayoutAnimatedInOwnThread, or
407     // applyLayoutThreaded. Resetting the handler prevents an an undesired early
408     // termination of the new layout calculation due to previous stop or cancel
409     // requests.
410     handler.reset();
411 
412     final Graph2DLayoutExecutor layoutExecutor = new Graph2DLayoutExecutor(Graph2DLayoutExecutor.UNBUFFERED);
413     layoutExecutor.doLayout(view, layouter);
414   }
415 
416   /**
417    * Creates a simply dialog for aborting layout calculation.
418    * @param layoutName the name of the layout algorithm used.
419    * @return a dialog for aborting layout calculation.
420    */
421   private JDialog newCancelDialog(
422           final AbortHandler handler, final String layoutName
423   ) {
424     final JDialog dialog = new JDialog(JOptionPane.getRootFrame(), "");
425     final Box box = Box.createVerticalBox();
426     box.setBorder(BorderFactory.createEmptyBorder(6, 6, 6, 6));
427     final JLabel label = new JLabel("Layout Running [" + layoutName + "].");
428     box.add(label);
429     box.add(Box.createVerticalStrut(12));
430     box.add(new JButton(new AbstractAction("Stop") {
431       private boolean stopped;
432       public void actionPerformed(ActionEvent e) {
433         // first, request an early but graceful termination
434         if (!stopped) {
435           handler.stop();
436           statusLabel.setText("Stopping.");
437           label.setText("Stopping Thread.[" + layoutName + "].");
438           ((JButton)e.getSource()).setText("Cancel");
439           stopped = true;
440         } else {
441           // graceful termination is not fast enough, request immediate
442           // termination
443           handler.cancel();
444           setEnabled(false);
445           statusLabel.setText("Cancelling.");
446         }
447       }
448     }));
449     dialog.getContentPane().add(box);
450     dialog.setLocationRelativeTo(view);
451     dialog.pack();
452     return dialog;
453   }
454 
455   public static void main(String[] args) {
456     EventQueue.invokeLater(new Runnable() {
457       public void run() {
458         Locale.setDefault(Locale.ENGLISH);
459         initLnF();
460         (new Graph2DLayoutExecutorDemo()).start();
461       }
462     });
463   }
464 
465   /**
466    * Reports exceptions that occur during a layout calculation.
467    */
468   private class ExceptionHandler implements Graph2DLayoutExecutor.ExceptionListener {
469     public void exceptionHappened( final Throwable t ) {
470       if (t instanceof AlgorithmAbortedException) {
471         statusLabel.setText("Layout cancelled.");
472       } else {
473         t.printStackTrace(System.err);
474         statusLabel.setText("Exception Happened.");
475       }
476     }
477   }
478 }
479