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.tree;
29  
30  import demo.view.DemoBase;
31  import y.algo.GraphConnectivity;
32  import y.algo.Trees;
33  import y.base.DataMap;
34  import y.base.DataProvider;
35  import y.base.Edge;
36  import y.base.EdgeCursor;
37  import y.base.EdgeList;
38  import y.base.Node;
39  import y.base.NodeCursor;
40  import y.base.NodeList;
41  import y.base.NodeMap;
42  import y.geom.YPoint;
43  import y.layout.AbstractLayoutStage;
44  import y.layout.LayoutGraph;
45  import y.layout.LayoutOrientation;
46  import y.layout.LayoutTool;
47  import y.layout.Layouter;
48  import y.layout.NodeLayout;
49  import y.layout.hierarchic.IncrementalHierarchicLayouter;
50  import y.layout.hierarchic.incremental.IncrementalHintsFactory;
51  import y.layout.hierarchic.incremental.SimplexNodePlacer;
52  import y.layout.organic.SmartOrganicLayouter;
53  import y.layout.tree.BalloonLayouter;
54  import y.layout.tree.TreeLayouter;
55  import y.layout.tree.XCoordComparator;
56  import y.util.DataProviderAdapter;
57  import y.util.DataProviders;
58  import y.util.Maps;
59  import y.view.EdgeRealizer;
60  import y.view.EditMode;
61  import y.view.Graph2D;
62  import y.view.Graph2DLayoutExecutor;
63  import y.view.LineType;
64  import y.view.NavigationMode;
65  import y.view.NodeLabel;
66  import y.view.NodeRealizer;
67  import y.view.SmartNodeLabelModel;
68  import y.view.ViewMode;
69  
70  import javax.swing.AbstractAction;
71  import javax.swing.ButtonGroup;
72  import javax.swing.Icon;
73  import javax.swing.JLabel;
74  import javax.swing.JMenu;
75  import javax.swing.JMenuBar;
76  import javax.swing.JToggleButton;
77  import javax.swing.JToolBar;
78  import java.awt.Color;
79  import java.awt.Component;
80  import java.awt.Dimension;
81  import java.awt.EventQueue;
82  import java.awt.Graphics;
83  import java.awt.Insets;
84  import java.awt.event.ActionEvent;
85  import java.awt.event.MouseEvent;
86  import java.net.URL;
87  import java.util.HashSet;
88  import java.util.Locale;
89  import java.util.WeakHashMap;
90  
91  /**
92   * This demo shows how to collapse and expand sub trees by simply clicking on
93   * a root node. Several different layout algorithms can be chosen:
94   * {@link y.layout.tree.TreeLayouter},
95   * {@link y.layout.tree.BalloonLayouter},
96   * {@link y.layout.organic.SmartOrganicLayouter} and
97   * {@link y.layout.hierarchic.IncrementalHierarchicLayouter}.
98   */
99  public class CollapsibleTreeDemo extends DemoBase {
100   public static final byte STYLE_TREE = 1;
101   public static final byte STYLE_BALLOON = 2;
102   private static final byte STYLE_ORGANIC = 3;
103   private static final byte STYLE_HIERARCHIC = 4;
104 
105   private static final Color LEAF_COLOR = new Color(154, 205, 54);
106   private static final Color COLLAPSIBLE_COLOR = new Color(154, 205, 255);
107   private static final Color EXPANDABLE_COLOR = new Color(255, 154, 0);
108 
109   static final Icon expandableIcon;
110   static final Icon collapsibleIcon;
111 
112   static {
113     collapsibleIcon = new Icon(){
114       public void paintIcon(Component c, Graphics g, int x, int y) {
115         Color col = g.getColor();
116         g.setColor(Color.white);
117         g.fillRect(x + 1, y + 1, 19, 9);
118         g.setColor(Color.darkGray);
119         g.fillRect(x + 3, y + 3, 15, 5);
120         g.setColor(Color.gray);
121         g.setColor(col);
122       }
123 
124       public int getIconWidth() {
125         return 18;
126       }
127 
128       public int getIconHeight() {
129         return 9;
130       }
131     };
132 
133     expandableIcon = new Icon(){
134       public void paintIcon(Component c, Graphics g, int x, int y) {
135         Color col = g.getColor();
136         g.setColor(Color.white);
137         g.fillRect(x + 6, y + 1, 9, 19);
138         g.fillRect(x + 1, y + 6, 19, 9);
139         g.setColor(Color.darkGray);
140         g.fillRect(x + 3, y + 8, 15, 5);
141         g.fillRect(x + 8, y + 3, 5, 15);
142         g.setColor(Color.gray);
143         g.setColor(col);
144       }
145 
146       public int getIconWidth() {
147         return 18;
148       }
149 
150       public int getIconHeight() {
151         return 18;
152       }
153     };
154   }
155 
156   private byte style = STYLE_TREE;
157   private TreeLayouter treeLayouter;
158   private BalloonLayouter balloonLayouter;
159   private SmartOrganicLayouter organicLayouter;
160   private IncrementalHierarchicLayouter hierarchicLayouter;
161   private CollapsibleTreeDemo.CollapseExpandViewMode viewMode;
162   private DataMap ihlHintMap;
163   private IncrementalHintsFactory hintsFactory;
164 
165   public CollapsibleTreeDemo() {
166     Graph2D graph = view.getGraph2D();
167     
168     //create a sample tree structure
169     createTree(graph);
170 
171     //collapse/expand some nodes
172     viewMode.collapseSubtree(graph, Trees.getRoot(graph));
173     Node root = Trees.getRoot(graph);
174     viewMode.expandSubtree(graph, root, 2);
175 
176     //configure layouters
177     treeLayouter = new TreeLayouter();
178     treeLayouter.setComparator(new XCoordComparator()); //important to keep node order of collapsed/expanded items.
179     treeLayouter.setLayoutOrientation(LayoutOrientation.LEFT_TO_RIGHT);
180     treeLayouter.setLayoutStyle(TreeLayouter.ORTHOGONAL_STYLE);
181 
182     balloonLayouter = new BalloonLayouter();
183     balloonLayouter.setFromSketchModeEnabled(true);
184     balloonLayouter.setCompactnessFactor(0.1);
185     balloonLayouter.setAllowOverlaps(true);
186 
187     organicLayouter = new SmartOrganicLayouter();
188     organicLayouter.setScope(SmartOrganicLayouter.SCOPE_MAINLY_SUBSET);
189     organicLayouter.setMinimalNodeDistance(20);
190     organicLayouter.setMultiThreadingAllowed(true);
191 
192     hierarchicLayouter = new IncrementalHierarchicLayouter();
193     hierarchicLayouter.setOrthogonallyRouted(true);
194     hierarchicLayouter.setLayoutOrientation(LayoutOrientation.TOP_TO_BOTTOM);
195     // read the "old" nodes from the sketch
196     hierarchicLayouter.setLayoutMode(IncrementalHierarchicLayouter.LAYOUT_MODE_INCREMENTAL);
197     ((SimplexNodePlacer) hierarchicLayouter.getNodePlacer()).setBaryCenterModeEnabled(true);
198 
199     // create a map to store the hints for the incremental layout mechanism
200     ihlHintMap = Maps.createHashedDataMap();
201     graph.addDataProvider(IncrementalHierarchicLayouter.INCREMENTAL_HINTS_DPKEY, ihlHintMap);
202     // get a reference to a hints factory
203     hintsFactory = hierarchicLayouter.createIncrementalHintsFactory();
204 
205     //layout the graph
206     layout(view.getGraph2D(), null, true);
207   }
208   
209   protected void configureDefaultRealizers() {
210     super.configureDefaultRealizers();
211     NodeRealizer nr = view.getGraph2D().getDefaultNodeRealizer();
212     nr.setSize(80, 30);
213     NodeLabel nl = nr.createNodeLabel();
214     nr.addLabel(nl);
215     nr.setLineColor(null);
216     nl.setIcon(collapsibleIcon);
217     nl.setIconTextGap((byte) 0);
218     SmartNodeLabelModel model = new SmartNodeLabelModel();
219     nl.setLabelModel(model,
220         model.createDiscreteModelParameter(SmartNodeLabelModel.POSITION_CENTER));
221     nl.setInsets(new Insets(4, 4, 4, 4));
222     nl.setDistance(0);    
223     EdgeRealizer er = view.getGraph2D().getDefaultEdgeRealizer();
224     er.setLineType(LineType.LINE_2);
225     er.setLineColor(Color.gray);
226   }
227   
228   protected void loadGraph(URL resource) {
229     super.loadGraph(resource);
230     Graph2D graph = view.getGraph2D();
231     viewMode.collapseSubtree(graph, Trees.getRoot(graph));
232     Node root = Trees.getRoot(graph);
233     viewMode.expandSubtree(graph, root, 2);
234     layout(graph, null, true);
235   }
236   
237   protected void initialize() {  
238     super.initialize();
239     view.setPreferredSize(new Dimension(900,600));
240   }
241   
242   /** EditMode not supported by this demo. */
243   protected EditMode createEditMode() {
244     return null;
245   }
246 
247   /** Register CollapseExpandViewMode and NavigationMode to support panning */
248   protected void registerViewModes() {
249     viewMode = new CollapseExpandViewMode();    
250     view.addViewMode(viewMode); 
251     NavigationMode navigationMode = new NavigationMode();
252     view.addViewMode(navigationMode);
253   }
254 
255   /** Create menu bar for this demo */
256   protected JMenuBar createMenuBar() {
257     JMenuBar menuBar = new JMenuBar();
258     JMenu menu = new JMenu("File");
259     menu.add(new PrintAction());
260     menu.addSeparator();
261     menu.add(new ExitAction());
262     menuBar.add(menu);
263     return menuBar;
264   }
265 
266   /**
267    * Overwritten to disable undo/redo because this is not an editable demo.
268    */
269   protected boolean isUndoRedoEnabled() {
270     return false;
271   }
272 
273   /**
274    * Overwritten to disable clipboard because this is not an editable demo.
275    */
276   protected boolean isClipboardEnabled() {
277     return false;
278   }
279 
280   /**
281    * A ViewMode that allows to expand and collapse the subtrees rooted at a node by simply clicking on the node.
282    * Clicking on a node, while the CTRL modifier key is pushed, will expand/collapse all nodes in the subtree. Note that this view mode
283    * is also responsible to keeping track of the expansion state of each node.
284    */
285   class CollapseExpandViewMode extends ViewMode {
286     NodeMap collapsedEdges = Maps.createNodeMap(new WeakHashMap());
287     NodeMap collapsedState = Maps.createNodeMap(new WeakHashMap());
288 
289     public void mouseClicked(MouseEvent ev) {
290       //if (ev.getClickCount() != 2) return;
291       Node node = getHitInfo(ev).getHitNode();
292 
293       if (node != null) {
294         prepareForLayout(view.getGraph2D(), node);
295         if (collapsedState.getBool(node)) {
296           if (ev.isControlDown()) {//ctrl is pressed expand whole subtree (max depth of 10000) of current node
297             expandSubtree(getGraph2D(), node, 10000);
298           } else {//ctrl is not pressed expand only current node
299             expandNode(getGraph2D(), node);
300           }
301         } else {
302           if (ev.isControlDown()) {//ctrl is pressed collapse whole subtree of current node
303             collapseSubtree(getGraph2D(), node);
304           } else {//ctrl is not pressed collapse only current node
305             collapseNode(getGraph2D(), node);
306           }
307         }
308         layout(getGraph2D(), node, false);
309       } 
310     }
311 
312     /**
313      * Collapses the given node and it's whole subtree.
314      *
315      * @param graph the graph, the root node belongs to.
316      * @param root  the node whose subtree is to be collapsed.
317      */
318     public void collapseSubtree(Graph2D graph, Node root) {
319       NodeList list = GraphConnectivity.getSuccessors(graph, new NodeList(root), graph.N());
320       NodeCursor nodeCursor = list.nodes();
321       for (nodeCursor.toLast(); nodeCursor.ok(); nodeCursor.prev()) {
322         Node node = nodeCursor.node();
323         if (!collapsedState.getBool(node) && node != root) {
324           collapseNode(graph, node);
325         }
326       }
327       collapseNode(graph, root);
328     }
329 
330     /**
331      * collapses the given node.
332      *
333      * @param graph the graph, the root node belongs to.
334      * @param root  the node which is to be collapsed.
335      */
336     public void collapseNode(Graph2D graph, final Node root) {
337       EdgeList edgeList = collapsedEdges.get(root) != null ? (EdgeList) collapsedEdges.get(root) : new EdgeList();
338       edgeList.addAll(root.outEdges());
339       NodeList collapsedNodes = GraphConnectivity.getSuccessors(graph, new NodeList(root), graph.N());
340 
341       for (NodeCursor nc = collapsedNodes.nodes(); nc.ok(); nc.next()) {
342         Node n = nc.node();
343         edgeList.addAll(n.outEdges());
344         double x = graph.getCenterX(n) - graph.getCenterX(root);
345         double y = graph.getCenterY(n) - graph.getCenterY(root);
346 
347         // store relative location to root
348         graph.getRealizer(n).setLocation(0.01 * x, 0.01 * y);
349 
350         //remove node from graph
351         graph.hide(n);
352       }
353       collapsedState.setBool(root, true);
354       collapsedEdges.set(root, edgeList);
355       
356       if (!edgeList.isEmpty()) {
357         NodeRealizer rootR = getGraph2D().getRealizer(root);
358         if(rootR.labelCount() > 1) {
359           getGraph2D().getRealizer(root).getLabel(1).setIcon(expandableIcon);
360         }
361         rootR.setFillColor(EXPANDABLE_COLOR);
362       }
363     }
364 
365     /**
366      * Expands a node and it's subtree to a given depth.
367      *
368      * @param graph the graph, the root node belongs to.
369      * @param root  the node whose subtree is to be expanded.
370      * @param depth determines the depth (how many layers) till which the subtree should be expanded.
371      */
372     public void expandSubtree(Graph2D graph, Node root, int depth) {
373       if (depth <= 0) {
374         return;
375       }
376       //expand the root
377       expandNode(graph, root);
378       NodeList list = GraphConnectivity.getSuccessors(graph, new NodeList(root), depth);
379       for (NodeCursor nodeCursor = list.nodes(); nodeCursor.ok(); nodeCursor.next()) {
380         Node node = nodeCursor.node();
381         if (collapsedState.getBool(node)) {
382           //expand the subtree
383           expandSubtree(graph, node, depth - 1);
384         }
385       }
386     }
387 
388     /**
389      * Expands a single node.
390      *
391      * @param graph the graph, the root node belongs to.
392      * @param root  the node which is to be expanded.
393      */
394     public void expandNode(Graph2D graph, Node root) {
395       final EdgeList edgeList = (EdgeList) collapsedEdges.get(root);
396       if (edgeList != null) {
397         for (EdgeCursor ec = edgeList.edges(); ec.ok(); ec.next()) {
398           Edge e = ec.edge();
399           if (!graph.contains(e.source())) {
400             graph.unhide(e.source());
401             graph.setLocation(e.source(), graph.getX(root) + graph.getX(e.source()),
402                 graph.getY(root) + graph.getY(e.source()));
403           }
404           if (!graph.contains(e.target())) {
405             graph.unhide(e.target());
406             graph.setLocation(e.target(), graph.getX(root) + graph.getX(e.target()),
407                 graph.getY(root) + graph.getY(e.target()));
408           }
409           //inserts the edge into the graph
410           graph.unhide(e);
411           //cosmetics
412           graph.getRealizer(e).clearBends();
413         }
414         collapsedEdges.set(root, null);
415       }
416       collapsedState.setBool(root, false);
417 
418       if (root.outDegree() > 0) {
419         NodeRealizer rootR = getGraph2D().getRealizer(root);
420         if(rootR.labelCount() > 1) {
421           rootR.getLabel(1).setIcon(collapsibleIcon);
422         }
423         rootR.setFillColor(COLLAPSIBLE_COLOR);
424       }
425     }
426   }
427 
428   /**
429    * Layout the tree according to the set layout style.
430    *
431    * @param graph2D    the graph, which will be laid out.
432    * @param focusNode  the current focus.
433    * @param fitContent determines whether to fit the content to the current view. Should be prevented, if layout. is
434    *                   started due to a mouse click on a node.
435    */
436   void layout(Graph2D graph2D, final Node focusNode, boolean fitContent) {
437     //calculate layout according to chosen style
438     Layouter layouter = null;
439     switch (style) {
440       case CollapsibleTreeDemo.STYLE_TREE:
441         layouter = treeLayouter;
442         break;
443       case CollapsibleTreeDemo.STYLE_BALLOON:
444         layouter = balloonLayouter;
445         break;
446       case CollapsibleTreeDemo.STYLE_ORGANIC:
447         prepareForLayout(graph2D, focusNode);
448         layouter = organicLayouter;
449         break;
450       case CollapsibleTreeDemo.STYLE_HIERARCHIC:
451         prepareForLayout(graph2D, focusNode);
452         layouter = hierarchicLayouter;
453         break;
454       default:
455         layouter = treeLayouter;
456     }
457     
458     graph2D.addDataProvider(FocusNodeLayoutStage.FOCUS_NODE_DPKEY, FocusNodeLayoutStage.createFocusNodeDataProvider(focusNode));
459 
460     final Graph2DLayoutExecutor layoutExecutor = new Graph2DLayoutExecutor();
461     if (fitContent) {
462       layoutExecutor.getLayoutMorpher().setSmoothViewTransform(true);
463     } else {
464       layoutExecutor.getLayoutMorpher().setKeepZoomFactor(true);
465     }
466     layoutExecutor.getLayoutMorpher().setEasedExecution(true);
467     layoutExecutor.doLayout(view, new FocusNodeLayoutStage(layouter));
468   }
469 
470   public static class FocusNodeLayoutStage extends AbstractLayoutStage {
471     
472     public static final Object FOCUS_NODE_DPKEY = "FocusNodeStage#FOCUS_NODE_DPKEY";
473     
474     public FocusNodeLayoutStage(Layouter coreLayouter) {
475       super(coreLayouter);
476     }
477     
478     public boolean canLayout(LayoutGraph graph) {
479       return canLayoutCore(graph);
480     }
481 
482     public void doLayout(LayoutGraph graph) {
483       DataProvider dp = graph.getDataProvider(FOCUS_NODE_DPKEY);
484       if(dp != null) {
485         Node focusNode = null;
486         for (NodeCursor nc = graph.nodes(); nc.ok(); nc.next()) {
487           Node n = nc.node();
488           if(dp.getBool(n)) {
489             focusNode = n;
490             break;
491           }
492         }
493         YPoint oldFocus = null;
494         if(focusNode != null) {
495           oldFocus = graph.getCenter(focusNode);
496           doLayoutCore(graph);
497           NodeLayout nl = graph.getNodeLayout(focusNode);
498           YPoint newFocus = new YPoint(nl.getX() + 0.5 * nl.getWidth(), nl.getY() + 0.5 * nl.getHeight());
499           double dx = newFocus.x - oldFocus.x;
500           double dy = newFocus.y - oldFocus.y;
501           LayoutTool.moveSubgraph(graph, graph.nodes(), -dx, -dy);            
502         } 
503         else {
504           doLayoutCore(graph);
505         }
506       }
507       else {
508         doLayoutCore(graph);
509       }  
510     }
511   
512     public static DataProvider createFocusNodeDataProvider(final Node focusNode) {
513       return new DataProviderAdapter() {
514         public boolean getBool(Object obj) {
515           return obj == focusNode;
516         }
517       };
518     }    
519   }
520   
521   private void prepareForLayout(Graph2D graph2D, Node node) {
522     if (node != null){
523       NodeList incrementalNodes = GraphConnectivity.getSuccessors(graph2D, new NodeList(node), graph2D.N());
524       final HashSet incrementalNodesSet = new HashSet(incrementalNodes);
525       // mark nodes as "new"
526       for (NodeCursor nodeCursor = incrementalNodes.nodes(); nodeCursor.ok(); nodeCursor.next()) {
527         ihlHintMap.set(nodeCursor.node(), hintsFactory.createLayerIncrementallyHint(nodeCursor.node()));
528       }
529       graph2D.addDataProvider(SmartOrganicLayouter.NODE_SUBSET_DATA, new DataProviderAdapter() {
530         public boolean getBool(Object dataHolder) {
531           return incrementalNodesSet.contains(dataHolder);
532         }
533       });
534       graph2D.addDataProvider(IncrementalHierarchicLayouter.INCREMENTAL_HINTS_DPKEY, ihlHintMap);
535       organicLayouter.setScope(SmartOrganicLayouter.SCOPE_MAINLY_SUBSET);
536     } else {
537       graph2D.removeDataProvider(IncrementalHierarchicLayouter.INCREMENTAL_HINTS_DPKEY);
538       graph2D.addDataProvider(SmartOrganicLayouter.NODE_SUBSET_DATA, DataProviders.createConstantDataProvider(Boolean.FALSE));
539       organicLayouter.setScope(SmartOrganicLayouter.SCOPE_ALL);
540     }
541   }
542 
543 
544   /** Adds some buttons to the toolbar, to choose the layout style from. */
545   protected JToolBar createToolBar() {
546     JToolBar toolbar = super.createToolBar();
547     toolbar.addSeparator();
548     toolbar.add(new JLabel("Layout:"));
549     toolbar.addSeparator(TOOLBAR_SMALL_SEPARATOR);
550 
551     ButtonGroup group = new ButtonGroup();
552     JToggleButton b1 = new JToggleButton(new AbstractAction(
553             "Tree", SHARED_LAYOUT_ICON) {
554       public void actionPerformed(ActionEvent e) {
555         style = CollapsibleTreeDemo.STYLE_TREE;
556         layout(view.getGraph2D(), null, true);
557       }
558     });
559     b1.setSelected(true);
560     group.add(b1);
561     toolbar.add(b1);
562 
563 
564     JToggleButton b2 = new JToggleButton(new AbstractAction(
565             "Balloon", SHARED_LAYOUT_ICON) {
566       public void actionPerformed(ActionEvent e) {
567         style = CollapsibleTreeDemo.STYLE_BALLOON;
568         layout(view.getGraph2D(), null, true);
569       }
570     });
571     group.add(b2);
572     toolbar.add(b2);
573 
574     JToggleButton b3 = new JToggleButton(new AbstractAction(
575             "Organic", SHARED_LAYOUT_ICON) {
576       public void actionPerformed(ActionEvent e) {
577         style = CollapsibleTreeDemo.STYLE_ORGANIC;
578         layout(view.getGraph2D(), null, true);
579       }
580     });
581     group.add(b3);
582     toolbar.add(b3);
583 
584     JToggleButton b4 = new JToggleButton(new AbstractAction(
585             "Hierarchic", SHARED_LAYOUT_ICON) {
586       public void actionPerformed(ActionEvent e) {
587         style = CollapsibleTreeDemo.STYLE_HIERARCHIC;
588         Graph2D graph = view.getGraph2D();
589         layout(graph, Trees.getRoot(graph), true);
590       }
591     });
592     group.add(b4);
593     toolbar.add(b4);
594 
595     return toolbar;
596   }
597 
598   void createTree(Graph2D graph) {
599     NodeList queue = new NodeList();
600     queue.add(graph.createNode());
601     for (int i = 0; i < 50; i++) {
602       Node root = queue.popNode();
603       Node c1 = graph.createNode();
604       Edge e1 = graph.createEdge(root, c1);
605       Node c2 = graph.createNode();
606       Edge e2 = graph.createEdge(root, c2);
607       queue.add(c2);
608       queue.add(c1);
609       if (i == 25 || i == 40) {
610         for (int j = 0; j < 20; j++) {
611           Node c3 = graph.createNode();
612           Edge e3 = graph.createEdge(root, c3);
613           queue.add(c3);
614         }
615       }
616     }
617     for (NodeCursor nodeCursor = graph.nodes(); nodeCursor.ok(); nodeCursor.next()) {
618       Node node = nodeCursor.node();
619       if (node.outDegree() == 0) {
620         graph.getRealizer(node).getLabel(1).setIcon(null);
621         graph.getRealizer(node).setFillColor(LEAF_COLOR);
622       }
623     }
624   }
625 
626 
627   public static void main(String[] args) {
628     EventQueue.invokeLater(new Runnable() {
629       public void run() {
630         Locale.setDefault(Locale.ENGLISH);
631         initLnF();
632         (new CollapsibleTreeDemo()).start();
633       }
634     });
635   }
636 }