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.withoutview;
29  
30  import y.base.DataProvider;
31  import y.base.Node;
32  import y.base.NodeMap;
33  import y.layout.BufferedLayouter;
34  import y.layout.DefaultLayoutGraph;
35  import y.layout.LayoutGraph;
36  import y.layout.LayoutOrientation;
37  import y.layout.NodeLayout;
38  import y.layout.PortCalculator;
39  import y.layout.grouping.GroupingKeys;
40  import y.layout.hierarchic.IncrementalHierarchicLayouter;
41  import y.util.Maps;
42  
43  import java.awt.Color;
44  import java.awt.EventQueue;
45  import java.awt.Graphics2D;
46  import java.awt.font.TextLayout;
47  import java.awt.geom.Ellipse2D;
48  import java.awt.geom.GeneralPath;
49  import java.awt.geom.Rectangle2D;
50  
51  /**
52   * Demonstrates how to use {@link PortCalculator} and
53   * {@link y.layout.IntersectionCalculator} to make sure that edge connection
54   * points lie on the visual outline of nodes. Because {@link LayoutGraph} and
55   * layout algorithms only support rectangular nodes, non-rectangular visual
56   * outlines are defined by associating a symbolic shape type identifier to each
57   * node (see {@link IntersectionCalculators#SHAPE_DPKEY}).
58   */
59  public class IntersectionCalculatorDemo {
60    public static void main( String[] args ) {
61      EventQueue.invokeLater(new Runnable() {
62        public void run() {
63          (new IntersectionCalculatorDemo()).doit();
64        }
65      });
66    }
67  
68    /**
69     * Creates a sample graph, arranges the sample graph, and finally visualizes
70     * the sample graph.
71     */
72    private void doit() {
73      LayoutGraph graph = createSampleGraph();
74  
75      layoutGraph(graph);
76  
77      displayGraph(graph);
78    }
79  
80    /**
81     * Arranges the specified graph.
82     * @param graph the graph to be arranged.
83     */
84    private void layoutGraph( final LayoutGraph graph ) {
85      // IncrementalHierarchicLayouter is good choice here because by default it
86      // connects edges to the border of the bounding box of the corresponding
87      // nodes.
88      final IncrementalHierarchicLayouter layouter = new IncrementalHierarchicLayouter();
89      layouter.setLayoutOrientation(LayoutOrientation.LEFT_TO_RIGHT);
90  
91      // IMPORTANT:
92      // register PortCalculator as a post-processing step
93      // PortCalculator corrects IHL's edge connection points such that edges
94      // no longer connect to the border of the bounding box but the visual
95      // outline of the node
96      layouter.prependStage(new PortCalculator());
97  
98      (new BufferedLayouter(layouter)).doLayout(graph);
99    }
100 
101   /**
102    * Displays the specified graph in a viewer component that supports elliptical
103    * and diamond shaped nodes as well as rectangular nodes.
104    * @param graph the graph to be displayed.
105    */
106   private void displayGraph( final LayoutGraph graph ) {
107     (new MyLayoutPreviewPanel(graph)).createFrame("").setVisible(true);
108   }
109 
110   /**
111    * Creates a sample graph. The graph created by this method comes with
112    * data providers that hold the shape type for each node as well as
113    * intersection calculators for source and target nodes of the graph's edges.
114    * @return a sample graph.
115    */
116   private LayoutGraph createSampleGraph() {
117     final DefaultLayoutGraph graph = new DefaultLayoutGraph();
118 
119     // create some nodes
120     //
121     // VERY IMPORTANT:
122     // assign sizes to nodes - most layout algorithms break on zero width
123     // or zero height nodes!
124     Node v0 = graph.createNode();
125     graph.setSize(v0, 60, 30);
126     Node v1 = graph.createNode();
127     graph.setSize(v1, 30, 60);
128     Node v2 = graph.createNode();
129     graph.setSize(v2, 90, 90);
130     Node v3 = graph.createNode();
131     graph.setSize(v3, 30, 30);
132     Node v4 = graph.createNode();
133     graph.setSize(v4, 30, 30);
134     Node v5 = graph.createNode();
135     graph.setSize(v5, 60, 40);
136     Node v6 = graph.createNode();
137     graph.setSize(v6, 30, 30);
138 
139     // create some edges...
140     graph.createEdge(v0, v1);
141     graph.createEdge(v0, v2);
142     graph.createEdge(v1, v3);
143     graph.createEdge(v3, v4);
144     graph.createEdge(v1, v5);
145     graph.createEdge(v2, v5);
146     graph.createEdge(v6, v2);
147 
148 
149     // associate a specific shape to each node
150     // if there is no explicit mapping for a node, it is assumed to be
151     // rectangular
152     final NodeMap shapes = Maps.createHashedNodeMap();
153     shapes.setInt(v0, IntersectionCalculators.SHAPE_DIAMOND);
154     shapes.setInt(v1, IntersectionCalculators.SHAPE_ELLIPSE);
155     shapes.setInt(v2, IntersectionCalculators.SHAPE_DIAMOND);
156     shapes.setInt(v5, IntersectionCalculators.SHAPE_ELLIPSE);
157     graph.addDataProvider(IntersectionCalculators.SHAPE_DPKEY, shapes);
158 
159     // associate node shape type based intersection calculators to the
160     // source nodes of the graph's edges
161     IntersectionCalculators.addIntersectionCalculator(graph, shapes, true);
162     // associate node shape type based intersection calculators to the
163     // target nodes of the graph's edges
164     IntersectionCalculators.addIntersectionCalculator(graph, shapes, false);
165     return graph;
166   }
167 
168 
169   
170   /**
171    * Visualizes layout graph instances.
172    * This extension adds support for elliptical and diamond shaped nodes.
173    */
174   public static class MyLayoutPreviewPanel extends LayoutPreviewPanel {
175     final Ellipse2D.Double ellipse;
176     final GeneralPath diamond;
177   
178     public MyLayoutPreviewPanel( final LayoutGraph graph ) {
179       super(graph);
180       ellipse = new Ellipse2D.Double();
181       diamond = new GeneralPath();
182     }
183 
184     protected void paint(
185             final Graphics2D g,
186             final LayoutGraph graph,
187             final Node node
188     ) {
189       final NodeLayout nl = graph.getNodeLayout(node);
190       final DataProvider dp = graph.getDataProvider(GroupingKeys.GROUP_DPKEY);
191       if (dp != null && dp.getBool(node)) {
192         rectangle.setFrame(nl.getX(), nl.getY(), nl.getWidth(), nl.getHeight());
193         g.draw(rectangle);
194       } else {
195         switch (getShape(graph, node)) {
196           case IntersectionCalculators.SHAPE_ELLIPSE:
197             ellipse.setFrame(nl.getX(), nl.getY(), nl.getWidth(), nl.getHeight());
198             g.fill(ellipse);
199             break;
200           case IntersectionCalculators.SHAPE_DIAMOND:
201             setDiamondFrame(diamond, nl.getX(), nl.getY(), nl.getWidth(), nl.getHeight());
202             g.fill(diamond);
203             break;
204           default:
205             rectangle.setFrame(nl.getX(), nl.getY(), nl.getWidth(), nl.getHeight());
206             g.fill(rectangle);
207             break;
208         }
209       }
210   
211       paintNodeLabel(g, graph, node, Integer.toString(node.index()));
212     }
213   
214     protected void paintNodeLabel(
215             final Graphics2D g,
216             final LayoutGraph graph,
217             final Node node,
218             final String text
219     ) {
220       final Color oldColor = g.getColor();
221       g.setColor(Color.white);
222   
223       final TextLayout tl = new TextLayout(text, g.getFont(), g.getFontRenderContext());
224       final Rectangle2D box = tl.getBounds();
225       final NodeLayout nl = graph.getNodeLayout(node);
226       final double tx = nl.getX() + (nl.getWidth() - box.getWidth())*0.5;
227       final double ty = nl.getY() + box.getHeight() + (nl.getHeight() - box.getHeight())*0.5;
228       tl.draw(g, (float) (tx), (float) (ty));
229   
230       g.setColor(oldColor);
231     }
232 
233     private static void setDiamondFrame(
234             final GeneralPath path,
235             final double x, final double y,
236             final double width, final double height
237     ) {
238       final double w2 = width * 0.5;
239       final double h2 = height * 0.5;
240       final double x2 = x + w2;
241       final double y2 = y + h2;
242   
243       path.reset();
244       path.moveTo((float) x2, (float) y);
245       path.lineTo((float) (x + width), (float) y2);
246       path.lineTo((float) x2, (float) (y + height));
247       path.lineTo((float) x, (float) y2);
248       path.closePath();
249     }
250 
251     private static int getShape( final LayoutGraph graph, final Node node ) {
252       final DataProvider dp = graph.getDataProvider(IntersectionCalculators.SHAPE_DPKEY);
253       if (dp != null) {
254         return dp.getInt(node);
255       }
256       return IntersectionCalculators.SHAPE_RECTANGLE;
257     }
258   }
259 }
260