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