View Javadoc

1   /*
2   
3       dsh-piccolo-venn  Piccolo2D venn diagram nodes and supporting classes.
4       Copyright (c) 2009-2013 held jointly by the individual authors.
5   
6       This library is free software; you can redistribute it and/or modify it
7       under the terms of the GNU Lesser General Public License as published
8       by the Free Software Foundation; either version 3 of the License, or (at
9       your option) any later version.
10  
11      This library is distributed in the hope that it will be useful, but WITHOUT
12      ANY WARRANTY; with out even the implied warranty of MERCHANTABILITY or
13      FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
14      License for more details.
15  
16      You should have received a copy of the GNU Lesser General Public License
17      along with this library;  if not, write to the Free Software Foundation,
18      Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA.
19  
20      > http://www.fsf.org/licensing/licenses/lgpl.html
21      > http://www.opensource.org/licenses/lgpl-license.php
22  
23  */
24  package org.dishevelled.piccolo.venn;
25  
26  import java.awt.BasicStroke;
27  import java.awt.Color;
28  import java.awt.Paint;
29  import java.awt.Stroke;
30  import java.awt.geom.Area;
31  import java.awt.geom.Ellipse2D;
32  import java.awt.geom.Point2D;
33  import java.awt.geom.Rectangle2D;
34  import java.util.Arrays;
35  import java.util.List;
36  import java.util.Set;
37  
38  import org.piccolo2d.PNode;
39  import org.piccolo2d.nodes.PArea;
40  import org.piccolo2d.nodes.PPath;
41  import org.piccolo2d.nodes.PText;
42  import org.dishevelled.venn.BinaryVennModel;
43  
44  /**
45   * Binary venn diagram node.
46   *
47   * @param <E> value type
48   * @author  Michael Heuer
49   * @version $Revision$ $Date$
50   */
51  public class BinaryVennNode<E>
52      extends AbstractBinaryVennNode<E>
53  {
54      /** Path node for the first set. */
55      private final PPath first = new PPath.Double(FIRST_SHAPE, STROKE);
56  
57      /** Area node for the first only view. */
58      private final PArea firstOnly = new PArea(AREA_STROKE);
59  
60      /** Label for the size of the first only view. */
61      private final PText firstOnlySize = new PText();
62  
63      /** Path node for the second set. */
64      private final PPath second = new PPath.Double(SECOND_SHAPE, STROKE);
65  
66      /** Area node for the second only view. */
67      private final PArea secondOnly = new PArea(AREA_STROKE);
68  
69      /** Label for the size of the second only view. */
70      private final PText secondOnlySize = new PText();
71  
72      /** Area node for the intersection view. */
73      private final PArea intersection = new PArea(AREA_STROKE);
74  
75      /** Label for the size of the intersection view. */
76      private final PText intersectionSize = new PText();
77  
78      /** List of nodes. */
79      private final List<PNode> nodes = Arrays.asList(new PNode[] { firstOnly, secondOnly, intersection });
80  
81      /** List of size labels. */
82      private final List<PText> sizeLabels = Arrays.asList(new PText[] { firstOnlySize, secondOnlySize, intersectionSize });
83  
84      /** Cached area. */
85      private Area f;
86  
87      /** Cached area. */
88      private Area s;
89  
90      /** Cached rectangle. */
91      private Rectangle2D a = new Rectangle2D.Double();
92  
93      /** Cached area. */
94      private Rectangle2D b = new Rectangle2D.Double();
95  
96      /** Cached point. */
97      private Point2D c = new Point2D.Double();
98  
99      /** Label gap, <code>8.0d</code>. */
100     private static final double LABEL_GAP = 8.0d;
101 
102     /** Adjust label gap, <code>10.0d</code>. */
103     private static final double ADJUST_LABEL_GAP = 10.0d;
104 
105     /** First shape. */
106     private static final Ellipse2D FIRST_SHAPE = new Ellipse2D.Double(0.0d, 0.0d, 128.0d, 128.0d);
107 
108     /** First paint. */
109     private static final Paint FIRST_PAINT = new Color(30, 30, 30, 50);
110 
111     /** Second shape. */
112     private static final Ellipse2D SECOND_SHAPE = new Ellipse2D.Double(((2.0d * 128.0d) / 3.0d), 0.0d, 128.0d, 128.0d);
113 
114     /** Second paint. */
115     private static final Paint SECOND_PAINT = new Color(5, 37, 255, 50);
116 
117     /** Stroke. */
118     private static final Stroke STROKE = new BasicStroke(0.5f);
119 
120     /** Stroke paint. */
121     private static final Paint STROKE_PAINT = new Color(20, 20, 20);
122 
123     /** Area paint. */
124     private static final Paint AREA_PAINT = new Color(0, 0, 0, 0);
125 
126     /** Area stroke. */
127     private static final Stroke AREA_STROKE = null;
128 
129 
130     /**
131      * Create a new empty binary venn node.
132      */
133     public BinaryVennNode()
134     {
135         super();
136         initNodes();
137         updateContents();
138     }
139 
140     /**
141      * Create a new binary venn node with the specified sets.
142      *
143      * @param firstLabelText label text for the first set
144      * @param first first set, must not be null
145      * @param secondLabelText label text for the second set
146      * @param second second set, must not be null
147      */
148     public BinaryVennNode(final String firstLabelText, final Set<? extends E> first,
149         final String secondLabelText, final Set<? extends E> second)
150     {
151         super(firstLabelText, first, secondLabelText, second);
152         initNodes();
153         updateContents();
154     }
155 
156     /**
157      * Create a new binary venn node with the specified model.
158      *
159      * @param model model for this binary venn node, must not be null
160      */
161     public BinaryVennNode(final BinaryVennModel<E> model)
162     {
163         super(model);
164         initNodes();
165         updateContents();
166     }
167 
168 
169     /**
170      * Initialize nodes.
171      */
172     private void initNodes()
173     {
174         first.setPaint(FIRST_PAINT);
175         first.setStrokePaint(STROKE_PAINT);
176         second.setPaint(SECOND_PAINT);
177         second.setStrokePaint(STROKE_PAINT);
178         firstOnly.setPaint(AREA_PAINT);
179         secondOnly.setPaint(AREA_PAINT);
180         intersection.setPaint(AREA_PAINT);
181 
182         addChild(first);
183         addChild(second);
184         addChild(firstOnlySize);
185         addChild(secondOnlySize);
186         addChild(intersectionSize);
187         addChild(firstOnly);
188         addChild(secondOnly);
189         addChild(intersection);
190         addChild(getFirstLabel());
191         addChild(getSecondLabel());
192     }
193 
194     @Override
195     protected void updateLabels()
196     {
197         super.updateLabels();
198 
199         if (firstOnlySize != null) // updateLabels is called in super constructor
200         {
201             firstOnlySize.setVisible(getDisplaySizeLabels() && (getDisplaySizesForEmptyAreas() || !getModel().firstOnly().isEmpty()));
202             secondOnlySize.setVisible(getDisplaySizeLabels() && (getDisplaySizesForEmptyAreas() || !getModel().secondOnly().isEmpty()));
203             intersectionSize.setVisible(getDisplaySizeLabels() && (getDisplaySizesForEmptyAreas() || !getModel().intersection().isEmpty()));
204         }
205     }
206 
207     @Override
208     protected void updateContents()
209     {
210         firstOnlySize.setText(String.valueOf(getModel().firstOnly().size()));
211         secondOnlySize.setText(String.valueOf(getModel().secondOnly().size()));
212         intersectionSize.setText(String.valueOf(getModel().intersection().size()));
213 
214         firstOnlySize.setVisible(getDisplaySizeLabels() && (getDisplaySizesForEmptyAreas() || !getModel().firstOnly().isEmpty()));
215         secondOnlySize.setVisible(getDisplaySizeLabels() && (getDisplaySizesForEmptyAreas() || !getModel().secondOnly().isEmpty()));
216         intersectionSize.setVisible(getDisplaySizeLabels() && (getDisplaySizesForEmptyAreas() || !getModel().intersection().isEmpty()));
217     }
218 
219     @Override
220     protected void layoutChildren()
221     {
222         f = new Area(first.getPathReference());
223         s = new Area(second.getPathReference());
224 
225         firstOnly.reset();
226         firstOnly.add(f);
227         firstOnly.subtract(s);
228 
229         secondOnly.reset();
230         secondOnly.add(s);
231         secondOnly.subtract(f);
232 
233         intersection.reset();
234         intersection.add(f);
235         intersection.intersect(s);
236 
237         offset(firstOnly.getAreaReference(), firstOnlySize);
238         offset(secondOnly.getAreaReference(), secondOnlySize);
239         offset(intersection.getAreaReference(), intersectionSize);
240 
241         labelLeft(firstOnly.getAreaReference(), getFirstLabel());
242         labelRight(secondOnly.getAreaReference(), getSecondLabel());
243         adjustLabels(getFirstLabel(), getSecondLabel());
244     }
245 
246     /**
247      * Offset the specified size label to the center of the specified area.
248      *
249      * @param area area
250      * @param size size label
251      */
252     private void offset(final Area area, final PText size)
253     {
254         b = size.getFullBoundsReference();
255         c = Centers.centerOf(area, c);
256         size.setOffset(c.getX() - (b.getWidth() / 2.0d), c.getY() - (b.getHeight() / 2.0d));
257     }
258 
259     /**
260      * Offset the specified label to the top and left of the center of the specified area.
261      *
262      * @param area area
263      * @param label label
264      */
265     private void labelLeft(final Area area, final PText label)
266     {
267         a = area.getBounds2D();
268         b = label.getFullBoundsReference();
269         c = Centers.centerOf(area, c);
270         label.setOffset(c.getX() - ((2.0d * b.getWidth()) / 3.0d), a.getY() - b.getHeight() - LABEL_GAP);
271     }
272 
273     /**
274      * Offset the specified label to the top and right of the center of the specified area.
275      *
276      * @param area area
277      * @param label label
278      */
279     private void labelRight(final Area area, final PText label)
280     {
281         a = area.getBounds2D();
282         b = label.getFullBoundsReference();
283         c = Centers.centerOf(area, c);
284         label.setOffset(c.getX() - (b.getWidth() / 3.0d), a.getY() - b.getHeight() - LABEL_GAP);
285     }
286 
287     /**
288      * Adjust the horizontal offsets for the specified pair of labels if their bounds overlap.
289      *
290      * @param leftLabel left label
291      * @param rightLabel right label
292      */
293     private void adjustLabels(final PText leftLabel, final PText rightLabel)
294     {
295         a = leftLabel.getFullBoundsReference();
296         b = rightLabel.getFullBoundsReference();
297         Rectangle2D.intersect(a, b, a);
298         if (a.getWidth() > 0.0d)
299         {
300             leftLabel.offset(-1.0 * a.getWidth() / 2.0d - ADJUST_LABEL_GAP, 0.0d);
301             rightLabel.offset(a.getWidth() / 2.0d + ADJUST_LABEL_GAP, 0.0d);
302         }
303     }
304 
305     /**
306      * Return the path node for the first set.
307      *
308      * @return the path node for the first set
309      */
310     public PPath getFirst()
311     {
312         return first;
313     }
314 
315     /**
316      * Return the path node for the second set.
317      *
318      * @return the path node for the second set
319      */
320     public PPath getSecond()
321     {
322         return second;
323     }
324 
325     /**
326      * Return the area node for the first only view.
327      *
328      * @return the area node for the first only view
329      */
330     public PArea getFirstOnly()
331     {
332         return firstOnly;
333     }
334 
335     /**
336      * Return the area node for the second only view.
337      *
338      * @return the area node for the second only view
339      */
340     public PArea getSecondOnly()
341     {
342         return secondOnly;
343     }
344 
345     /**
346      * Return the area node for the intersection view.
347      *
348      * @return the area node for the intersection view
349      */
350     public PArea getIntersection()
351     {
352         return intersection;
353     }
354 
355     @Override
356     public Iterable<PNode> nodes()
357     {
358         return nodes;
359     }
360 
361     @Override
362     public PText labelForNode(final PNode node)
363     {
364         if (firstOnly.equals(node))
365         {
366             return getFirstOnlyLabel();
367         }
368         else if (secondOnly.equals(node))
369         {
370             return getSecondOnlyLabel();
371         }
372         else if (intersection.equals(node))
373         {
374             return getIntersectionLabel();
375         }
376         return null;
377     }
378 
379     @Override
380     public String labelTextForNode(final PNode node)
381     {
382         if (firstOnly.equals(node))
383         {
384             return getFirstOnlyLabelText();
385         }
386         else if (secondOnly.equals(node))
387         {
388             return getSecondOnlyLabelText();
389         }
390         else if (intersection.equals(node))
391         {
392             return getIntersectionLabelText();
393         }
394         return null;
395     }
396 
397     @Override
398     public Iterable<PText> sizeLabels()
399     {
400         return sizeLabels;
401     }
402 
403     @Override
404     public Set<E> viewForNode(final PNode node)
405     {
406         if (firstOnly.equals(node))
407         {
408             return getModel().firstOnly();
409         }
410         else if (secondOnly.equals(node))
411         {
412             return getModel().secondOnly();
413         }
414         else if (intersection.equals(node))
415         {
416             return getModel().intersection();
417         }
418         return null;
419     }
420 }