1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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.Shape;
30 import java.awt.Stroke;
31 import java.awt.geom.Area;
32 import java.awt.geom.Point2D;
33 import java.awt.geom.Rectangle2D;
34 import java.util.ArrayList;
35 import java.util.HashMap;
36 import java.util.List;
37 import java.util.Map;
38 import java.util.Set;
39 import java.util.concurrent.Executors;
40 import java.util.concurrent.ExecutorService;
41
42 import javax.swing.SwingUtilities;
43 import javax.swing.SwingWorker;
44
45 import com.google.common.collect.ImmutableSet;
46 import com.google.common.collect.Sets;
47
48 import org.dishevelled.bitset.MutableBitSet;
49 import org.dishevelled.bitset.ImmutableBitSet;
50 import org.dishevelled.venn.VennLayout;
51 import org.dishevelled.venn.VennModel;
52 import org.piccolo2d.PNode;
53 import org.piccolo2d.nodes.PArea;
54 import org.piccolo2d.nodes.PPath;
55 import org.piccolo2d.nodes.PText;
56 import org.piccolo2d.util.PBounds;
57
58
59
60
61
62
63
64 public class VennNode<E>
65 extends AbstractVennNode<E>
66 {
67
68 private static final ExecutorService EXECUTOR_SERVICE = Executors.newFixedThreadPool(2);
69
70
71 private static final long MS = 2000L;
72
73
74 private static final Paint AREA_PAINT = new Color(0, 0, 0, 0);
75
76
77 private static final Stroke AREA_STROKE = null;
78
79
80 private static final Paint AREA_STROKE_PAINT = null;
81
82
83 private static final Paint[] PAINTS = new Color[]
84 {
85 new Color(30, 30, 30, 50),
86 new Color(5, 37, 255, 50),
87 new Color(255, 100, 5, 50),
88 new Color(11, 255, 5, 50),
89
90
91 new Color(141, 211, 199, 50),
92 new Color(255, 255, 179, 50),
93 new Color(190, 186, 218, 50),
94 new Color(251, 128, 114, 50),
95 new Color(128, 177, 211, 50),
96 new Color(253, 180, 98, 50),
97 new Color(179, 222, 105, 50),
98 new Color(252, 205, 229, 50),
99 new Color(217, 217, 217, 50),
100 new Color(188, 128, 189, 50),
101 new Color(204, 235, 197, 50),
102 new Color(255, 237, 111, 50),
103 };
104
105
106 private static final Stroke STROKE = new BasicStroke(0.5f);
107
108
109 private static final Paint STROKE_PAINT = new Color(20, 20, 20);
110
111
112 private static final String[] DEFAULT_LABEL_TEXT = new String[]
113 {
114 "First set",
115 "Second set",
116 "Third set",
117 "Fourth set",
118 "Fifth set",
119 "Sixth set",
120 "Seventh set",
121 "Eighth set",
122 "Ninth set",
123 "Tenth set",
124 "Eleventh set",
125 "Twelveth set",
126 "Thirteenth set",
127 "Fourteenth set",
128 "Fifteenth set",
129 "Sixteenth set"
130 };
131
132
133 private final List<PPath> pathNodes;
134
135
136 private final List<PText> labels;
137
138
139 private final List<String> labelTexts;
140
141
142 private final Map<ImmutableBitSet, PArea> areaNodes;
143
144
145 private final Map<ImmutableBitSet, String> areaLabelTexts;
146
147
148 private final Map<ImmutableBitSet, PText> sizeLabels;
149
150
151 private VennLayout layout;
152
153
154 private final VennModel<E> model;
155
156
157
158
159
160
161
162 public VennNode(final VennModel<E> model)
163 {
164 super();
165 if (model == null)
166 {
167 throw new IllegalArgumentException("model must not be null");
168 }
169 this.model = model;
170 this.layout = new InitialLayout();
171
172 pathNodes = new ArrayList<PPath>(this.model.size());
173 labels = new ArrayList<PText>(this.model.size());
174 labelTexts = new ArrayList<String>(this.model.size());
175 areaNodes = new HashMap<ImmutableBitSet, PArea>();
176 areaLabelTexts = new HashMap<ImmutableBitSet, String>();
177 sizeLabels = new HashMap<ImmutableBitSet, PText>();
178
179 createNodes();
180 updateLabels();
181 }
182
183
184
185
186
187 private void createNodes()
188 {
189 for (int i = 0, size = size(); i < size; i++)
190 {
191 PPath pathNode = new PPath.Double();
192 pathNode.setPaint(PAINTS[i]);
193 pathNode.setStroke(STROKE);
194 pathNode.setStrokePaint(STROKE_PAINT);
195 pathNodes.add(pathNode);
196
197 labelTexts.add(DEFAULT_LABEL_TEXT[i]);
198
199 PText label = new PText();
200 labels.add(label);
201 }
202
203
204 Set<Set<Integer>> powerSet = Sets.powerSet(range(size()));
205 for (Set<Integer> set : powerSet)
206 {
207 if (!set.isEmpty())
208 {
209 ImmutableBitSet key = toImmutableBitSet(set);
210
211 PArea areaNode = new PArea();
212 areaNode.setPaint(AREA_PAINT);
213 areaNode.setStroke(AREA_STROKE);
214 areaNode.setStrokePaint(AREA_STROKE_PAINT);
215 areaNodes.put(key, areaNode);
216
217 PText sizeLabel = new PText();
218 sizeLabels.put(key, sizeLabel);
219 }
220 }
221
222 for (PText label : labels)
223 {
224 addChild(label);
225 }
226 for (PPath pathNode : pathNodes)
227 {
228 addChild(pathNode);
229 }
230 for (PText sizeLabel : sizeLabels.values())
231 {
232 addChild(sizeLabel);
233 }
234 for (PArea areaNode : areaNodes.values())
235 {
236 addChild(areaNode);
237 }
238 }
239
240
241
242
243 private void layoutNodes()
244 {
245 for (int i = 0, size = size(); i < size; i++)
246 {
247 PPath pathNode = pathNodes.get(i);
248 pathNode.reset();
249 pathNode.append(layout.get(i), false);
250 PBounds pathNodeBounds = pathNode.getBoundsReference();
251
252 PText label = labels.get(i);
253 PBounds labelBounds = label.getBoundsReference();
254
255 label.setOffset(pathNodeBounds.getX() + pathNodeBounds.getWidth() / 2.0d - labelBounds.getWidth() / 2.0d,
256 pathNodeBounds.getY() - labelBounds.getHeight() / 2.0d - 12.0d);
257 }
258
259 for (ImmutableBitSet key : areaNodes.keySet())
260 {
261 int first = first(key);
262 int[] additional = additional(key);
263
264 PArea areaNode = areaNodes.get(key);
265 areaNode.reset();
266 areaNode.add(new Area(layout.get(first)));
267
268 for (int i = 0, size = additional.length; i < size; i++)
269 {
270 areaNode.intersect(new Area(layout.get(additional[i])));
271 }
272
273 for (int i = 0, size = size(); i < size; i++)
274 {
275 if (!key.getQuick(i))
276 {
277 areaNode.subtract(new Area(layout.get(i)));
278 }
279 }
280
281 Point2D luneCenter = layout.luneCenter(first, additional);
282 PText sizeLabel = sizeLabels.get(key);
283 PBounds sizeLabelBounds = sizeLabel.getBoundsReference();
284
285 sizeLabel.setOffset(luneCenter.getX() - sizeLabelBounds.getWidth() / 2.0d,
286 luneCenter.getY() - sizeLabelBounds.getHeight() / 2.0d);
287
288 EXECUTOR_SERVICE.submit(new LayoutWorker(areaNode.getAreaReference(), sizeLabel));
289 }
290 }
291
292 @Override
293 protected void updateLabels()
294 {
295 for (int i = 0; i < size(); i++)
296 {
297 PText label = labels.get(i);
298 label.setText(buildLabel(labelTexts.get(i), model.get(i).size()));
299 label.setVisible(getDisplayLabels());
300 }
301 for (ImmutableBitSet key : areaNodes.keySet())
302 {
303 int first = first(key);
304 int[] additional = additional(key);
305 int size = model.exclusiveTo(first, additional).size();
306 boolean isEmpty = (size == 0);
307
308 PArea areaNode = areaNodes.get(key);
309 boolean areaNodeIsEmpty = !(layout instanceof VennNode.InitialLayout) && areaNode.isEmpty();
310
311 PText sizeLabel = sizeLabels.get(key);
312 sizeLabel.setText(String.valueOf(size));
313 sizeLabel.setVisible(getDisplaySizeLabels() && !areaNodeIsEmpty && (getDisplaySizesForEmptyAreas() || !isEmpty));
314
315 areaLabelTexts.put(key, buildAreaLabel(first, additional));
316 }
317 }
318
319
320
321
322
323
324 public final int size()
325 {
326 return model.size();
327 }
328
329
330
331
332
333
334 public final VennLayout getLayout()
335 {
336 return layout;
337 }
338
339
340
341
342
343
344
345
346 public final void setLayout(final VennLayout layout)
347 {
348 if (layout == null)
349 {
350 throw new IllegalArgumentException("layout must not be null");
351 }
352 VennLayout oldLayout = this.layout;
353 this.layout = layout;
354 firePropertyChange(-1, "layout", oldLayout, this.layout);
355
356 SwingUtilities.invokeLater(new Runnable()
357 {
358 @Override
359 public void run()
360 {
361 layoutNodes();
362 updateLabels();
363 }
364 });
365 }
366
367
368
369
370
371
372 public final VennModel<E> getModel()
373 {
374 return model;
375 }
376
377
378
379
380
381
382
383
384 public final PPath getPath(final int index)
385 {
386 return pathNodes.get(index);
387 }
388
389
390
391
392
393
394
395
396 public final String getLabelText(final int index)
397 {
398 return labelTexts.get(index);
399 }
400
401
402
403
404
405
406
407
408
409
410 public final void setLabelText(final int index, final String labelText)
411 {
412 String oldLabelText = labelTexts.get(index);
413 labelTexts.set(index, labelText);
414 firePropertyChange(-1, "labelTexts", oldLabelText, labelTexts.get(index));
415 updateLabels();
416 }
417
418
419
420
421
422
423
424
425
426 public final PText getLabel(final int index)
427 {
428 return labels.get(index);
429 }
430
431
432
433
434
435
436
437
438
439
440 public final PArea getArea(final int index, final int... additional)
441 {
442 checkIndices(index, additional);
443 return areaNodes.get(toImmutableBitSet(index, additional));
444 }
445
446
447
448
449
450
451
452
453
454
455 public final String getAreaLabelText(final int index, final int... additional)
456 {
457 checkIndices(index, additional);
458 return areaLabelTexts.get(toImmutableBitSet(index, additional));
459 }
460
461
462
463
464
465
466
467
468
469
470 public final PText getSizeLabel(final int index, final int... additional)
471 {
472 checkIndices(index, additional);
473 return sizeLabels.get(toImmutableBitSet(index, additional));
474 }
475
476 @Override
477 public Iterable<PText> labels()
478 {
479 return labels;
480 }
481
482 @Override
483 public Iterable<PNode> nodes()
484 {
485 List<PNode> nodes = new ArrayList<PNode>(pathNodes.size() + areaNodes.size());
486 nodes.addAll(pathNodes);
487 nodes.addAll(areaNodes.values());
488 return nodes;
489 }
490
491 @Override
492 public PText labelForNode(final PNode node)
493 {
494 if (node instanceof PPath)
495 {
496 PPath pathNode = (PPath) node;
497 int index = pathNodes.indexOf(pathNode);
498 return labels.get(index);
499 }
500 return null;
501 }
502
503 @Override
504 public String labelTextForNode(final PNode node)
505 {
506 if (node instanceof PPath)
507 {
508 PPath pathNode = (PPath) node;
509 int index = pathNodes.indexOf(pathNode);
510 return labelTexts.get(index);
511 }
512 else if (node instanceof PArea)
513 {
514 PArea areaNode = (PArea) node;
515 for (Map.Entry<ImmutableBitSet, PArea> entry : areaNodes.entrySet())
516 {
517 if (entry.getValue().equals(areaNode))
518 {
519 return areaLabelTexts.get(entry.getKey());
520 }
521 }
522 }
523 return null;
524 }
525
526 @Override
527 public Iterable<PText> sizeLabels()
528 {
529 return sizeLabels.values();
530 }
531
532 @Override
533 public Set<E> viewForNode(final PNode node)
534 {
535 if (node instanceof PPath)
536 {
537 PPath pathNode = (PPath) node;
538 int index = pathNodes.indexOf(pathNode);
539 return model.get(index);
540 }
541 else if (node instanceof PArea)
542 {
543 PArea areaNode = (PArea) node;
544 for (Map.Entry<ImmutableBitSet, PArea> entry : areaNodes.entrySet())
545 {
546 if (entry.getValue().equals(areaNode))
547 {
548 ImmutableBitSet key = entry.getKey();
549 return model.exclusiveTo(first(key), additional(key));
550 }
551 }
552 }
553 return null;
554 }
555
556
557
558
559
560
561
562
563
564 protected final String buildAreaLabel(final int index, final int... additional)
565 {
566 checkIndices(index, additional);
567 StringBuilder sb = new StringBuilder();
568 sb.append(labelTexts.get(index));
569 if (additional.length > 0) {
570 for (int i = 0, size = additional.length - 1; i < size; i++)
571 {
572 sb.append(", ");
573 sb.append(labelTexts.get(additional[i]));
574 }
575 sb.append(" and ");
576 sb.append(labelTexts.get(additional[Math.max(0, additional.length - 1)]));
577 }
578 sb.append(" only");
579 return sb.toString();
580 }
581
582
583
584
585
586
587
588
589
590 private void checkIndices(final int index, final int... additional)
591 {
592 int maxIndex = size() - 1;
593 if (index < 0 || index > maxIndex)
594 {
595 throw new IndexOutOfBoundsException("index out of bounds");
596 }
597 if (additional != null && additional.length > 0)
598 {
599 if (additional.length > maxIndex)
600 {
601 throw new IndexOutOfBoundsException("too many indices provided");
602 }
603 for (int i = 0, j = additional.length; i < j; i++)
604 {
605 if (additional[i] < 0 || additional[i] > maxIndex)
606 {
607 throw new IndexOutOfBoundsException("additional index [" + i + "] out of bounds");
608 }
609 }
610 }
611 }
612
613
614
615
616
617
618
619 static ImmutableSet<Integer> range(final int n)
620 {
621 Set<Integer> range = Sets.newHashSet();
622 for (int i = 0; i < n; i++)
623 {
624 range.add(Integer.valueOf(i));
625 }
626 return ImmutableSet.copyOf(range);
627 }
628
629
630
631
632
633
634
635
636 static int first(final ImmutableBitSet bitSet)
637 {
638 return (int) bitSet.nextSetBit(0L);
639 }
640
641
642
643
644
645
646
647
648
649 static int[] additional(final ImmutableBitSet bitSet)
650 {
651 int[] additional = new int[Math.max(0, (int) bitSet.cardinality() - 1)];
652 int index = 0;
653 long first = bitSet.nextSetBit(0);
654 for (long value = bitSet.nextSetBit(first + 1); value >= 0L; value = bitSet.nextSetBit(value + 1))
655 {
656 additional[index] = (int) value;
657 index++;
658 }
659 return additional;
660 }
661
662
663
664
665
666
667
668 static int first(final Set<Integer> values)
669 {
670 if (values.isEmpty())
671 {
672 return -1;
673 }
674 return values.iterator().next().intValue();
675 }
676
677
678
679
680
681
682
683 static int[] additional(final Set<Integer> values)
684 {
685 int[] additional = new int[Math.max(0, values.size() - 1)];
686 int index = -1;
687 for (Integer value : values)
688 {
689 if (index >= 0)
690 {
691 additional[index] = value.intValue();
692 }
693 index++;
694 }
695 return additional;
696 }
697
698
699
700
701
702
703
704
705
706 static ImmutableBitSet toImmutableBitSet(final int index, final int... additional)
707 {
708 int size = 1 + ((additional == null) ? 0 : additional.length);
709 MutableBitSet mutableBitSet = new MutableBitSet(size);
710 mutableBitSet.set(index);
711 if (additional != null)
712 {
713 for (int i = 0; i < additional.length; i++)
714 {
715 mutableBitSet.set(additional[i]);
716 }
717 }
718 mutableBitSet.trimTrailingZeros();
719 return mutableBitSet.immutableCopy();
720 }
721
722
723
724
725
726
727
728 static ImmutableBitSet toImmutableBitSet(final Set<Integer> indices)
729 {
730 if (indices == null)
731 {
732 throw new IllegalArgumentException("indices must not be null");
733 }
734 if (indices.isEmpty())
735 {
736 throw new IllegalArgumentException("indices must not be empty");
737 }
738 MutableBitSet mutableBitSet = new MutableBitSet(indices.size());
739 for (Integer index : indices)
740 {
741 mutableBitSet.set(index);
742 }
743 mutableBitSet.trimTrailingZeros();
744 return mutableBitSet.immutableCopy();
745 }
746
747
748
749
750 private final class InitialLayout implements VennLayout {
751
752 private final Point2D offscreenLeft = new Point2D.Double(-10000.0d, 0.0d);
753
754
755 private final Rectangle2D empty = new Rectangle2D.Double(-10000.0d, 0.0d, 0.0d, 0.0d);
756
757
758 @Override
759 public int size()
760 {
761 return VennNode.this.size();
762 }
763
764 @Override
765 public Shape get(final int index)
766 {
767 if (index < 0 || index >= size())
768 {
769 throw new IndexOutOfBoundsException("index " + index + " out of bounds");
770 }
771 return empty;
772 }
773
774 @Override
775 public Point2D luneCenter(final int index, final int... additional)
776 {
777 checkIndices(index, additional);
778 return offscreenLeft;
779 }
780
781 @Override
782 public Rectangle2D boundingRectangle()
783 {
784 return empty;
785 }
786 }
787
788
789
790
791
792 private final class LayoutWorker
793 extends SwingWorker<Point2D, Object>
794 {
795
796 private Area area;
797
798
799 private PText size;
800
801
802
803
804
805
806
807
808 private LayoutWorker(final Area area, final PText size)
809 {
810 this.area = area;
811 this.size = size;
812 }
813
814
815 @Override
816 public Point2D doInBackground()
817 {
818 return Centers.centroidOf(area);
819 }
820
821 @Override
822 protected void done()
823 {
824 try
825 {
826 Rectangle2D bounds = size.getFullBoundsReference();
827 Point2D centroid = get();
828 size.animateToPositionScaleRotation(centroid.getX() - (bounds.getWidth() / 2.0d),
829 centroid.getY() - (bounds.getHeight() / 2.0d), 1.0d, 0.0d, MS);
830 }
831 catch (Exception e)
832 {
833
834 }
835 }
836 }
837 }