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.hierarchic;
29  
30  import demo.view.hierarchy.GroupingDemo;
31  import y.base.Edge;
32  import y.base.EdgeCursor;
33  import y.base.EdgeList;
34  import y.base.EdgeMap;
35  import y.layout.hierarchic.IncrementalHierarchicLayouter;
36  import demo.layout.module.IncrementalHierarchicLayoutModule;
37  import y.option.OptionHandler;
38  import y.view.Arrow;
39  import y.view.EdgeRealizer;
40  import y.view.EditMode;
41  import y.view.Graph2D;
42  import y.view.LineType;
43  import y.view.PopupMode;
44  
45  import javax.swing.AbstractAction;
46  import javax.swing.Action;
47  import javax.swing.JPopupMenu;
48  import javax.swing.JToolBar;
49  import java.awt.EventQueue;
50  import java.awt.event.ActionEvent;
51  import java.awt.event.ActionListener;
52  import java.util.Locale;
53  
54  /**
55   * This demo showcases how {@link IncrementalHierarchicLayouter} can be used to layout graphs
56   * that have <em>undirected</em> edges or a mixture of <em>directed</em> and <em>undirected</em> edges.
57   * 
58   * <p>
59   *   Edges drawn without arrow head at the target side are automatically considered to be undirected. The same
60   *   applies for edges that have an arrow head at both ends. Whether an edge should be directed or undirected
61   *   can be adjusted via a pop-up menu as well as via toolbar buttons.
62   * </p>
63   */
64  public class UndirectedHierarchicLayoutDemo extends GroupingDemo {
65  
66    //use module to configure and run IncrementalHierarchicLayouter
67    private IncrementalHierarchicLayoutModule ihlModule;
68  
69    public UndirectedHierarchicLayoutDemo() {
70      this(null);
71    }
72  
73    public UndirectedHierarchicLayoutDemo(String helpFilePath) {
74      addHelpPane(helpFilePath);
75      
76      loadInitialGraph();
77  
78      ihlModule = new IncrementalHierarchicLayoutModule();
79    }
80  
81    protected EditMode createEditMode() {
82      final EditMode editMode = super.createEditMode();
83      editMode.setPopupMode(new DirectionSelectionPopupMode());
84      return editMode;
85    }
86  
87    /**
88     * Loads a suitable initial graph.
89     */
90    protected void loadInitialGraph() {
91      loadGraph("resource/UndirectedHierarchicLayoutDemo.graphml");
92    }
93  
94    protected JToolBar createToolBar() {
95      final JToolBar toolBar = super.createToolBar();
96      toolBar.addSeparator();
97  
98      //add action that executes layout algorithm
99      final Action layoutAction = new AbstractAction("Layout", SHARED_LAYOUT_ICON) {
100       public void actionPerformed(ActionEvent e) {
101         doLayout();
102       }
103     };
104     toolBar.add(createActionControl(layoutAction));
105     
106     //add action that allows to configure layout algorithm
107     final Action propertiesAction = new AbstractAction(
108             "Settings", getIconResource("resource/properties.png")) {
109       public void actionPerformed(ActionEvent e) {
110         final OptionHandler settings = ihlModule.getOptionHandler();
111         if (settings != null) {
112           final ActionListener listener = new ActionListener() {
113             public void actionPerformed(ActionEvent e) {
114               doLayout();
115             }
116           };
117 
118           OptionSupport.showDialog(settings, listener, false, view.getFrame());
119         }
120       }
121     };
122     toolBar.add(createActionControl(propertiesAction));
123     
124     //add actions that allow to make edges directed or undirected
125     toolBar.addSeparator();
126     toolBar.add(new MakeDirectedAction("Make Selection Directed"));
127     toolBar.addSeparator();
128     toolBar.add(new MakeUnDirectedAction("Make Selection Undirected"));
129 
130     return toolBar;
131   }
132 
133   /**
134    * Applies a layout on the current graph using {@link IncrementalHierarchicLayouter}.
135    */
136   private void doLayout() {
137     final Graph2D graph = view.getGraph2D();
138 
139     //register data that marks directed edges
140     final EdgeMap directedEdgesMap = getDirectedEdgesMap(graph);
141     graph.addDataProvider(IncrementalHierarchicLayouter.EDGE_DIRECTEDNESS_DPKEY, directedEdgesMap);
142 
143     //execute the layout
144     try {
145       ihlModule.start(view.getGraph2D());
146     } finally {
147       //clean-up
148       graph.removeDataProvider(IncrementalHierarchicLayouter.EDGE_DIRECTEDNESS_DPKEY);
149       graph.disposeEdgeMap(directedEdgesMap);
150     }
151   }
152 
153   /**
154    * Gets an {@link EdgeMap} that marks the directed edges of the given graph depending on the arrows
155    * defined for the edges.
156    */
157   private EdgeMap getDirectedEdgesMap(Graph2D graph) {
158     EdgeMap directedMap = graph.createEdgeMap();
159     for (EdgeCursor ec = graph.edges(); ec.ok(); ec.next()) {
160       final EdgeRealizer realizer = graph.getRealizer(ec.edge());
161       if ((realizer.getSourceArrow() == Arrow.NONE && realizer.getTargetArrow() != Arrow.NONE)
162           || (realizer.getTargetArrow() == Arrow.NONE && realizer.getSourceArrow() != Arrow.NONE)) {
163         //edge has some arrow at exactly one side -> consider as directed
164         directedMap.setDouble(ec.edge(), 1.0);
165       } else {
166         //edge has no arrows at all or arrows on both ends -> consider as undirected
167         directedMap.setDouble(ec.edge(), 0.0);
168       }
169     }
170     return directedMap;
171   }
172 
173   /**
174    * This popup mode allows to select whether an edge should be directed or undirected.
175    */
176   private class DirectionSelectionPopupMode extends PopupMode {
177 
178     public JPopupMenu getEdgePopup(Edge e) {
179       JPopupMenu pm = new JPopupMenu();
180       if (e != null) {
181         final EdgeCursor edgeCursor = new EdgeList(e).edges();
182         pm.add(new MakeDirectedAction("Make Directed", edgeCursor));
183         pm.add(new MakeUnDirectedAction("Make Undirected", edgeCursor));
184       }
185       return pm;
186     }
187 
188     public JPopupMenu getSelectionPopup(double x, double y) {
189       final EdgeCursor selectedEdges = getGraph2D().selectedEdges();
190       if (selectedEdges.ok()) {
191         final JPopupMenu pm = new JPopupMenu();
192         pm.add(new MakeDirectedAction("Make Directed", selectedEdges));
193         pm.add(new MakeUnDirectedAction("Make Undirected", selectedEdges));
194         return pm;
195       } else {
196         return null;
197       }
198     }
199   }
200 
201   private class MakeDirectedAction extends AbstractAction {
202 
203     private EdgeCursor edgeCursor;
204 
205     MakeDirectedAction(String name) {
206       this(name, null);
207     }
208 
209     private MakeDirectedAction(String name, EdgeCursor edgeCursor) {
210       super(name);
211       this.edgeCursor = edgeCursor;
212     }
213 
214     public void actionPerformed(ActionEvent e) {
215       EdgeCursor ec = edgeCursor != null ? edgeCursor : view.getGraph2D().selectedEdges();
216       for (; ec.ok(); ec.next()) {
217         final Edge edge = ec.edge();
218         makeDirected(edge);
219       }
220       view.updateView();
221     }
222 
223     private void makeDirected(Edge edge) {
224       final Graph2D graph = view.getGraph2D();
225       final EdgeRealizer realizer = graph.getRealizer(edge);
226       realizer.setLineType(LineType.LINE_1);
227       realizer.setSourceArrow(Arrow.NONE);
228       realizer.setTargetArrow(Arrow.STANDARD);
229     }
230   }
231 
232   private class MakeUnDirectedAction extends AbstractAction {
233 
234     private EdgeCursor edgeCursor;
235 
236     MakeUnDirectedAction(String name) {
237       this(name, null);
238     }
239 
240     MakeUnDirectedAction(String name, EdgeCursor edgeCursor) {
241       super(name);
242       this.edgeCursor = edgeCursor;
243     }
244 
245     public void actionPerformed(ActionEvent e) {
246       EdgeCursor ec = edgeCursor != null ? edgeCursor : view.getGraph2D().selectedEdges();
247       for (; ec.ok(); ec.next()) {
248         final Edge edge = ec.edge();
249         makeUndirected(edge);
250       }
251       view.updateView();
252 
253     }
254 
255     private void makeUndirected(Edge edge) {
256       final Graph2D graph = view.getGraph2D();
257       final EdgeRealizer realizer = graph.getRealizer(edge);
258       realizer.setLineType(LineType.DASHED_1);
259       realizer.setSourceArrow(Arrow.NONE);
260       realizer.setTargetArrow(Arrow.NONE);
261     }
262   }
263 
264 
265   /**
266    * Launches this demo.
267    *
268    * @param args ignored.
269    */
270   public static void main(String[] args) {
271     EventQueue.invokeLater(new Runnable() {
272       public void run() {
273         Locale.setDefault(Locale.ENGLISH);
274         initLnF();
275         (new UndirectedHierarchicLayoutDemo("resource/undirectedhierarchiclayouthelp.html"))
276             .start("Undirected Hierarchic Layout Demo");
277       }
278     });
279   }
280 }
281