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.view.orgchart;
15  
16  import java.awt.BorderLayout;
17  import java.awt.Color;
18  import java.awt.Component;
19  import java.awt.Dimension;
20  import java.awt.EventQueue;
21  import java.awt.FlowLayout;
22  import java.awt.Font;
23  import java.awt.event.ActionEvent;
24  import java.io.IOException;
25  import java.io.InputStream;
26  import java.net.URL;
27  
28  import javax.swing.AbstractAction;
29  import javax.swing.Action;
30  import javax.swing.BorderFactory;
31  import javax.swing.Box;
32  import javax.swing.BoxLayout;
33  import javax.swing.ButtonGroup;
34  import javax.swing.ImageIcon;
35  import javax.swing.JButton;
36  import javax.swing.JCheckBox;
37  import javax.swing.JComponent;
38  import javax.swing.JEditorPane;
39  import javax.swing.JFrame;
40  import javax.swing.JLabel;
41  import javax.swing.JPanel;
42  import javax.swing.JRadioButton;
43  import javax.swing.JRootPane;
44  import javax.swing.JScrollPane;
45  import javax.swing.JSplitPane;
46  import javax.swing.JTable;
47  import javax.swing.JTree;
48  import javax.swing.event.ChangeEvent;
49  import javax.swing.event.ChangeListener;
50  import javax.swing.event.TreeSelectionEvent;
51  import javax.swing.event.TreeSelectionListener;
52  import javax.swing.table.DefaultTableModel;
53  import javax.swing.tree.DefaultTreeCellRenderer;
54  import javax.swing.tree.DefaultTreeModel;
55  import javax.swing.tree.DefaultTreeSelectionModel;
56  import javax.swing.tree.TreeModel;
57  import javax.swing.tree.TreeNode;
58  import javax.swing.tree.TreePath;
59  import javax.swing.tree.TreeSelectionModel;
60  
61  import demo.view.DemoDefaults;
62  import org.xml.sax.InputSource;
63  
64  import demo.view.orgchart.OrgChartTreeModel.Employee;
65  
66  import y.base.Node;
67  import y.base.NodeCursor;
68  import y.view.Graph2DSelectionEvent;
69  import y.view.Graph2DSelectionListener;
70  import y.view.Overview;
71  
72  /**
73   * This demo visualizes an organization chart. This comprehensive demo shows 
74   * many aspects of yFiles. In particular it shows how to
75   * <ul>
76   * <li>visualize XML-formatted data as a graph</li>
77   * <li>create a tree diagram from a {@link javax.swing.tree.TreeModel}</li>
78   * <li>create customized node realizers that show text and multiple icons</li>
79   * <li>create a customized {@link y.view.NavigationMode} view mode</li>
80   * <li>create fancy roll-over effects when hovering over nodes</li> 
81   * <li>implement level of detail (LoD) rendering of graphs</li>
82   * <li>synchronize the selection state of {@link javax.swing.JTree} and {@link y.view.Graph2D}</li>
83   * <li>customize {@link y.layout.tree.GenericTreeLayouter} to make it a perfect match for laying out organization charts</li>
84   * <li>create local views for a large diagram</li>
85   * <li>apply incremental layout and apply nice fade in and fade out effects to added or removed elements</li>
86   * <li>implement and use keyboard navigation for {@link y.view.Graph2DView}</li>
87   * </ul>
88   * <p>
89   * This demo is composed of multiple classes. Class {@link demo.view.orgchart.OrgChartDemo} is the organization chart application and driver class 
90   * that organizes the UI elements and makes use of the Swing component {@link demo.view.orgchart.JOrgChart}. The model data used in this sample
91   * is represented by the Swing tree model {@link demo.view.orgchart.OrgChartTreeModel}. Class {@link demo.view.orgchart.JOrgChart}
92   * builds upon the more generic tree chart component {@link demo.view.orgchart.JTreeChart}. In a nutshell, JTreeChart 
93   * visualizes a generic TreeModel and includes all the viewer logic, while JOrgChart visualizes a OrgChartTreeModel and 
94   * customizes the look and feel of the component. Also it customizes the look and feel of the component to make it suitable for
95   * organization charts.
96   * </p> 
97   */
98  public class OrgChartDemo {
99    
100   private JOrgChart orgChart;
101   private JTable propertiesTable;
102   private JTree tree;
103     
104   /**
105    * Adds all UI elements of the application to a root pane container. 
106    */
107   public void addContentTo( final JRootPane rootPane ) {
108     final JPanel contentPane = new JPanel(new BorderLayout());
109 
110     OrgChartTreeModel model = readOrgChart(getClass().getResource("resources/orgchartmodel.xml"));
111     if (model != null) {
112       Box leftPanel = new Box(BoxLayout.Y_AXIS);
113 
114       orgChart = createOrgChart(model);
115       orgChart.setFitContentOnResize(true);
116 
117       Overview overview = orgChart.createOverview();
118       leftPanel.add(createTitledPanel(overview, "Overview"));
119 
120       tree = createStructureView(model);
121       JComponent viewOptions = createViewOptionsPanel();
122       leftPanel.add(viewOptions, BorderLayout.NORTH);
123       leftPanel.add(createTitledPanel(new JScrollPane(tree),"Structure View"));
124 
125       propertiesTable = createPropertiesTable();
126       JScrollPane scrollPane = new JScrollPane(propertiesTable);
127       scrollPane.setPreferredSize(new Dimension(200, 120));
128       leftPanel.add(createTitledPanel(scrollPane,"Properties"));
129 
130 
131       //sync app whenever orgchart selection changes occur
132       orgChart.getGraph2D().addGraph2DSelectionListener(new OrgChartSelectionUpdater());
133       //sync app whenever tree selection changes occur
134       tree.getSelectionModel().addTreeSelectionListener(new TreeSelectionUpdater());
135       //tree.addKeyListener(new TreeActionListener());
136 
137       contentPane.setLayout(new BorderLayout());
138       contentPane.add(leftPanel, BorderLayout.WEST);
139 
140       JComponent helpPane = createHelpPane(getClass().getResource("resources/orgcharthelp.html"));
141       helpPane.setMinimumSize(new Dimension(200,10));
142       
143       JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,
144           createTitledPanel(orgChart, "Organization Chart"),
145           createTitledPanel(helpPane, "Help"));
146       splitPane.setOneTouchExpandable(true);
147       
148       splitPane.setResizeWeight(1);
149 
150       contentPane.add(splitPane, BorderLayout.CENTER);
151     } else {
152       contentPane.setPreferredSize(new Dimension(320, 24));
153       contentPane.add(new JLabel("Could not create Organization Chart.", JLabel.CENTER));
154     }
155 
156     rootPane.setContentPane(contentPane);
157   }
158 
159   /**
160    * Starts the application in a JFrame.
161    */
162   private void start() {
163     final JFrame frame = new JFrame("yFiles Organization Chart Demo");
164     frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
165     addContentTo(frame.getRootPane());
166     frame.pack();
167     frame.setVisible(true);
168     orgChart.requestFocus();
169   }
170 
171   /**
172    * Creates the application help pane.
173    */
174   JComponent createHelpPane(URL helpURL) {
175     try {
176       JEditorPane editorPane = new JEditorPane(helpURL);                
177       editorPane.setEditable(false);
178       editorPane.setPreferredSize(new Dimension(250, 250));
179       return new JScrollPane(editorPane);
180     } catch (IOException e) {
181       e.printStackTrace();
182     }
183     return null;
184   }
185 
186   /**
187    * Reads and returns the OrgChartTreeModel from an XML file.
188    */
189   OrgChartTreeModel readOrgChart(URL orgChartURL) {
190     OrgChartTreeModel model = null;
191     try {
192       InputStream stream = orgChartURL.openStream();
193       model = OrgChartTreeModel.create(new InputSource(stream));    
194       stream.close();      
195     }catch(IOException ioex) {
196       System.err.println("Failed to read from " + orgChartURL);
197       ioex.printStackTrace();
198     }
199     return model;
200   }
201   
202   /**
203    * Creates a JOrgChart component for the given model.
204    */
205   JOrgChart createOrgChart(OrgChartTreeModel model) {    
206     JOrgChart orgChart = new JOrgChart(model);
207     orgChart.setPreferredSize(new Dimension(720, 750));
208     addGlassPaneComponents(orgChart);
209     return orgChart;
210   }
211   
212   /**
213    * Creates and returns a JTree-based structure view of the tree model.
214    */
215   JTree createStructureView(TreeModel model) {
216     JTree tree = new JTree(model);      
217     tree.setCellRenderer(new DefaultTreeCellRenderer() {
218       public Component getTreeCellRendererComponent(
219           JTree tree,Object value,boolean sel,boolean expanded,boolean leaf,int row, boolean hasFocus) {
220         if(value instanceof Employee) {
221             Employee employee = (Employee)value;
222             value = employee.name;
223         }
224         return super.getTreeCellRendererComponent(
225             tree, value, sel,
226             expanded, leaf, row,
227             hasFocus);            
228       }
229     });
230     
231     for(int i = 0; i < tree.getRowCount(); i++) {
232       tree.expandPath(tree.getPathForRow(i));
233     }
234 
235     return tree;
236   }
237   
238   /**
239    * Creates a JTable based properties view that displays the details of a selected model element.
240    */
241   JTable createPropertiesTable() {
242     DefaultTableModel tm = new DefaultTableModel();
243     tm.addColumn("", new Object[]{"Name", "Position", "Phone", "Fax", "Email", "Business Unit", "Status"});
244     tm.addColumn("", new Object[]{"",     "",         "",       "",    ""    , ""             , ""});
245     return new JTable(tm) {
246       public boolean isCellEditable(int row, int column) {
247         return false;
248       }
249     };
250   }
251   
252   /**
253    * Updates the properties table when being called.
254    */
255   void updatePropertiesTable(Employee e) {
256     DefaultTableModel tm = (DefaultTableModel) propertiesTable.getModel();
257     tm.setValueAt(e.name,             0, 1);
258     tm.setValueAt(e.position,         1, 1);
259     tm.setValueAt(e.phone,            2, 1);
260     tm.setValueAt(e.fax,              3, 1);
261     tm.setValueAt(e.email,            4, 1);
262     tm.setValueAt(e.businessUnit,     5, 1);
263     tm.setValueAt(e.status,           6, 1);   
264   }
265   
266   /**
267    * Create a panel that allows to configure view options.  
268    */
269   JPanel createViewOptionsPanel() {
270     JPanel panel = new JPanel();
271     panel.setBorder(BorderFactory.createEmptyBorder(10,10,10,10));
272     final JRadioButton button1 = new JRadioButton(new AbstractAction("Global View") {
273       public void actionPerformed(ActionEvent e) {
274         orgChart.showGlobalHierarchy();
275       }      
276     });
277     button1.setSelected(true);
278         
279     final JRadioButton button2 = new JRadioButton(new AbstractAction("Local View") {
280       public void actionPerformed(ActionEvent e) {
281         orgChart.showLocalHierarchy(null);                
282       }      
283     });
284         
285     ButtonGroup bg = new ButtonGroup();
286     bg.add(button1);
287     bg.add(button2);
288     panel.add(button1);
289     panel.add(button2);
290 
291     final JCheckBox checkBox = new JCheckBox(new AbstractAction("Show Colleagues") {
292       public void actionPerformed(ActionEvent e) {        
293         boolean result = ((JCheckBox)e.getSource()).isSelected();
294         if(result != orgChart.isSiblingViewEnabled()) {
295           orgChart.setSiblingViewEnabled(result);
296           orgChart.showLocalHierarchy(null);
297         }        
298       }      
299     });
300     checkBox.setEnabled(false);       
301     panel.add(checkBox);
302 
303     final JCheckBox checkBox2 = new JCheckBox(new AbstractAction("Show Business Units") {
304       public void actionPerformed(ActionEvent e) {        
305         boolean result = ((JCheckBox)e.getSource()).isSelected();
306         if(result != orgChart.isGroupViewEnabled()) {
307           orgChart.setGroupViewEnabled(result);
308           orgChart.updateChart();
309         }        
310       }      
311     });          
312     panel.add(checkBox2);
313     
314     button2.addChangeListener(new ChangeListener() {
315       public void stateChanged(ChangeEvent e) {
316         checkBox.setEnabled(button2.isSelected());                
317       }      
318     });
319     
320     panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
321     
322     return createTitledPanel(panel, "View Options");
323     
324   }
325   
326   /**
327    * Adds some toolbar buttons on top of JOrgChart.
328    */
329   private void addGlassPaneComponents(JOrgChart orgChart ) {
330     JPanel glassPane = orgChart.getGlassPane();
331     glassPane.setLayout(new BorderLayout());
332     
333     JPanel bar = new JPanel(new FlowLayout(FlowLayout.LEFT,5,0));
334     bar.setOpaque(false);
335     bar.setBorder(BorderFactory.createEmptyBorder(20,15,0,0));
336 
337     Action zoomIn = orgChart.createZoomInAction();
338     zoomIn.putValue(AbstractAction.SMALL_ICON, new ImageIcon(getClass().getResource("resources/icons/zoomIn.png")));
339     zoomIn.putValue(AbstractAction.SHORT_DESCRIPTION, "Zoom into Chart");
340     bar.add(createButton(zoomIn));
341     
342     Action zoomOut = orgChart.createZoomOutAction();
343     zoomOut.putValue(AbstractAction.SMALL_ICON, new ImageIcon(getClass().getResource("resources/icons/zoomOut.png")));    
344     zoomOut.putValue(AbstractAction.SHORT_DESCRIPTION, "Zoom out of Chart");
345     bar.add(createButton(zoomOut));
346   
347     Action fitContent = orgChart.createFitContentAction();
348     fitContent.putValue(AbstractAction.SMALL_ICON, new ImageIcon(getClass().getResource("resources/icons/zoomFit.png")));    
349     fitContent.putValue(AbstractAction.SHORT_DESCRIPTION, "Fit Chart into View");
350     bar.add(createButton(fitContent));
351     
352     glassPane.add(bar, BorderLayout.NORTH);
353   }
354 
355   /**
356    * Creates a button for an action.
357    */
358   private JButton createButton(Action action) {
359     JButton button = new JButton(action);
360     button.setBackground(Color.WHITE);
361     return button;
362   }
363 
364 
365   /**
366    * A TreeSelectionListener that propagates selection changes to JOrgChart.
367    */
368   class TreeSelectionUpdater implements TreeSelectionListener {
369     public void valueChanged(TreeSelectionEvent e) {
370       TreePath path = e.getPath();
371       Employee employee = (Employee) path.getLastPathComponent();
372       Node node = orgChart.getNodeForUserObject(employee);
373       
374       if(orgChart.isLocalViewEnabled() && (node == null || node.getGraph() == null)) {
375         orgChart.showLocalHierarchy(employee);
376         node = orgChart.getNodeForUserObject(employee);
377       }
378       
379       if(node != null) {
380         if(e.isAddedPath()) {
381           for(NodeCursor nc = orgChart.getGraph2D().selectedNodes(); nc.ok(); nc.next()) {
382             if(nc.node() != node) {
383               orgChart.getGraph2D().setSelected(nc.node(), false);
384             }
385           }
386           if(!orgChart.getGraph2D().isSelected(node)) {
387             orgChart.getGraph2D().setSelected(node, e.isAddedPath());        
388             orgChart.focusNode(node);
389           }
390         }
391       }
392     }        
393   }
394 
395   /**
396    * A Graph2DSelectionListener that propagates selection changes to a JTree.
397    */
398   class OrgChartSelectionUpdater implements Graph2DSelectionListener {    
399     public void onGraph2DSelectionEvent(Graph2DSelectionEvent e) {
400       if(e.getSubject() instanceof Node) {
401         Node node = (Node) e.getSubject();        
402         Employee p = (Employee) orgChart.getUserObject(node);
403         if(p != null) {
404           syncTreeSelection(node);
405           if(orgChart.getGraph2D().isSelected(node)) { 
406             updatePropertiesTable(p);          
407           }
408         }
409       }    
410     }
411     
412     void syncTreeSelection(Node node) {
413       
414       DefaultTreeSelectionModel smodel = (DefaultTreeSelectionModel) tree.getSelectionModel();
415       smodel.setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
416           
417       DefaultTreeModel model = (DefaultTreeModel) tree.getModel();     
418       TreeNode treeNode = (TreeNode) orgChart.getTreeNode(node);      
419       TreeNode[] pathToRoot = model.getPathToRoot(treeNode);
420       TreePath path = new TreePath(pathToRoot);
421        
422       if(orgChart.getGraph2D().isSelected(node)) {
423         smodel.addSelectionPath(path);
424       } else  {
425         smodel.removeSelectionPath(path);
426       }
427       tree.scrollPathToVisible(path);
428     }
429   }
430 
431   /**
432    * Create a panel for a component and adds a title to it. 
433    */
434   public JPanel createTitledPanel(JComponent content, String title) {
435     JPanel panel = new JPanel();
436     JLabel label = new JLabel(title);
437     label.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
438     label.setBackground(new Color(231, 219,182));
439     label.setOpaque(true);
440     label.setForeground(Color.DARK_GRAY);
441     label.setFont(label.getFont().deriveFont(Font.BOLD));
442     label.setFont(label.getFont().deriveFont(13.0f));
443     panel.setLayout(new BorderLayout());
444     panel.add(label, BorderLayout.NORTH);
445     panel.add(content, BorderLayout.CENTER);
446     return panel;
447   }
448   
449   /**
450    * Main driver method.
451    */
452   public static void main(String[] args) {
453     EventQueue.invokeLater(new Runnable() {
454       public void run() {
455         DemoDefaults.initLnF();
456         (new OrgChartDemo()).start();
457       }
458     });
459   }
460 }
461