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;
15  
16  import demo.view.DemoBase;
17  import demo.view.DemoDefaults;
18  import y.base.Node;
19  import y.layout.BufferedLayouter;
20  import y.layout.Layouter;
21  import y.layout.organic.SmartOrganicLayouter;
22  import y.layout.hierarchic.IncrementalHierarchicLayouter;
23  import y.layout.orthogonal.OrthogonalLayouter;
24  import y.layout.random.RandomLayouter;
25  import y.view.Graph2D;
26  import y.view.Graph2DLayoutExecutor;
27  
28  import javax.swing.AbstractAction;
29  import javax.swing.Action;
30  import javax.swing.JToolBar;
31  import javax.swing.Box;
32  import javax.swing.JLabel;
33  import javax.swing.JButton;
34  import javax.swing.JDialog;
35  import javax.swing.JOptionPane;
36  import javax.swing.BorderFactory;
37  import javax.swing.SwingUtilities;
38  import javax.swing.JRootPane;
39  import javax.swing.JPanel;
40  import javax.swing.JProgressBar;
41  import javax.swing.JComboBox;
42  import java.awt.event.ActionEvent;
43  import java.awt.BorderLayout;
44  import java.awt.Dimension;
45  import java.awt.EventQueue;
46  import java.util.Locale;
47  import java.util.Random;
48  
49  /**
50   * Demonstrates how the {@link Graph2DLayoutExecutor} can be used to apply
51   * layout algorithms to a {@link Graph2D}.
52   *
53   * @see <a href="http://docs.yworks.com/yfiles/doc/developers-guide/viewer_layout.html#cls_Graph2DLayoutExecutor">Section Class Graph2DLayoutExecutor</a> in the yFiles for Java Developer's Guide
54   */
55  public class Graph2DLayoutExecutorDemo extends DemoBase
56  {
57    // the label that shows some status (if non-blocking)
58    private JLabel statusLabel;
59    // the progress bar that shows indeterminate progress (if non-blocking)
60    private JProgressBar progressBar = new JProgressBar();
61  
62    // the types of execution
63    private JComboBox layoutExecutionTypeBox;
64  
65    // the type of layouter
66    private JComboBox layouterBox;
67  
68    public Graph2DLayoutExecutorDemo() {
69      //build sample graph
70      buildGraph( view.getGraph2D() );
71  
72      view.setViewPoint2D(-200.0, -200.0);
73    }
74  
75    protected void configureDefaultRealizers() {
76      // painting shadows is expensive and not well suited for graphs with many
77      // nodes such as this demo's sample graph
78      DemoDefaults.registerDefaultNodeConfiguration(false);
79      DemoDefaults.configureDefaultRealizers(view);
80    }
81  
82    /**
83     * Overwritten to add the status label and the progress bar.
84     */
85    public void addContentTo(JRootPane rootPane) {
86      this.statusLabel = new JLabel("Status");
87      final Dimension minimumSize = this.statusLabel.getMinimumSize();
88      this.statusLabel.setMinimumSize(new Dimension(Math.max(200, minimumSize.width), minimumSize.height));
89      final JPanel panel = new JPanel();
90      panel.add(this.statusLabel, BorderLayout.LINE_START);
91      this.progressBar.setMaximum(100);
92      this.progressBar.setMinimum(0);
93      this.progressBar.setValue(0);
94      panel.add(progressBar, BorderLayout.CENTER);
95      getContentPane().add(panel, BorderLayout.SOUTH);
96      super.addContentTo(rootPane);
97    }
98  
99    /** Creates a relatively large random graph to give the layout algorithms something to chew. */
100   void buildGraph(Graph2D graph) {
101     graph.clear();
102     Node[] nodes = new Node[400];
103     for(int i = 0; i < nodes.length; i++)
104     {
105       nodes[i] = graph.createNode();
106       graph.getRealizer(nodes[i]).setLabelText(String.valueOf(i));
107     }
108     Random random = new Random(0L);
109     for ( int i = 0; i < nodes.length; i++ ) {
110 
111       int edgeCount;
112 
113       if (random.nextInt(10) == 0) {
114         edgeCount = 4 + random.nextInt(5);
115       } else {
116         edgeCount = random.nextInt(3);
117       }
118 
119       for ( int j = 0; j < edgeCount; j++ ) {
120         graph.createEdge( nodes[ i ], nodes[ random.nextInt(nodes.length) ] );
121       }
122     }
123 
124     (new BufferedLayouter(new RandomLayouter())).doLayout(graph);
125   }
126 
127   /**
128    * Adds an extra layout action to the toolbar
129    */
130   protected JToolBar createToolBar() {
131     final Action layoutAction = new AbstractAction(
132             "Layout", SHARED_LAYOUT_ICON) {
133       public void actionPerformed(ActionEvent e) {
134         applyLayout();
135       }
136     };
137 
138     // chooser for the layouter
139     layouterBox = new JComboBox(new Object[]{"Hierarchic", "Organic", "Orthogonal"});
140     layouterBox.setMaximumSize(layouterBox.getPreferredSize());
141     layouterBox.setSelectedIndex(0);
142 
143     // chooser for the execution type.
144     layoutExecutionTypeBox = new JComboBox(
145       new Object[]{"Animated", "AnimatedThreaded", "Buffered", "Threaded", "Unbuffered", "AnimatedInOwnThread"});
146     layoutExecutionTypeBox.setMaximumSize(layoutExecutionTypeBox.getPreferredSize());
147     layoutExecutionTypeBox.setSelectedIndex(1);
148 
149     final JToolBar toolBar = super.createToolBar();
150     toolBar.addSeparator();
151     toolBar.add(createActionControl(layoutAction));
152     toolBar.addSeparator(TOOLBAR_SMALL_SEPARATOR);
153     toolBar.add(layouterBox);
154     toolBar.addSeparator(TOOLBAR_SMALL_SEPARATOR);
155     toolBar.add(layoutExecutionTypeBox);
156 
157     return toolBar;
158   }
159 
160   /**
161    * Configures and invokes a layout algorithm
162    */
163   void applyLayout() {
164     Layouter layouter = createLayouter();
165     switch (layoutExecutionTypeBox.getSelectedIndex()) {
166       case 0:
167         applyLayoutAnimated(layouter);
168         break;
169       case 1:
170         applyLayoutAnimatedThreaded(layouter);
171         break;
172       case 2:
173         applyLayoutBuffered(layouter);
174         break;
175       case 3:
176         applyLayoutThreaded(layouter);
177         break;
178       case 4:
179         applyLayoutUnbuffered(layouter);
180         break;
181       case 5:
182         applyLayoutAnimatedInOwnThread(layouter);
183         break;
184     }
185   }
186 
187   /**
188    * Creates and returns a Layouter instance according to the given layout options.
189    */
190   Layouter createLayouter() {
191     switch (layouterBox.getSelectedIndex()) {
192       default:
193       case 0:
194         return new IncrementalHierarchicLayouter();
195       case 1:
196         final SmartOrganicLayouter organicLayouter = new SmartOrganicLayouter();
197         organicLayouter.setQualityTimeRatio(1.0);
198         organicLayouter.setMaximumDuration(2L * 60L * 1000L);
199         return organicLayouter;
200       case 2:
201         return new OrthogonalLayouter();
202     }
203   }
204 
205   /**
206    * Applies the given layout algorithm to the graph
207    * This is done in a separate Thread asynchronously.
208    * Although the view and UI is responsive direct mouse and keyboard input is blocked.
209    * The layout process can be canceled and even killed through a dialog that is spawned.
210    */
211   void applyLayoutAnimatedThreaded(final Layouter layouter) {
212     this.progressBar.setIndeterminate(true);
213     final Graph2DLayoutExecutor layoutExecutor = new Graph2DLayoutExecutor(Graph2DLayoutExecutor.ANIMATED_THREADED);
214     // set a slow animation, so that the animation can easily be canceled.
215     layoutExecutor.getLayoutMorpher().setPreferredDuration(3000L);
216     layoutExecutor.getLayoutMorpher().setEasedExecution(true);
217     layoutExecutor.getLayoutMorpher().setSmoothViewTransform(true);
218     // lock the view so that the graph cannot be edited.
219     layoutExecutor.setLockingView(true);
220 
221     final JDialog dialog = new JDialog(JOptionPane.getRootFrame(), "");
222 
223     // the following method will return immediately and the layout and animation is performed in a new thread
224     // asynchronously.
225     final Graph2DLayoutExecutor.LayoutThreadHandle handle = layoutExecutor.doLayout(view, layouter, new Runnable() {
226       public void run() {
227         dialog.dispose();
228         progressBar.setIndeterminate(false);
229         statusLabel.setText("Layout Done");
230       }
231     }, new Graph2DLayoutExecutor.ExceptionListener() {
232       public void exceptionHappened(Throwable t) {
233         //dialog.dispose();
234         t.printStackTrace(System.err);
235         statusLabel.setText("Exception Happened.");
236       }
237     });
238 
239     // this is visible because the layout is not blocking (this) EDT
240     this.statusLabel.setText("Layout is running");
241 
242     final Box box = Box.createVerticalBox();
243     box.setBorder(BorderFactory.createEmptyBorder(6, 6, 6, 6));
244     final JLabel label = new JLabel("Layout Running [" + layouter.getClass().getName() + "].");
245     box.add(label);
246     box.add(Box.createVerticalStrut(12));
247     box.add(new JButton(new AbstractAction("Cancel") {
248       private boolean canceled;
249       public void actionPerformed(ActionEvent e) {
250         // first, simply cancel the layout
251         if (!canceled) {
252           handle.cancel();
253           statusLabel.setText("Cancelling");
254           label.setText("Canceled Thread.[" + layouter.getClass().getName() + "].");
255           ((JButton)e.getSource()).setText("Kill");
256           canceled = true;
257         } else {
258           // if it's not dead, yet, one could possibly try to kill the thread. 
259           // this is o.k. most of the time (no debugger, etc.), but should be used with care.
260           handle.getThread().stop();
261           setEnabled(false);
262           statusLabel.setText("Killed");
263         }
264       }
265     }));
266     dialog.getContentPane().add(box);
267     dialog.setLocationRelativeTo(view);
268     dialog.pack();
269 
270     if (handle.isRunning()) {
271       dialog.setVisible(true);
272     }
273   }
274 
275   /**
276    * Applies the given layout algorithm to the graph
277    * This is done synchronously blocking the calling Thread, thus leaving the view unresponsive during the layout.
278    */
279   void applyLayoutBuffered(final Layouter layouter){
280     final Graph2DLayoutExecutor layoutExecutor = new Graph2DLayoutExecutor(Graph2DLayoutExecutor.BUFFERED);
281     layoutExecutor.doLayout(view, layouter);
282   }
283 
284   /**
285    * Applies the given layout algorithm to the graph in an animated fashion.
286    * This is done synchronously blocking the calling Thread, thus leaving the view unresponsive during the layout
287    * and animation.
288    */
289   void applyLayoutAnimated(final Layouter layouter){
290     // this won't be visible to the user because the EDT is blocked.
291     statusLabel.setText("Starting Animated Blocking Layout");
292     progressBar.setIndeterminate(true);
293     try {
294       final Graph2DLayoutExecutor layoutExecutor = new Graph2DLayoutExecutor(Graph2DLayoutExecutor.ANIMATED);
295       layoutExecutor.doLayout(view, layouter);
296     } finally {
297       progressBar.setIndeterminate(false);
298       statusLabel.setText("Animated Blocking Layout Done.");
299     }
300   }
301 
302   /**
303    * Applies the given layout algorithm to the graph in an animated fashion using a blocking call
304    * from a separate newly spawned thread.
305    * This leaves the view responsive, but the view is still editable during the layout.
306    */
307   void applyLayoutAnimatedInOwnThread(final Layouter layouter){
308     statusLabel.setText("Starting own layout thread.");
309     progressBar.setIndeterminate(true);
310     final Graph2DLayoutExecutor layoutExecutor = new Graph2DLayoutExecutor(Graph2DLayoutExecutor.ANIMATED);
311     new Thread(new Runnable() {
312       public void run() {
313         try {
314           layoutExecutor.doLayout(view, layouter);
315         } finally {
316           SwingUtilities.invokeLater(new Runnable() {
317             public void run() {
318               statusLabel.setText("Layout Thread Finished.");
319               progressBar.setIndeterminate(false);
320             }
321           });
322         }
323       }
324     }).start();
325   }
326 
327   /**
328    * Runs the layout in a separate thread, leaving the view responsive
329    * but the view is still editable during the layout.
330    * @param layouter
331    */
332   void applyLayoutThreaded(final Layouter layouter){
333     statusLabel.setText("Starting threaded layout");
334     progressBar.setIndeterminate(true);
335     final Graph2DLayoutExecutor layoutExecutor = new Graph2DLayoutExecutor(Graph2DLayoutExecutor.THREADED);
336     layoutExecutor.doLayout(view, layouter, new Runnable() {
337       public void run() {
338         statusLabel.setText("Layout Returned");
339         progressBar.setIndeterminate(false);
340       }
341     }, null);
342     statusLabel.setText("Return from doLayout()");
343   }
344 
345   void applyLayoutUnbuffered(final Layouter layouter) {
346     final Graph2DLayoutExecutor layoutExecutor = new Graph2DLayoutExecutor(Graph2DLayoutExecutor.UNBUFFERED);
347     layoutExecutor.doLayout(view, layouter);
348   }
349 
350   public static void main(String[] args) {
351     EventQueue.invokeLater(new Runnable() {
352       public void run() {
353         Locale.setDefault(Locale.ENGLISH);
354         initLnF();
355         (new Graph2DLayoutExecutorDemo()).start();
356       }
357     });
358   }
359 }