1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33 package net.sf.jtreemap.swing;
34
35 import java.awt.Color;
36 import java.awt.FontMetrics;
37 import java.awt.Graphics;
38 import java.awt.Insets;
39 import java.awt.Point;
40 import java.awt.event.MouseAdapter;
41 import java.awt.event.MouseEvent;
42 import java.awt.event.MouseMotionAdapter;
43 import java.io.Serializable;
44 import java.util.Enumeration;
45
46 import javax.swing.JComponent;
47 import javax.swing.JToolTip;
48 import javax.swing.JTree;
49 import javax.swing.ToolTipManager;
50 import javax.swing.border.Border;
51 import javax.swing.tree.TreePath;
52
53 import net.sf.jtreemap.swing.provider.ColorProvider;
54 import net.sf.jtreemap.swing.provider.UniqueColorProvider;
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73 public class JTreeMap extends JComponent {
74 private static final long serialVersionUID = 7255952672238300249L;
75 private static final int BORDER_FOR_FONT = 5;
76 private static final int MAX_NUM_CHAR = 3;
77 private static final int INSET = 4;
78 private static final int DISMISS_DELAY_MS = 100000;
79 private static final Color TRANSPARENCY_COLOR = new Color(204, 204, 204, 128);
80
81
82
83
84 private JTree treeView;
85
86
87 private TreeMapNode activeLeaf = null;
88
89
90 private ColorProvider colorProvider = null;
91
92
93 private TreeMapNode displayedRoot = null;
94
95
96 private TreeMapNode root = null;
97
98
99 private SplitStrategy strategy = null;
100
101
102 private IToolTipBuilder toolTipBuilder;
103
104
105 private final Zoom zoom = new Zoom();
106
107
108
109
110
111
112
113
114
115
116
117 public JTreeMap(final TreeMapNode root) {
118 this(root, new SplitSquarified(), null, null, false);
119 }
120
121
122
123
124
125
126
127
128
129
130
131 public JTreeMap(final TreeMapNode root, final JTree treeView) {
132 this(root, new SplitSquarified(), null, null, false);
133 this.treeView = treeView;
134 }
135
136
137
138
139
140
141
142
143
144
145
146
147 public JTreeMap(final TreeMapNode root, final SplitStrategy strategy, final JTree treeView, final String weightPrefix, final String valuePrefix,
148 final boolean showWeight) {
149 this(root, strategy, weightPrefix, valuePrefix, showWeight);
150 this.treeView = treeView;
151 }
152
153
154
155
156
157
158
159
160
161
162
163 public JTreeMap(final TreeMapNode root, final SplitStrategy strategy, final String weightPrefix, final String valuePrefix,
164 final boolean showWeight) {
165
166 final ToolTipManager ttm = ToolTipManager.sharedInstance();
167 ttm.setInitialDelay(0);
168 ttm.setReshowDelay(0);
169 ttm.setDismissDelay(DISMISS_DELAY_MS);
170 ttm.setEnabled(true);
171 ttm.setLightWeightPopupEnabled(true);
172 setToolTipText("");
173
174
175 toolTipBuilder = new DefaultToolTipBuilder(this, weightPrefix, valuePrefix, showWeight);
176
177 setRoot(root);
178 setStrategy(strategy);
179 setColorProvider(new UniqueColorProvider());
180
181 addMouseMotionListener(new HandleMouseMotion());
182 addMouseListener(new HandleMouseClick());
183 }
184
185
186
187
188
189 public void calculatePositions() {
190 if (this.getStrategy() != null && displayedRoot != null) {
191 getStrategy().calculatePositions(this.displayedRoot);
192 }
193 }
194
195 @Override
196 public JToolTip createToolTip() {
197 return toolTipBuilder.getToolTip();
198 }
199
200
201
202
203
204
205
206
207
208 protected void draw(final Graphics g, final TreeMapNode item) {
209 if (item.isLeaf() && item.getValue() != null) {
210 g.setColor(this.colorProvider.getColor(item.getValue()));
211 g.fillRect(item.getX(), item.getY(), item.getWidth(), item.getHeight());
212 } else {
213 for (final Enumeration e = item.children(); e.hasMoreElements();) {
214 draw(g, (TreeMapNode) e.nextElement());
215 }
216 }
217 }
218
219
220
221
222
223
224
225
226
227
228
229
230 protected void drawLabel(final Graphics g, final TreeMapNode item) {
231 final FontMetrics fm = g.getFontMetrics(g.getFont());
232
233 if (fm.getHeight() < item.getHeight() - 2) {
234 String label = item.getLabel();
235
236 final int y = (item.getHeight() + fm.getAscent() - fm.getDescent()) / 2;
237 final int stringWidth = fm.stringWidth(label);
238
239
240 if (item.getWidth() - BORDER_FOR_FONT <= stringWidth) {
241
242
243 final int nbChar = label.length() * item.getWidth() / stringWidth;
244 if (nbChar > MAX_NUM_CHAR) {
245
246 label = label.substring(0, nbChar - MAX_NUM_CHAR) + "...";
247 } else {
248
249 label = "";
250 }
251 }
252 final int x = (item.getWidth() - fm.stringWidth(label)) / 2;
253
254
255 g.setColor(Color.black);
256 g.drawString(label, item.getX() + x + 1, item.getY() + y + 1);
257 g.drawString(label, item.getX() + x - 1, item.getY() + y + 1);
258 g.drawString(label, item.getX() + x + 1, item.getY() + y - 1);
259 g.drawString(label, item.getX() + x - 1, item.getY() + y - 1);
260 g.drawString(label, item.getX() + x + 1, item.getY() + y);
261 g.drawString(label, item.getX() + x - 1, item.getY() + y);
262 g.drawString(label, item.getX() + x, item.getY() + y + 1);
263 g.drawString(label, item.getX() + x, item.getY() + y - 1);
264
265 g.setColor(Color.white);
266 g.drawString(label, item.getX() + x, item.getY() + y);
267 }
268 }
269
270
271
272
273
274
275
276
277
278
279
280
281 protected void drawLabels(final Graphics g, final TreeMapNode item) {
282
283 g.setFont(this.getFont());
284 if (this.displayedRoot.isLeaf()) {
285 drawLabel(g, displayedRoot);
286 } else {
287 for (final Enumeration e = displayedRoot.children(); e.hasMoreElements();) {
288 drawLabel(g, (TreeMapNode) e.nextElement());
289 }
290 }
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310 }
311
312
313
314
315
316
317 public TreeMapNode getActiveLeaf() {
318 return activeLeaf;
319 }
320
321
322
323
324
325
326 public ColorProvider getColorProvider() {
327 return colorProvider;
328 }
329
330
331
332
333
334
335
336
337
338
339 public TreeMapNode getDisplayedRoot() {
340 return displayedRoot;
341 }
342
343
344
345
346
347
348 public TreeMapNode getRoot() {
349 return root;
350 }
351
352
353
354
355
356
357 public SplitStrategy getStrategy() {
358 return strategy;
359 }
360
361 @Override
362 public Point getToolTipLocation(final MouseEvent event) {
363 int posX;
364 int posY;
365 final JToolTip toolTip = createToolTip();
366 final int xMax = displayedRoot.getX() + displayedRoot.getWidth();
367 final int yMin = displayedRoot.getY();
368 if (this.activeLeaf != null) {
369 if (this.activeLeaf.getWidth() >= toolTip.getWidth() + 2 * INSET && activeLeaf.getHeight() >= toolTip.getHeight() + 2 * INSET) {
370 posX = activeLeaf.getX() + INSET;
371 posY = activeLeaf.getY() + INSET;
372 } else {
373 posX = activeLeaf.getX() + activeLeaf.getWidth() + INSET;
374 posY = activeLeaf.getY() - toolTip.getHeight() - INSET;
375 }
376
377 if (posY < yMin + INSET) {
378 posY = yMin + INSET;
379 }
380 if (posX + toolTip.getWidth() > xMax - INSET && this.activeLeaf.getX() >= toolTip.getWidth() + INSET) {
381 posX = activeLeaf.getX() - INSET - toolTip.getWidth();
382 }
383
384 return new Point(posX, posY);
385 }
386 return null;
387 }
388
389
390
391
392
393
394 @Override
395 protected void paintComponent(final Graphics g) {
396 super.paintComponent(g);
397 final int width = getSize().width;
398 final int height = getSize().height;
399 final Insets insets = getInsets();
400
401 final int border = TreeMapNode.getBorder();
402 root.setDimension(this.root.getX(), root.getY(), width - border - insets.left - insets.right, height - border - insets.top - insets.bottom);
403
404 if (!this.root.equals(this.displayedRoot)) {
405 displayedRoot.setDimension(this.displayedRoot.getX(), displayedRoot.getY(), width - border - insets.left - insets.right,
406 height - border - insets.top - insets.bottom);
407 }
408
409 calculatePositions();
410
411 if (this.displayedRoot.children().hasMoreElements()) {
412
413 g.setColor(this.getBackground());
414 g.fillRect(this.displayedRoot.getX(), displayedRoot.getY(), displayedRoot.getWidth() + border, displayedRoot.getHeight() + border);
415
416 draw(g, displayedRoot);
417
418 if (this.activeLeaf != null) {
419 reveal(g, activeLeaf);
420 }
421
422 drawLabels(g, displayedRoot);
423 }
424
425 }
426
427
428
429
430
431
432
433
434
435 protected void reveal(final Graphics g, final TreeMapNode item) {
436 if (item.isLeaf()) {
437 g.setColor(TRANSPARENCY_COLOR);
438 g.fillRect(item.getX(), item.getY(), item.getWidth(), item.getHeight());
439 }
440 }
441
442
443
444
445
446
447
448 public void setActiveLeaf(final TreeMapNode newActiveLeaf) {
449 if (newActiveLeaf == null || newActiveLeaf.isLeaf()) {
450 activeLeaf = newActiveLeaf;
451 }
452 }
453
454
455
456
457
458
459 @Override
460 public void setBorder(final Border border) {
461
462 Insets insets = getInsets();
463 displayedRoot.setDimension(this.displayedRoot.getX() - insets.left, displayedRoot.getY() - insets.top,
464 displayedRoot.getWidth() + insets.left + insets.right, displayedRoot.getHeight() + insets.top + insets.bottom);
465
466 super.setBorder(border);
467
468
469 insets = getInsets();
470 displayedRoot.setDimension(this.displayedRoot.getX() + insets.left, displayedRoot.getY() + insets.top,
471 displayedRoot.getWidth() - insets.left - insets.right, displayedRoot.getHeight() - insets.top - insets.bottom);
472 }
473
474
475
476
477
478
479
480 public void setColorProvider(final ColorProvider newColorProvider) {
481 colorProvider = newColorProvider;
482 }
483
484
485
486
487
488
489
490
491
492
493
494 public void setDisplayedRoot(final TreeMapNode newDisplayedRoot) {
495 displayedRoot = newDisplayedRoot;
496 }
497
498
499
500
501
502
503
504 public void setRoot(final TreeMapNode newRoot) {
505 root = newRoot;
506 final Insets insets = getInsets();
507 root.setX(insets.left);
508 root.setY(insets.top);
509 setDisplayedRoot(this.root);
510
511 }
512
513
514
515
516
517
518
519 public void setStrategy(final SplitStrategy newStrat) {
520 strategy = newStrat;
521 }
522
523
524
525
526
527
528
529 public void setToolTipBuilder(final IToolTipBuilder toolTipBuilder) {
530 this.toolTipBuilder = toolTipBuilder;
531 }
532
533
534
535
536
537
538
539
540 public void setZoomKeepProportion(final boolean keepProportion) {
541 zoom.setKeepProportion(keepProportion);
542 }
543
544
545
546
547 public void unzoom() {
548 zoom.undo();
549 }
550
551
552
553
554
555
556
557 public void zoom(final TreeMapNode dest) {
558
559 unzoom();
560
561 if (dest != null) {
562 zoom.execute(dest);
563 }
564 }
565
566
567
568
569
570
571 protected class HandleMouseMotion extends MouseMotionAdapter {
572 @Override
573 public void mouseMoved(final MouseEvent e) {
574 if (getDisplayedRoot().children().hasMoreElements()) {
575 final TreeMapNode t = getDisplayedRoot().getActiveLeaf(e.getX(), e.getY());
576 if (t == null || !t.equals(getActiveLeaf())) {
577 setActiveLeaf(t);
578 repaint();
579 }
580 }
581 }
582
583 }
584
585
586
587
588
589
590 protected class HandleMouseClick extends MouseAdapter {
591 @Override
592 public void mouseClicked(final MouseEvent e) {
593 if (e.getButton() == MouseEvent.BUTTON1 && e.getClickCount() == 1) {
594 final TreeMapNode t = getDisplayedRoot().getChild(e.getX(), e.getY());
595 if (t != null && !t.isLeaf()) {
596 if (treeView == null) {
597 zoom(t);
598 } else {
599 final TreePath path = new TreePath(t.getPath());
600 treeView.setSelectionPath(path);
601 treeView.scrollPathToVisible(path);
602 }
603
604 } else {
605 if (treeView == null) {
606 zoom((TreeMapNode) getDisplayedRoot().getParent());
607 } else {
608 final TreePath path = new TreePath(((TreeMapNode) getDisplayedRoot().getParent()).getPath());
609 treeView.setSelectionPath(path);
610 treeView.scrollPathToVisible(path);
611 }
612 }
613 repaint();
614 }
615 }
616
617 @Override
618 public void mouseExited(final MouseEvent arg0) {
619 setActiveLeaf(null);
620 repaint();
621 }
622 }
623
624
625
626
627
628
629 private class Zoom implements Serializable {
630 private static final long serialVersionUID = 6708828099608367996L;
631 private boolean enable;
632 private boolean keepProportion = false;
633
634 public Zoom() {
635 enable = true;
636 }
637
638
639
640
641
642
643
644 public void execute(final TreeMapNode dest) {
645 if (this.enable) {
646 JTreeMap.this.setActiveLeaf(null);
647
648 setNewDimension(dest);
649
650 JTreeMap.this.setDisplayedRoot(dest);
651
652 enable = false;
653 }
654 }
655
656
657
658
659 public boolean isKeepProportion() {
660 return keepProportion;
661 }
662
663
664
665
666
667 public void setKeepProportion(final boolean keepProportion) {
668 this.keepProportion = keepProportion;
669 }
670
671
672
673
674
675
676
677 protected void setNewDimension(final TreeMapNode dest) {
678 if (dest == null) {
679 return;
680 }
681 dest.setX(JTreeMap.this.getRoot().getX());
682 dest.setY(JTreeMap.this.getRoot().getY());
683
684 final int rootWidth = JTreeMap.this.getRoot().getWidth();
685 final int rootHeight = JTreeMap.this.getRoot().getHeight();
686
687 if (isKeepProportion()) {
688 final int destHeight = dest.getHeight();
689 final int destWidth = dest.getWidth();
690 final float divWidth = (float) destWidth / (float) rootWidth;
691 final float divHeight = (float) destHeight / (float) rootHeight;
692
693 if (divWidth >= divHeight) {
694 dest.setHeight(Math.round(destHeight / divWidth));
695 dest.setWidth(rootWidth);
696 } else {
697 dest.setHeight(rootHeight);
698 dest.setWidth(Math.round(destWidth / divHeight));
699 }
700
701 } else {
702 dest.setHeight(rootHeight);
703 dest.setWidth(rootWidth);
704 }
705 }
706
707
708
709
710 public void undo() {
711 if (!this.enable) {
712 JTreeMap.this.setDisplayedRoot(JTreeMap.this.getRoot());
713 enable = true;
714 }
715 }
716 }
717
718 public JTree getTreeView() {
719 return treeView;
720 }
721
722 public void setTreeView(final JTree treeView) {
723 this.treeView = treeView;
724 }
725 }
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741