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 java.awt.BorderLayout;
31  import java.awt.Cursor;
32  import java.awt.EventQueue;
33  import java.awt.Graphics2D;
34  import java.awt.Color;
35  import java.awt.geom.Ellipse2D;
36  import java.awt.event.ActionEvent;
37  import java.util.ArrayList;
38  import java.util.HashMap;
39  import java.util.List;
40  import java.util.Locale;
41  import java.util.Map;
42  import java.util.Iterator;
43  
44  import javax.swing.AbstractAction;
45  import javax.swing.Action;
46  import javax.swing.JScrollPane;
47  import javax.swing.JToolBar;
48  
49  import demo.view.DemoBase;
50  import demo.view.DemoDefaults;
51  import demo.view.application.DragAndDropDemo;
52  
53  import y.base.Edge;
54  import y.base.Node;
55  import y.layout.PortCandidate;
56  import y.layout.PortCandidateSet;
57  import y.layout.PortConstraintKeys;
58  import y.layout.hierarchic.IncrementalHierarchicLayouter;
59  import y.layout.hierarchic.incremental.SimplexNodePlacer;
60  import y.util.DataProviderAdapter;
61  import y.view.BridgeCalculator;
62  import y.view.DefaultGraph2DRenderer;
63  import y.view.EditMode;
64  import y.view.GenericNodeRealizer;
65  import y.view.Graph2D;
66  import y.view.NodeRealizer;
67  import y.view.GenericNodeRealizer.ContainsTest;
68  import y.view.GenericNodeRealizer.Painter;
69  
70  /**
71   * This demo shows how {@link PortCandidateSet}s can be used with {@link IncrementalHierarchicLayouter} to control 
72   * from what side edges connect to certain node types in the automatic layout process.
73   * <br/>
74   * Usage: The template nodes in the list to the left have different port candidate sets. Try changing
75   * the graph using the templates and note the effect for a new layout.
76   *  
77   * @see <a href="http://docs.yworks.com/yfiles/doc/api/index.html#/dguide/layout_advanced_features#adv_port_candidates" target="_blank">Section Port candidates</a> in the yFiles for Java Developer's Guide
78   */
79  public class PortCandidateDemo extends DemoBase {
80  
81    // the layouter instance to use for the automatic layouts
82    private IncrementalHierarchicLayouter layouter;
83    
84    private Map portCandidateMap;
85    
86    public PortCandidateDemo() {    
87      
88      final List nodeRealizerList = new ArrayList();
89      portCandidateMap = new HashMap();
90      addNodeRealizerTemplates(nodeRealizerList);
91  
92      // create DragAndDrop List support
93      DragAndDropDemo.DragAndDropSupport dndSupport = new DragAndDropDemo.DragAndDropSupport(nodeRealizerList, view);
94      
95      // add the list to the UI
96      contentPane.add(new JScrollPane(dndSupport.getList()), BorderLayout.WEST);
97      layouter = createLayouter();
98      
99      loadGraph("resource/PortCandidateDemo.graphml");
100   }
101 
102   /**
103    * Overwritten to disable node label setting and disallow resizing.
104    */
105   protected EditMode createEditMode() {
106     final EditMode editMode = super.createEditMode();
107     editMode.assignNodeLabel(false);
108     editMode.allowResizeNodes(false);
109     return editMode;
110   }
111 
112   /**
113    * Add a layout button to the ToolBar
114    */
115   protected JToolBar createToolBar() {
116     final Action layoutAction = new AbstractAction(
117             "Layout", SHARED_LAYOUT_ICON) {
118       public void actionPerformed(ActionEvent e) {
119         runLayout();
120       }
121     };
122 
123     final JToolBar toolBar = super.createToolBar();
124     toolBar.addSeparator();
125     toolBar.add(createActionControl(layoutAction));
126     return toolBar;
127   }
128 
129   /**
130    * Configures the view.
131    */
132   protected void configureDefaultRealizers() {
133     super.configureDefaultRealizers();
134     view.getGraph2D().getDefaultNodeRealizer().getLabel().setFontSize(16);
135     ((DefaultGraph2DRenderer) view.getGraph2DRenderer()).setBridgeCalculator(new BridgeCalculator());    
136   }
137 
138   /**
139    * Creates the Layouter instance.
140    */
141   private IncrementalHierarchicLayouter createLayouter() {
142     final IncrementalHierarchicLayouter ihl = new IncrementalHierarchicLayouter();
143     ((SimplexNodePlacer) ihl.getNodePlacer()).setBaryCenterModeEnabled(true);
144     ihl.setLayoutMode(IncrementalHierarchicLayouter.LAYOUT_MODE_FROM_SCRATCH);
145     ihl.setOrthogonallyRouted(true);
146     final Graph2D graph = view.getGraph2D();
147 
148     // create an adapter that returns the PortCandidateSet associated with the GenericNodeRealizer configuration
149     graph.addDataProvider(PortCandidateSet.NODE_DP_KEY, new DataProviderAdapter() {
150       public Object get(Object dataHolder) {
151         final Node node = (Node) dataHolder;
152         final NodeRealizer realizer = graph.getRealizer(node);
153         if (realizer instanceof GenericNodeRealizer) {
154           return portCandidateMap.get(((GenericNodeRealizer) realizer).getConfiguration());
155         } else {
156           return null;
157         }
158       }
159     });
160 
161     // create automatic bus structures for outgoing edges of "start" nodes
162     graph.addDataProvider(PortConstraintKeys.SOURCE_GROUPID_KEY, new DataProviderAdapter() {
163       public Object get(Object dataHolder) {
164         Edge edge = (Edge) dataHolder;
165         Node source = edge.source();
166         GenericNodeRealizer gnr = (GenericNodeRealizer) graph.getRealizer(source);
167         String sourceConfiguration = gnr.getConfiguration();
168         if ("start".equals(sourceConfiguration)) {
169           return source;
170         }
171         return null;
172       }
173     });
174 
175     //... and bus structures for incoming edges at "switch" and "branch" nodes
176     graph.addDataProvider(PortConstraintKeys.TARGET_GROUPID_KEY, new DataProviderAdapter() {
177       public Object get(Object dataHolder) {
178         Edge edge = (Edge) dataHolder;
179         Node target = edge.target();
180         GenericNodeRealizer gnr = (GenericNodeRealizer) graph.getRealizer(target);
181         String targetConfiguration = gnr.getConfiguration();
182         if ("switch".equals(targetConfiguration) || "branch".equals(targetConfiguration)) {
183           return target;
184         }
185         return null;
186       }
187     });
188 
189     return ihl;
190   }
191 
192   /**
193    * Run the layout in normal mode.
194    */
195   private void runLayout() {
196     Cursor oldCursor = view.getViewCursor();
197     try {
198       contentPane.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
199       view.applyLayoutAnimated(layouter);
200     } finally {
201       contentPane.setCursor(oldCursor);
202     }
203   }
204 
205   /** This method adds the possible NodeRealizer's for this application to the given list and configures the
206    * port candidate sets for each node type
207    */
208   private void addNodeRealizerTemplates(List nodeRealizerList) {    
209     {
210       //configure the PortCandidateSet for start nodes
211       //edges connected to start nodes may connect from any side. the port location is always in the center of the node   
212       PortCandidateSet candidateSet = new PortCandidateSet();
213       candidateSet.add(PortCandidate.createCandidate(0.0d, 0.0d, PortCandidate.ANY, 0.0), Integer.MAX_VALUE);
214       addNodeRealizerTemplate("start", nodeRealizerList, candidateSet);
215     }
216     {
217       //configure the PortCandidateSet for state nodes
218       //edges can connect form either north (top) or south (bottom) side. port locations are not constrained. 
219       PortCandidateSet candidateSet = new PortCandidateSet();
220       candidateSet.add(PortCandidate.createCandidate(PortCandidate.NORTH, 0.0), Integer.MAX_VALUE);
221       candidateSet.add(PortCandidate.createCandidate(PortCandidate.SOUTH, 0.0), Integer.MAX_VALUE);
222       addNodeRealizerTemplate("state", nodeRealizerList, candidateSet);
223     }
224     {      
225       // configure the PortCandidateSet for switch nodes
226       // at most one edge can connect from the east (right) and the west (left) side of the node.
227       // these east and west locations are preferred over the top and bottom locations which will only
228       // be used if the east and west sides are already saturated
229       PortCandidateSet candidateSet = new PortCandidateSet();
230       candidateSet.add(PortCandidate.createCandidate(0.0, -15.0, PortCandidate.NORTH, 0.0), 1);
231       candidateSet.add(PortCandidate.createCandidate(0.0, +15.0, PortCandidate.SOUTH, 0.0), 1);
232       candidateSet.add(PortCandidate.createCandidate(+30.0, 0.0, PortCandidate.EAST, 0.0), 1);
233       candidateSet.add(PortCandidate.createCandidate(-30.0, 0.0, PortCandidate.WEST, 0.0), 1);
234       candidateSet.add(PortCandidate.createCandidate(0.0, -15.0, PortCandidate.NORTH, 1.0), Integer.MAX_VALUE);
235       candidateSet.add(PortCandidate.createCandidate(0.0, +15.0, PortCandidate.SOUTH, 1.0), Integer.MAX_VALUE);
236       addNodeRealizerTemplate("switch", nodeRealizerList, candidateSet);
237     }
238     {
239       // configure the PortCandidateSet for branch nodes
240       // at most one edge can connect from the south (bottom) side of the node.
241       // this south location is preferred over the left and right side locations which will only
242       // be used if the south side is already saturated
243       PortCandidateSet candidateSet = new PortCandidateSet();
244       candidateSet.add(PortCandidate.createCandidate(0.0, -15.0, PortCandidate.NORTH, 0.0), Integer.MAX_VALUE);
245       candidateSet.add(PortCandidate.createCandidate(0.0, +15.0, PortCandidate.SOUTH, 0.0), 1);
246       candidateSet.add(PortCandidate.createCandidate(+30.0, 0.0, PortCandidate.EAST, 1.0), Integer.MAX_VALUE);
247       candidateSet.add(PortCandidate.createCandidate(-30.0, 0.0, PortCandidate.WEST, 1.0), Integer.MAX_VALUE);
248       addNodeRealizerTemplate("branch", nodeRealizerList, candidateSet);
249     }
250     {     
251       // configure the PortCandidateSet for end nodes  
252       // the first edge will be connected to the top side
253       // the second edge will be connected to either the left or right side
254       // the third edge will be connected to either the left or right side but not the same side as the second edge
255       // all further edges will either be connected to the left or right side      
256       PortCandidateSet candidateSet = new PortCandidateSet();
257       candidateSet.add(PortCandidate.createCandidate(0.0, -15.0, PortCandidate.NORTH, 0.0), 1);
258       candidateSet.add(PortCandidate.createCandidate(+30.0, 0.0, PortCandidate.EAST, 1.0), 1);
259       candidateSet.add(PortCandidate.createCandidate(-30.0, 0.0, PortCandidate.WEST, 1.0), 1);
260       candidateSet.add(PortCandidate.createCandidate(+30.0, 0.0, PortCandidate.EAST, 2.0), Integer.MAX_VALUE);
261       candidateSet.add(PortCandidate.createCandidate(-30.0, 0.0, PortCandidate.WEST, 2.0), Integer.MAX_VALUE);
262       addNodeRealizerTemplate("end", nodeRealizerList, candidateSet);
263     }
264   }
265   
266   void addNodeRealizerTemplate(String configuration, List nodeRealizerList, PortCandidateSet candidateSet) {
267     final GenericNodeRealizer.Factory factory = GenericNodeRealizer.getFactory();    
268     final Map map = GenericNodeRealizer.getFactory().createDefaultConfigurationMap();
269     GenericNodeRealizer gnr = (GenericNodeRealizer) view.getGraph2D().getDefaultNodeRealizer();
270     map.put(Painter.class, new PortCandidatePainter(
271         (Painter)factory.getImplementation(DemoDefaults.NODE_CONFIGURATION, Painter.class), candidateSet));
272     map.put(ContainsTest.class, factory.getImplementation(DemoDefaults.NODE_CONFIGURATION, ContainsTest.class));
273 
274     //create a switch node configuration     
275     factory.addConfiguration(configuration, map);
276     gnr = (GenericNodeRealizer) gnr.createCopy();
277     gnr.setConfiguration(configuration);
278     gnr.setLabelText(configuration);
279     nodeRealizerList.add(gnr);
280     portCandidateMap.put(configuration, candidateSet);
281   }
282 
283   /**
284    * Decorator implementation that draws the set of fixed port candidate locations as small gray circles
285    */
286   static class PortCandidatePainter implements Painter {
287     private final Painter delegatePainter;
288     private final PortCandidateSet set;
289 
290     PortCandidatePainter(Painter delegatePainter, PortCandidateSet set) {
291       this.delegatePainter = delegatePainter;
292       this.set = set;
293     }
294 
295     public void paint(NodeRealizer context, Graphics2D graphics) {
296       delegatePainter.paint(context, graphics);
297       if (set != null) {
298         final Iterator entries = set.getEntries();
299         graphics.setColor(new Color(0,0,0,128));
300         while (entries.hasNext()) {
301           PortCandidateSet.Entry entry = (PortCandidateSet.Entry) entries.next();
302           final PortCandidate candidate = entry.getPortCandidate();
303           if (candidate.isFixed()) {
304             double x = candidate.getXOffset() + context.getCenterX();
305             double y = candidate.getYOffset() + context.getCenterY();
306             graphics.fill(new Ellipse2D.Double(x - 2.0, y - 2.0, 4.0, 4.0));
307           }
308         }
309       }
310     }
311 
312     public void paintSloppy(NodeRealizer context, Graphics2D graphics) {
313       delegatePainter.paintSloppy(context, graphics);
314     }
315   }
316 
317   
318   public static void main(String[] args) {
319     EventQueue.invokeLater(new Runnable() {
320       public void run() {
321         Locale.setDefault(Locale.ENGLISH);
322         initLnF();
323         new PortCandidateDemo().start();
324       }
325     });    
326   }
327 }
328