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.view.orgchart;
29  
30  import demo.view.DemoDefaults;
31  import demo.view.orgchart.OrgChartTreeModel.Employee;
32  import org.xml.sax.InputSource;
33  import y.base.Node;
34  import y.io.SuffixFileFilter;
35  import y.view.Graph2D;
36  import y.view.Graph2DSelectionEvent;
37  import y.view.Graph2DSelectionListener;
38  import y.view.Overview;
39  
40  import javax.swing.AbstractAction;
41  import javax.swing.Action;
42  import javax.swing.BorderFactory;
43  import javax.swing.Box;
44  import javax.swing.BoxLayout;
45  import javax.swing.ButtonGroup;
46  import javax.swing.DefaultCellEditor;
47  import javax.swing.Icon;
48  import javax.swing.JButton;
49  import javax.swing.JCheckBox;
50  import javax.swing.JComboBox;
51  import javax.swing.JComponent;
52  import javax.swing.JEditorPane;
53  import javax.swing.JFileChooser;
54  import javax.swing.JFrame;
55  import javax.swing.JLabel;
56  import javax.swing.JMenu;
57  import javax.swing.JMenuBar;
58  import javax.swing.JPanel;
59  import javax.swing.JRadioButton;
60  import javax.swing.JRootPane;
61  import javax.swing.JScrollPane;
62  import javax.swing.JSeparator;
63  import javax.swing.JSplitPane;
64  import javax.swing.JTable;
65  import javax.swing.JTree;
66  import javax.swing.SwingConstants;
67  import javax.swing.event.ChangeEvent;
68  import javax.swing.event.ChangeListener;
69  import javax.swing.event.TableModelEvent;
70  import javax.swing.event.TableModelListener;
71  import javax.swing.event.TreeSelectionEvent;
72  import javax.swing.event.TreeSelectionListener;
73  import javax.swing.table.DefaultTableModel;
74  import javax.swing.table.TableCellEditor;
75  import javax.swing.table.TableCellRenderer;
76  import javax.swing.table.TableModel;
77  import javax.swing.tree.DefaultTreeCellRenderer;
78  import javax.swing.tree.DefaultTreeModel;
79  import javax.swing.tree.DefaultTreeSelectionModel;
80  import javax.swing.tree.TreeModel;
81  import javax.swing.tree.TreeNode;
82  import javax.swing.tree.TreePath;
83  import javax.swing.tree.TreeSelectionModel;
84  import java.awt.BorderLayout;
85  import java.awt.Color;
86  import java.awt.Component;
87  import java.awt.Dimension;
88  import java.awt.EventQueue;
89  import java.awt.FlowLayout;
90  import java.awt.Font;
91  import java.awt.event.ActionEvent;
92  import java.io.File;
93  import java.io.FileInputStream;
94  import java.io.IOException;
95  import java.io.InputStream;
96  import java.net.URL;
97  
98  /**
99   * This demo visualizes an organization chart. This comprehensive demo shows 
100  * many aspects of yFiles. In particular it shows how to
101  * <ul>
102  * <li>visualize XML-formatted data as a graph</li>
103  * <li>create a tree diagram from a {@link javax.swing.tree.TreeModel}</li>
104  * <li>create customized node realizers that show text and multiple icons</li>
105  * <li>create a customized {@link y.view.NavigationMode} view mode</li>
106  * <li>create fancy roll-over effects when hovering over nodes</li> 
107  * <li>implement level of detail (LoD) rendering of graphs</li>
108  * <li>synchronize the selection state of {@link javax.swing.JTree} and {@link y.view.Graph2D}</li>
109  * <li>customize {@link y.layout.tree.GenericTreeLayouter} to make it a perfect match for laying out organization charts</li>
110  * <li>create local views for a large diagram</li>
111  * <li>apply incremental layout and apply nice fade in and fade out effects to added or removed elements</li>
112  * <li>implement and use keyboard navigation for {@link y.view.Graph2DView}</li>
113  * </ul>
114  * <p>
115  * This demo is composed of multiple classes. Class {@link demo.view.orgchart.OrgChartDemo} is the organization chart application and driver class 
116  * that organizes the UI elements and makes use of the Swing component {@link demo.view.orgchart.JOrgChart}. The model data used in this sample
117  * is represented by the Swing tree model {@link demo.view.orgchart.OrgChartTreeModel}. Class {@link demo.view.orgchart.JOrgChart}
118  * builds upon the more generic tree chart component {@link demo.view.orgchart.JTreeChart}. In a nutshell, JTreeChart 
119  * visualizes a generic TreeModel and includes all the viewer logic, while JOrgChart visualizes a OrgChartTreeModel and 
120  * customizes the look and feel of the component. Also it customizes the look and feel of the component to make it suitable for
121  * organization charts.
122  * </p> 
123  */
124 public class OrgChartDemo {
125   
126   private JOrgChart orgChart;
127   private JTable propertiesTable;
128   private JTree tree;
129   private String helpFile;
130 
131   public OrgChartDemo() {
132     this(null);
133   }
134 
135   public OrgChartDemo(final String helpFile) {
136     this.helpFile = helpFile;
137   }
138 
139   /**
140    * Adds all UI elements of the application to a root pane container. 
141    */
142   public void addContentTo( final JRootPane rootPane ) {
143     final JPanel contentPane = new JPanel(new BorderLayout());
144 
145     final OrgChartTreeModel model = readOrgChart(getResource("resources/orgchartmodel.xml"));
146     if (model != null) {
147       final Box leftPanel = new Box(BoxLayout.Y_AXIS);
148 
149       orgChart = createOrgChart(model);
150       orgChart.setFitContentOnResize(true);
151 
152       final Overview overview = orgChart.createOverview();
153       leftPanel.add(createTitledPanel(overview, "Overview"));
154 
155       tree = createStructureView(model);
156       final JComponent viewOptions = createViewOptionsPanel();
157       leftPanel.add(viewOptions, BorderLayout.NORTH);
158       leftPanel.add(createTitledPanel(new JScrollPane(tree),"Structure View"));
159 
160       propertiesTable = createPropertiesTable();
161       final JScrollPane scrollPane = new JScrollPane(propertiesTable);
162       scrollPane.setPreferredSize(new Dimension(200, 180));
163       leftPanel.add(createTitledPanel(scrollPane,"Properties"));
164 
165       //sync app whenever orgchart selection changes occur
166       orgChart.getGraph2D().addGraph2DSelectionListener(new OrgChartSelectionUpdater());
167       //sync app whenever tree selection changes occur
168       tree.getSelectionModel().addTreeSelectionListener(new TreeSelectionUpdater());
169       //tree.addKeyListener(new TreeActionListener());
170 
171       contentPane.setLayout(new BorderLayout());
172       final JSplitPane splitPane;
173       if (helpFile != null) {
174         contentPane.add(leftPanel, BorderLayout.WEST);
175         final JComponent helpPane = createHelpPane(getResource(helpFile));
176         helpPane.setMinimumSize(new Dimension(200, 10));
177 
178         splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,
179             createTitledPanel(orgChart, "Organization Chart"),
180             createTitledPanel(helpPane, "Help"));
181         splitPane.setOneTouchExpandable(true);
182 
183         splitPane.setResizeWeight(1);
184 
185         contentPane.add(splitPane, BorderLayout.CENTER);
186       } else {
187         final Box panel = new Box(BoxLayout.X_AXIS);
188         panel.add(leftPanel);
189         panel.add(new JSeparator(SwingConstants.VERTICAL));
190         contentPane.add(panel, BorderLayout.WEST);
191         contentPane.add(createTitledPanel(orgChart, "Organization Chart"), BorderLayout.CENTER);
192       }
193     } else {
194       contentPane.setPreferredSize(new Dimension(320, 24));
195       contentPane.add(new JLabel("Could not create Organization Chart.", JLabel.CENTER));
196     }
197 
198     rootPane.setContentPane(contentPane);
199     final JMenuBar menu = new JMenuBar();
200     final JMenu file = new JMenu("File");
201     menu.add(file);
202     file.add(createLoadAction());
203     file.add(createSaveAction());
204     file.addSeparator();
205     file.add(createExitAction());
206     rootPane.setJMenuBar(menu);
207   }
208 
209   private Action createExitAction() {
210     return new AbstractAction("Exit") {
211       public void actionPerformed(final ActionEvent e) {
212         System.exit(0);
213       }
214     };
215   }
216 
217   private Action createSaveAction() {
218     return new AbstractAction("Save") {
219       public void actionPerformed(final ActionEvent e) {
220         final JFileChooser fileChooser = new JFileChooser();
221         fileChooser.setFileFilter(new SuffixFileFilter("xml", "XML File"));
222         fileChooser.setAcceptAllFileFilterUsed(false);
223         if (fileChooser.showSaveDialog(null) == JFileChooser.APPROVE_OPTION) {
224           final OrgChartTreeModel.OrgChartWriter writer = new OrgChartTreeModel.OrgChartWriter();
225           writer.write(fileChooser.getSelectedFile(),(Employee) orgChart.getTreeNode(orgChart.getRootNode()));
226         }
227       }
228     };
229   }
230 
231   private Action createLoadAction() {
232     return new AbstractAction("Load") {
233       public void actionPerformed(final ActionEvent e) {
234         final JFileChooser fileChooser = new JFileChooser();
235         fileChooser.setDialogTitle("Open Org Chart");
236         fileChooser.setFileFilter(new SuffixFileFilter("xml","XML File"));
237         fileChooser.setAcceptAllFileFilterUsed(false);
238         if (fileChooser.showOpenDialog(null) == JFileChooser.APPROVE_OPTION) {
239           final OrgChartTreeModel orgChartTreeModel = readOrgChart(fileChooser.getSelectedFile());
240           orgChart.setModel(orgChartTreeModel);
241           orgChart.updateChart();
242           tree.setModel(orgChartTreeModel);
243           for (int i = 0; i < tree.getRowCount(); i++) {
244             tree.expandRow(i);
245           }
246         }
247       }
248     };
249   }
250 
251 
252   private URL getResource( final String name ) {
253     return demo.view.DemoBase.getResource(getClass(), name);
254   }
255 
256   /**
257    * Starts the application in a JFrame.
258    */
259   private void start() {
260     final JFrame frame = new JFrame("yFiles Organization Chart Demo");
261     frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
262     frame.setIconImage(demo.view.DemoBase.getFrameIcon());
263     addContentTo(frame.getRootPane());
264     frame.pack();
265     frame.setVisible(true);
266     orgChart.requestFocus();
267   }
268 
269   /**
270    * Creates the application help pane.
271    */
272   JComponent createHelpPane(final URL helpURL) {
273     try {
274       final JEditorPane editorPane = new JEditorPane(helpURL);
275       editorPane.setEditable(false);
276       editorPane.setPreferredSize(new Dimension(250, 250));
277       return new JScrollPane(editorPane);
278     } catch (IOException e) {
279       e.printStackTrace();
280     }
281     return null;
282   }
283 
284   /**
285    * Reads and returns the OrgChartTreeModel from an XML file.
286    */
287   OrgChartTreeModel readOrgChart(final URL orgChartURL) {
288     OrgChartTreeModel model = null;
289     try {
290       final InputStream stream = orgChartURL.openStream();
291       try {
292         model = OrgChartTreeModel.create(new InputSource(stream));
293       } finally {
294         stream.close();
295       }
296     } catch(IOException ioException) {
297       System.err.println("Failed to read from " + orgChartURL);
298       ioException.printStackTrace();
299     }
300     return model;
301   }
302 
303   /**
304    * Reads and returns the OrgChartTreeModel from an XML file.
305    */
306   OrgChartTreeModel readOrgChart(final File orgChartFile) {
307     OrgChartTreeModel model = null;
308     try {
309       final InputStream stream = new FileInputStream(orgChartFile);
310       try {
311         model = OrgChartTreeModel.create(new InputSource(stream));
312       } finally {
313         stream.close();
314       }
315     } catch(IOException ioException) {
316       System.err.println("Failed to read from " + orgChartFile.getAbsolutePath());
317       ioException.printStackTrace();
318     }
319     return model;
320   }
321 
322   /**
323    * Creates a JOrgChart component for the given model.
324    */
325   JOrgChart createOrgChart(final OrgChartTreeModel model) {
326     final JOrgChart orgChart = new JOrgChart(model);
327     orgChart.setPreferredSize(new Dimension(720, 750));
328     addGlassPaneComponents(orgChart);
329     return orgChart;
330   }
331 
332   /**
333    * Creates and returns a JTree-based structure view of the tree model.
334    */
335   JTree createStructureView(final TreeModel model) {
336     final JTree tree = new JTree(model);
337     tree.setCellRenderer(new DefaultTreeCellRenderer() {
338       public Component getTreeCellRendererComponent(final JTree tree, Object value,final boolean sel,
339              final boolean expanded, final boolean leaf,final int row, final boolean hasFocus) {
340         if(value instanceof Employee) {
341             final Employee employee = (Employee)value;
342             value = employee.name;
343         }
344         return super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus);
345       }
346     });
347     
348     for(int i = 0; i < tree.getRowCount(); i++) {
349       tree.expandPath(tree.getPathForRow(i));
350     }
351 
352     return tree;
353   }
354   
355   /**
356    * Creates a JTable based properties view that displays the details of a selected model element.
357    */
358   JTable createPropertiesTable() {
359     final DefaultTableModel tm = new DefaultTableModel();
360     tm.addColumn("", new Object[]{"Name", "Position", "Phone", "Fax", "Email", "Business Unit", "Status","Assistant"});
361     tm.addColumn("", new Object[]{"",     "",         "",       "",    ""    , ""             , "",Boolean.FALSE});
362     return new JTable(tm) {
363 
364       private final JComboBox statusBox = new JComboBox();
365       {
366         statusBox.addItem("unavailable");
367         statusBox.addItem("present");
368         statusBox.addItem("travel");
369       }
370 
371       public boolean isCellEditable(final int row, final int column) {
372         return row != 5 && column == 1;
373       }
374       public TableCellRenderer getCellRenderer(final int row, final int column) {
375         if (row == 7 && column == 1) {
376           return getDefaultRenderer(Boolean.class);
377         } else {
378           return super.getCellRenderer(row, column);
379         }
380       }
381       public TableCellEditor getCellEditor(final int row, final int column) {
382         if (row == 6 && column == 1) {
383           return new DefaultCellEditor(statusBox);
384         } else if (row == 7 && column == 1) {
385           return getDefaultEditor(Boolean.class);
386         } else {
387           return super.getCellEditor(row,column);
388         }
389       }
390     };
391   }
392   
393   /**
394    * Updates the properties table when being called.
395    */
396   void updatePropertiesTable(final Employee e) {
397     final DefaultTableModel tm = (DefaultTableModel) propertiesTable.getModel();
398     final TableModelListener[] tml = tm.getTableModelListeners();
399     for(int i = 0; i < tml.length-1; i++) {
400       tm.removeTableModelListener(tml[i]);
401     }
402     tm.setValueAt(e.name,             0, 1);
403     tm.setValueAt(e.position,         1, 1);
404     tm.setValueAt(e.phone,            2, 1);
405     tm.setValueAt(e.fax,              3, 1);
406     tm.setValueAt(e.email,            4, 1);
407     tm.setValueAt(e.businessUnit,     5, 1);
408     tm.setValueAt(e.status,           6, 1);
409     tm.setValueAt((e.assistant) ? Boolean.TRUE : Boolean.FALSE,        7, 1);
410     final boolean status = !e.vacant;
411     propertiesTable.setEnabled(status);
412     orgChart.updateView();
413     tm.addTableModelListener(new TableModelListener() {
414       public void tableChanged(final TableModelEvent event) {
415         final TableModel tableModel = (TableModel) event.getSource();
416         switch (event.getFirstRow()) {
417           case 0:
418             e.name = (String) tableModel.getValueAt(0, 1);
419             break;
420           case 1:
421             e.position = (String) tableModel.getValueAt(1, 1);
422             break;
423           case 2:
424             e.phone = (String) tableModel.getValueAt(2,1);
425             break;
426           case 3:
427             e.fax = (String) tableModel.getValueAt(3, 1);
428             break;
429           case 4:
430             e.email = (String) tableModel.getValueAt(4, 1);
431             break;
432           case 5:
433             e.businessUnit = (String) tableModel.getValueAt(5, 1);
434             break;
435           case 6:
436             e.status = (String) tableModel.getValueAt(6, 1);
437             break;
438           case 7:
439             e.assistant = ((Boolean) tableModel.getValueAt(7,1)).booleanValue();
440             orgChart.layoutGraph(true);
441             break;
442         }
443         orgChart.configureNodeRealizer(orgChart.getNodeForUserObject(e));
444         orgChart.getGraph2D().updateViews();
445       }
446     });
447   }
448   
449   /**
450    * Create a panel that allows to configure view options.  
451    */
452   JPanel createViewOptionsPanel() {
453     final JPanel panel = new JPanel();
454     panel.setBorder(BorderFactory.createEmptyBorder(10,10,10,10));
455     final JRadioButton button1 = new JRadioButton(new AbstractAction("Global View") {
456       public void actionPerformed(final ActionEvent e) {
457         orgChart.showGlobalHierarchy();
458       }      
459     });
460     button1.setSelected(true);
461         
462     final JRadioButton button2 = new JRadioButton(new AbstractAction("Local View") {
463       public void actionPerformed(final ActionEvent e) {
464         orgChart.showLocalHierarchy(null);                
465       }      
466     });
467         
468     final ButtonGroup bg = new ButtonGroup();
469     bg.add(button1);
470     bg.add(button2);
471     panel.add(button1);
472     panel.add(button2);
473 
474     final JCheckBox checkBox = new JCheckBox(new AbstractAction("Show Colleagues") {
475       public void actionPerformed(final ActionEvent e) {
476         final boolean result = ((JCheckBox)e.getSource()).isSelected();
477         if(result != orgChart.isSiblingViewEnabled()) {
478           orgChart.setSiblingViewEnabled(result);
479           orgChart.showLocalHierarchy(null);
480         }        
481       }      
482     });
483     checkBox.setEnabled(false);       
484     panel.add(checkBox);
485 
486     final JCheckBox checkBox2 = new JCheckBox(new AbstractAction("Show Business Units") {
487       public void actionPerformed(final ActionEvent e) {
488         final boolean result = ((JCheckBox)e.getSource()).isSelected();
489         if(result != orgChart.isGroupViewEnabled()) {
490           orgChart.setGroupViewEnabled(result);
491           orgChart.updateChart();
492         }        
493       }      
494     });          
495     panel.add(checkBox2);
496     
497     button2.addChangeListener(new ChangeListener() {
498       public void stateChanged(final ChangeEvent e) {
499         checkBox.setEnabled(button2.isSelected());                
500       }      
501     });
502     
503     panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
504     
505     return createTitledPanel(panel, "View Options");
506     
507   }
508   
509   /**
510    * Adds some toolbar buttons on top of JOrgChart.
511    */
512   private void addGlassPaneComponents(final JOrgChart orgChart ) {
513     final JPanel glassPane = orgChart.getGlassPane();
514     glassPane.setLayout(new BorderLayout());
515     
516     final JPanel bar = new JPanel(new FlowLayout(FlowLayout.LEFT,5,0));
517     bar.setOpaque(false);
518     bar.setBorder(BorderFactory.createEmptyBorder(20,15,0,0));
519 
520     final Action zoomIn = orgChart.createZoomInAction();
521     zoomIn.putValue(AbstractAction.SMALL_ICON, getIconResource("resource/zoomIn.png"));
522     zoomIn.putValue(AbstractAction.SHORT_DESCRIPTION, "Zoom into Chart");
523     bar.add(createButton(zoomIn));
524     
525     final Action zoomOut = orgChart.createZoomOutAction();
526     zoomOut.putValue(AbstractAction.SMALL_ICON, getIconResource("resource/zoomOut.png"));
527     zoomOut.putValue(AbstractAction.SHORT_DESCRIPTION, "Zoom out of Chart");
528     bar.add(createButton(zoomOut));
529   
530     final Action fitContent = orgChart.createFitContentAction();
531     fitContent.putValue(AbstractAction.SMALL_ICON, getIconResource("resource/zoomFit.png"));
532     fitContent.putValue(AbstractAction.SHORT_DESCRIPTION, "Fit Chart into View");
533     bar.add(createButton(fitContent));
534     
535     glassPane.add(bar, BorderLayout.NORTH);
536   }
537 
538   private Icon getIconResource( final String name ) {
539     return demo.view.DemoBase.getIconResource(name);
540   }
541 
542   /**
543    * Creates a button for an action.
544    */
545   private JButton createButton(final Action action) {
546     final JButton button = new JButton(action);
547     button.setBackground(Color.WHITE);
548     return button;
549   }
550 
551 
552   /**
553    * A TreeSelectionListener that propagates selection changes to JOrgChart.
554    */
555   class TreeSelectionUpdater implements TreeSelectionListener {
556     public void valueChanged(final TreeSelectionEvent e) {
557       final TreePath path = e.getPath();
558       final Employee employee = (Employee) path.getLastPathComponent();
559       Node node = orgChart.getNodeForUserObject(employee);
560       
561       if(orgChart.isLocalViewEnabled() && (node == null || node.getGraph() == null)) {
562         orgChart.showLocalHierarchy(employee);
563         node = orgChart.getNodeForUserObject(employee);
564       }
565       
566       if(node != null) {
567         if(e.isAddedPath()) {
568           final Graph2D graph2D = orgChart.getGraph2D();
569           if(!graph2D.isSelected(node)) {
570             graph2D.unselectAll();
571             graph2D.setSelected(node, e.isAddedPath());
572             orgChart.focusNode(node);
573           }
574         }
575       }
576     }        
577   }
578 
579   /**
580    * A Graph2DSelectionListener that propagates selection changes to a JTree.
581    */
582   class OrgChartSelectionUpdater implements Graph2DSelectionListener {    
583     public void onGraph2DSelectionEvent(final Graph2DSelectionEvent e) {
584       if(e.getSubject() instanceof Node) {
585         final Node node = (Node) e.getSubject();
586         final Employee p = (Employee) orgChart.getUserObject(node);
587         if(p != null) {
588           syncTreeSelection(node);
589           if(orgChart.getGraph2D().isSelected(node)) { 
590             updatePropertiesTable(p);          
591           }
592         }
593       }    
594     }
595     
596     void syncTreeSelection(final Node node) {
597       
598       final DefaultTreeSelectionModel smodel = (DefaultTreeSelectionModel) tree.getSelectionModel();
599       smodel.setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
600           
601       final DefaultTreeModel model = (DefaultTreeModel) tree.getModel();
602       final TreeNode treeNode = (TreeNode) orgChart.getTreeNode(node);
603       final TreeNode[] pathToRoot = model.getPathToRoot(treeNode);
604       final TreePath path = new TreePath(pathToRoot);
605        
606       if(orgChart.getGraph2D().isSelected(node)) {
607         smodel.addSelectionPath(path);
608       } else  {
609         smodel.removeSelectionPath(path);
610       }
611       tree.scrollPathToVisible(path);
612     }
613   }
614 
615   /**
616    * Create a panel for a component and adds a title to it. 
617    */
618   public JPanel createTitledPanel(final JComponent content, final String title) {
619     final JPanel panel = new JPanel();
620     final JLabel label = new JLabel(title);
621     label.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
622     label.setBackground(new Color(240,240,240));
623     label.setOpaque(true);
624     label.setForeground(Color.DARK_GRAY);
625     label.setFont(label.getFont().deriveFont(Font.BOLD));
626     label.setFont(label.getFont().deriveFont(13.0f));
627     panel.setLayout(new BorderLayout());
628     panel.add(label, BorderLayout.NORTH);
629     panel.add(content, BorderLayout.CENTER);
630     return panel;
631   }
632   
633   /**
634    * Main driver method.
635    */
636   public static void main(final String[] args) {
637     EventQueue.invokeLater(new Runnable() {
638       public void run() {
639         DemoDefaults.initLnF();
640         (new OrgChartDemo("resources/orgcharthelp.html")).start();
641       }
642     });
643   }
644 }
645