View Javadoc

1   /*
2    * ObjectLab, http://www.objectlab.co.uk/open is supporting JTreeMap.
3    * 
4    * Based in London, we are world leaders in the design and development 
5    * of bespoke applications for the securities financing markets.
6    * 
7    * <a href="http://www.objectlab.co.uk/open">Click here to learn more</a>
8    *           ___  _     _           _   _          _
9    *          / _ \| |__ (_) ___  ___| |_| |    __ _| |__
10   *         | | | | '_ \| |/ _ \/ __| __| |   / _` | '_ \
11   *         | |_| | |_) | |  __/ (__| |_| |__| (_| | |_) |
12   *          \___/|_.__// |\___|\___|\__|_____\__,_|_.__/
13   *                   |__/
14   *
15   *                     www.ObjectLab.co.uk
16   *
17   * $Id: JTreeMap.java 118 2006-12-12 22:41:48Z ekingulen $
18   * 
19   * Copyright 2006 the original author or authors.
20   *
21   * Licensed under the Apache License, Version 2.0 (the "License"); you may not
22   * use this file except in compliance with the License. You may obtain a copy of
23   * the License at
24   *
25   * http://www.apache.org/licenses/LICENSE-2.0
26   *
27   * Unless required by applicable law or agreed to in writing, software
28   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
29   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
30   * License for the specific language governing permissions and limitations under
31   * the License.
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  /**
54   * JComponent who represents each element of a tree in a rectangle of more or
55   * less big size according to its importance in the tree.
56   * <p>
57   * A tree structure may includes more or less important elements. For example,
58   * in a tree structure of files, there can be files of big size. Then it can be
59   * interesting to know which repertory is the most important on a hard disk.
60   * <p>
61   * Moreover, we can add a code color which makes it possible to introduce new
62   * information into the representation of the tree structure.
63   * <p>
64   * So, in a JTreeMap, you can see the size and the value of an element in a
65   * tree.
66   * 
67   * @see net.sf.jtreemap.swing.TreeMapNode
68   * @author Laurent Dutheil
69   */
70  public class JTreeMap extends JComponent {
71      private static final int BORDER_FOR_FONT = 5;
72  
73      private static final int MAX_NUM_CHAR = 3;
74  
75      private static final int INSET = 4;
76  
77      private static final int DISMISS_DELAY_MS = 100000;
78  
79      private static final long serialVersionUID = 7255952672238300249L;
80  
81      private static final Color TRANSPARENCY_COLOR = new Color(204, 204, 204, 128);
82  
83      /**
84       * The optional tree representation of the hierarchical data.
85       */
86      private JTree treeView;
87  
88      // active leaf
89      private TreeMapNode activeLeaf = null;
90  
91      // color provider
92      private ColorProvider colorProvider = null;
93  
94      // displayed root
95      private TreeMapNode displayedRoot = null;
96  
97      // root of the tree
98      private TreeMapNode root = null;
99  
100     // divide strategy
101     private SplitStrategy strategy = null;
102 
103     // tooltip builder
104     private IToolTipBuilder toolTipBuilder;
105 
106     // zoom
107     private Zoom zoom;
108 
109     /**
110      * Constructor of JTreeMap. <BR>
111      * The chosen strategy is SplitSquarified. <BR>
112      * The chosen color provider is UniqueColorProvider.
113      * 
114      * @see SplitSquarified
115      * @see UniqueColorProvider
116      * @param root
117      *            the root of the tree to display
118      */
119     public JTreeMap(final TreeMapNode root) {
120         this(root, new SplitSquarified(), null, null, false);
121     }
122 
123     /**
124      * Constructor of JTreeMap. <BR>
125      * The chosen strategy is SplitSquarified. <BR>
126      * The chosen color provider is UniqueColorProvider.
127      * 
128      * @see SplitSquarified
129      * @see UniqueColorProvider
130      * @param root the root of the tree to display
131      * @param treeView The tree representation of the hierarchical data. 
132      */
133     public JTreeMap(final TreeMapNode root, final JTree treeView) {
134         this(root, new SplitSquarified(), null, null, false);
135         this.treeView = treeView;
136     }
137 
138     /**
139      * Constructor of JTreeMap. <BR>
140      * The chosen color provider is UniqueColorProvider.
141      * 
142      * @see UniqueColorProvider
143      * @param root
144      *            the root of the tree to display
145      * @param strategy
146      *            the split strategy
147      * @param treeView The tree representation of the hierarchical data. 
148      */
149     public JTreeMap(final TreeMapNode root, final SplitStrategy strategy, final JTree treeView,
150             final String weightPrefix, final String valuePrefix, final boolean showWeight) {
151         this(root, strategy, weightPrefix, valuePrefix, showWeight);
152         this.treeView = treeView;
153     }
154     /**
155      * Constructor of JTreeMap. <BR>
156      * The chosen color provider is UniqueColorProvider.
157      * 
158      * @see UniqueColorProvider
159      * @param root
160      *            the root of the tree to display
161      * @param strategy
162      *            the split strategy
163      */
164     public JTreeMap(final TreeMapNode root, final SplitStrategy strategy, 
165             final String weightPrefix, final String valuePrefix, final boolean showWeight) {
166         // ToolTips appears without delay and stay as long as possible
167         final ToolTipManager ttm = ToolTipManager.sharedInstance();
168         ttm.setInitialDelay(0);
169         ttm.setReshowDelay(0);
170         ttm.setDismissDelay(DISMISS_DELAY_MS);
171         ttm.setEnabled(true);
172         ttm.setLightWeightPopupEnabled(true);
173         setToolTipText("");
174 
175         // the default DefaultToolTipBuilder
176         toolTipBuilder = new DefaultToolTipBuilder(this, weightPrefix, valuePrefix, showWeight);
177 
178         zoom = new Zoom();
179 
180         setRoot(root);
181         setStrategy(strategy);
182         setColorProvider(new UniqueColorProvider());
183 
184         addMouseMotionListener(new HandleMouseMotion());
185         addMouseListener(new HandleMouseClick());
186     }
187 
188     /**
189      * calculate the postitions for the displayed root. <BR>
190      * The positions of the root must be calculated first.
191      */
192     public void calculatePositions() {
193         if (this.getStrategy() != null && displayedRoot != null) {
194             getStrategy().calculatePositions(this.displayedRoot);
195         }
196     }
197 
198     @Override
199     public JToolTip createToolTip() {
200         return toolTipBuilder.getToolTip();
201     }
202 
203     /**
204      * draw the item.
205      * 
206      * @param g
207      *            Graphics where you have to draw
208      * @param item
209      *            item to draw
210      */
211     protected void draw(final Graphics g, final TreeMapNode item) {
212         if (item.isLeaf() && item.getValue() != null) {
213             g.setColor(this.colorProvider.getColor(item.getValue()));
214             g.fillRect(item.getX(), item.getY(), item.getWidth(), item.getHeight());
215         } else {
216             for (final Enumeration e = item.children(); e.hasMoreElements();) {
217                 draw(g, (TreeMapNode) (e.nextElement()));
218             }
219         }
220     }
221 
222     /**
223      * write the label in the middle of the item. <BR>
224      * You have first to define the font of the Graphics. <BR>
225      * You may override this method to change the position or the color of the
226      * label.
227      * 
228      * @param g
229      *            Graphics where you have to draw
230      * @param item
231      *            TreeMapNode to draw
232      */
233     protected void drawLabel(final Graphics g, final TreeMapNode item) {
234         final FontMetrics fm = g.getFontMetrics(g.getFont());
235         // if the height of the item is high enough
236         if (fm.getHeight() < item.getHeight() - 2) {
237             String label = item.getLabel();
238 
239             final int y = (item.getHeight() + fm.getAscent() - fm.getDescent()) / 2;
240             final int stringWidth = fm.stringWidth(label);
241             // the width of the label depends on the font :
242             // if the width of the label is larger than the item
243             if (item.getWidth() - BORDER_FOR_FONT <= stringWidth) {
244                 // We have to truncate the label
245                 // number of chars who can be writen in the item
246                 final int nbChar = (label.length() * item.getWidth()) / stringWidth;
247                 if (nbChar > MAX_NUM_CHAR) {
248                     // and add "..." at the end
249                     label = label.substring(0, nbChar - MAX_NUM_CHAR) + "...";
250                 } else {
251                     // if it is not enough large, we display nothing
252                     label = "";
253                 }
254             }
255             final int x = (item.getWidth() - fm.stringWidth(label)) / 2;
256 
257             // background in black
258             g.setColor(Color.black);
259             g.drawString(label, item.getX() + x + 1, item.getY() + y + 1);
260             g.drawString(label, item.getX() + x - 1, item.getY() + y + 1);
261             g.drawString(label, item.getX() + x + 1, item.getY() + y - 1);
262             g.drawString(label, item.getX() + x - 1, item.getY() + y - 1);
263             g.drawString(label, item.getX() + x + 1, item.getY() + y);
264             g.drawString(label, item.getX() + x - 1, item.getY() + y);
265             g.drawString(label, item.getX() + x, item.getY() + y + 1);
266             g.drawString(label, item.getX() + x, item.getY() + y - 1);
267             // label in white
268             g.setColor(Color.white);
269             g.drawString(label, item.getX() + x, item.getY() + y);
270         }
271     }
272 
273     /**
274      * Draw all the labels to draw. <BR>
275      * You may override this method to draw the labels you want. <BR>
276      * For exemples, all the leaves, or all the first level children, or all of
277      * them...
278      * 
279      * @param g
280      *            Graphics where you have to draw
281      * @param item
282      *            TreeMapNode to draw
283      */
284     protected void drawLabels(final Graphics g, final TreeMapNode item) {
285         // add the labels (level -1)
286         g.setFont(this.getFont());
287         if (this.displayedRoot.isLeaf()) {
288             drawLabel(g, displayedRoot);
289         } else {
290             for (final Enumeration e = displayedRoot.children(); e.hasMoreElements();) {
291                 drawLabel(g, (TreeMapNode) (e.nextElement()));
292             }
293         }
294 
295         /* uncomment to add the labels of the lowered levels (up to depth > 2) */
296         // int depth = item.getLevel() - displayedRoot.getLevel();
297         // float newSize = Math.max(20, getFont().getSize2D());
298         // java.awt.Font labelFont =
299         // getFont().deriveFont(java.awt.Font.BOLD,
300         // newSize - 3 * depth);
301         // g.setFont(labelFont);
302         // if (depth > 2) {
303         // drawLabel(g, item);
304         // return;
305         // }
306         // if (item.isLeaf()) {
307         // drawLabel(g, item);
308         // } else {
309         // for (Enumeration e = item.children(); e.hasMoreElements();) {
310         // drawLabels(g, (TreeMapNode) (e.nextElement()));
311         // }
312         // }
313     }
314 
315     /**
316      * get the active leaf (the one under the mouse).
317      * 
318      * @return Returns the activeLeaf.
319      */
320     public TreeMapNode getActiveLeaf() {
321         return activeLeaf;
322     }
323 
324     /**
325      * get the ColorProvider.
326      * 
327      * @return the ColorProvider
328      */
329     public ColorProvider getColorProvider() {
330         return colorProvider;
331     }
332 
333     /**
334      * get the displayed root.
335      * <p>
336      * This may be not the root of the jTreeMap. After a zoom, the displayed
337      * root can be the root of an under-tree.
338      * </p>
339      * 
340      * @return the displayed root
341      */
342     public TreeMapNode getDisplayedRoot() {
343         return displayedRoot;
344     }
345 
346     /**
347      * get the root.
348      * 
349      * @return the root
350      */
351     public TreeMapNode getRoot() {
352         return root;
353     }
354 
355     /**
356      * get the SplitStrategy.
357      * 
358      * @return the SplitStrategy
359      */
360     public SplitStrategy getStrategy() {
361         return strategy;
362     }
363 
364     @Override
365     public Point getToolTipLocation(final MouseEvent event) {
366         int posX;
367         int posY;
368         final JToolTip toolTip = createToolTip();
369         final int xMax = displayedRoot.getX() + displayedRoot.getWidth();
370         final int yMin = displayedRoot.getY();
371         if (this.activeLeaf != null) {
372             if (this.activeLeaf.getWidth() >= toolTip.getWidth() + 2 * INSET
373                     && activeLeaf.getHeight() >= toolTip.getHeight() + 2 * INSET) {
374                 posX = activeLeaf.getX() + INSET;
375                 posY = activeLeaf.getY() + INSET;
376             } else {
377                 posX = activeLeaf.getX() + activeLeaf.getWidth() + INSET;
378                 posY = activeLeaf.getY() - toolTip.getHeight() - INSET;
379             }
380 
381             if (posY < yMin + INSET) {
382                 posY = yMin + INSET;
383             }
384             if ((posX + toolTip.getWidth() > xMax - INSET) && (this.activeLeaf.getX() >= toolTip.getWidth() + INSET)) {
385                 posX = activeLeaf.getX() - INSET - toolTip.getWidth();
386             }
387 
388             return new Point(posX, posY);
389         }
390         return null;
391     }
392 
393     /*
394      * (non-Javadoc)
395      * 
396      * @see javax.swing.JComponent#paintComponent(java.awt.Graphics)
397      */
398     @Override
399     protected void paintComponent(final Graphics g) {
400         super.paintComponent(g);
401         final int width = getSize().width;
402         final int height = getSize().height;
403         final Insets insets = getInsets();
404 
405         final int border = TreeMapNode.getBorder();
406         root.setDimension(this.root.getX(), root.getY(), width - border - insets.left - insets.right, height - border
407                 - insets.top - insets.bottom);
408 
409         if (!this.root.equals(this.displayedRoot)) {
410             displayedRoot.setDimension(this.displayedRoot.getX(), displayedRoot.getY(), width - border - insets.left
411                     - insets.right, height - border - insets.top - insets.bottom);
412         }
413 
414         calculatePositions();
415 
416         if (this.displayedRoot.children().hasMoreElements()) {
417             // the background
418             g.setColor(this.getBackground());
419             g.fillRect(this.displayedRoot.getX(), displayedRoot.getY(), displayedRoot.getWidth() + border, displayedRoot
420                     .getHeight()
421                     + border);
422             // the JTreeMapExample
423             draw(g, displayedRoot);
424             // reveal the active leaf
425             if (this.activeLeaf != null) {
426                 reveal(g, activeLeaf);
427             }
428             // the labels
429             drawLabels(g, displayedRoot);
430         }
431 
432     }
433 
434     /**
435      * reveal the item.
436      * 
437      * @param g
438      *            Graphics where you have to draw
439      * @param item
440      *            TreeMapNode to reveal
441      */
442     protected void reveal(final Graphics g, final TreeMapNode item) {
443         if (item.isLeaf()) {
444             g.setColor(TRANSPARENCY_COLOR);
445             g.fillRect(item.getX(), item.getY(), item.getWidth(), item.getHeight());
446         }
447     }
448 
449     /**
450      * set the active leaf.
451      * 
452      * @param newActiveLeaf
453      *            the new active leaf
454      */
455     public void setActiveLeaf(final TreeMapNode newActiveLeaf) {
456         if (newActiveLeaf == null || newActiveLeaf.isLeaf()) {
457             activeLeaf = newActiveLeaf;
458         }
459     }
460 
461     /*
462      * (non-Javadoc)
463      * 
464      * @see javax.swing.JComponent#setBorder(javax.swing.border.Border)
465      */
466     @Override
467     public void setBorder(final Border border) {
468         // Substract the previous border insets
469         Insets insets = getInsets();
470         displayedRoot.setDimension(this.displayedRoot.getX() - insets.left, displayedRoot.getY() - insets.top, displayedRoot
471                 .getWidth()
472                 + insets.left + insets.right, displayedRoot.getHeight() + insets.top + insets.bottom);
473 
474         super.setBorder(border);
475 
476         // Add the new border insets
477         insets = getInsets();
478         displayedRoot.setDimension(this.displayedRoot.getX() + insets.left, displayedRoot.getY() + insets.top, displayedRoot
479                 .getWidth()
480                 - insets.left - insets.right, displayedRoot.getHeight() - insets.top - insets.bottom);
481     }
482 
483     /**
484      * set the ColorProvider.
485      * 
486      * @param newColorProvider
487      *            the new ColorPorvider
488      */
489     public void setColorProvider(final ColorProvider newColorProvider) {
490         colorProvider = newColorProvider;
491     }
492 
493     /**
494      * set the displayed root.
495      * <p>
496      * This may be not the root of the jTreeMap. After a zoom, the displayed
497      * root can be the root of an under-tree.
498      * </p>
499      * 
500      * @param newDisplayedRoot
501      *            new DiplayedRoot
502      */
503     public void setDisplayedRoot(final TreeMapNode newDisplayedRoot) {
504         displayedRoot = newDisplayedRoot;
505     }
506 
507     /**
508      * set the new root.
509      * 
510      * @param newRoot
511      *            the new root to set
512      */
513     public void setRoot(final TreeMapNode newRoot) {
514         root = newRoot;
515         final Insets insets = getInsets();
516         root.setX(insets.left);
517         root.setY(insets.top);
518         setDisplayedRoot(this.root);
519 
520     }
521 
522     /**
523      * set the new strategy.
524      * 
525      * @param newStrat
526      *            the new strategy to set
527      */
528     public void setStrategy(final SplitStrategy newStrat) {
529         strategy = newStrat;
530     }
531 
532     /**
533      * Set the builder of the toolTip.<BR>
534      * 
535      * @param toolTipBuilder
536      *            The toolTipBuilder to set.
537      */
538     public void setToolTipBuilder(final IToolTipBuilder toolTipBuilder) {
539         this.toolTipBuilder = toolTipBuilder;
540     }
541 
542     /**
543      * When you zoom the jTreeMap, you have the choice to keep proportions or
544      * not.
545      * 
546      * @param keepProportion
547      *            true if you want to keep proportions, else false
548      */
549     public void setZoomKeepProportion(final boolean keepProportion) {
550         zoom.setKeepProportion(keepProportion);
551     }
552 
553     /**
554      * Undo the zoom to display the root.
555      */
556     public void unzoom() {
557         zoom.undo();
558     }
559 
560     /**
561      * Zoom the JTreeMap to the dest node.
562      * 
563      * @param dest
564      *            node we want to zoom
565      */
566     public void zoom(final TreeMapNode dest) {
567         // undo the last zoom
568         unzoom();
569 
570         zoom.execute(dest);
571     }
572 
573     /**
574      * Listener who define the active leaf and set the tooltip text.
575      * 
576      * @author Laurent Dutheil
577      */
578     protected class HandleMouseMotion extends MouseMotionAdapter {
579 
580         @Override
581         public void mouseMoved(final MouseEvent e) {
582             if (getDisplayedRoot().children().hasMoreElements()) {
583                 final TreeMapNode t = getDisplayedRoot().getActiveLeaf(e.getX(), e.getY());
584                 if (t != null && !t.equals(getActiveLeaf())) {
585                     setActiveLeaf(t);
586                     repaint();
587                 }
588             }
589         }
590     }
591 
592     /**
593      * Listener which listens for double click to navigate one level down.
594      * 
595      * @author Ekin Gulen
596      */
597     protected class HandleMouseClick extends MouseAdapter {
598 
599         @Override
600         public void mouseClicked(MouseEvent e) {
601             if (e.getButton() == MouseEvent.BUTTON1 && e.getClickCount() == 1) {
602                 final TreeMapNode t = getDisplayedRoot().getChild(e.getX(), e.getY());
603                 if (t != null && !t.isLeaf()) {
604                     if (treeView == null) {
605                         zoom(t);
606                     } else {
607                         //zoom(t);
608                         // dont know why below does not work so for now leave it commented out
609                         TreePath path = new TreePath(t.getPath());
610                          treeView.setSelectionPath(path);
611                          treeView.scrollPathToVisible(path);
612                     }
613 
614                 } else {
615                     if (treeView == null) {
616                         zoom((TreeMapNode) getDisplayedRoot().getParent());
617                     } else {
618                         //zoom((TreeMapNode) getDisplayedRoot().getParent());
619                         // dont know why below does not work so for now leave it commented out
620                         TreePath path =  new TreePath(((TreeMapNode)getDisplayedRoot().getParent()).getPath());
621                         treeView.setSelectionPath(path);
622                         treeView.scrollPathToVisible(path);
623                     }
624                 }
625                 repaint();
626             }
627         }
628     }
629     
630     
631 
632     /**
633      * Class who zoom and unzoom the JTreeMap.
634      * 
635      * @author Laurent Dutheil
636      */
637     private class Zoom implements Serializable {
638         /**
639          * 
640          */
641         private static final long serialVersionUID = 6708828099608367996L;
642 
643         private boolean enable;
644 
645         private boolean keepProportion = false;
646 
647         /**
648          * Constructor
649          */
650         public Zoom() {
651             enable = true;
652         }
653 
654         /**
655          * Execute the zoom.
656          * 
657          * @param dest
658          *            TreeMapNode where you want to zoom
659          */
660         public void execute(final TreeMapNode dest) {
661             if (this.enable) {
662                 JTreeMap.this.setActiveLeaf(null);
663 
664                 setNewDimension(dest);
665 
666                 JTreeMap.this.setDisplayedRoot(dest);
667 
668                 enable = false;
669             }
670         }
671 
672         /**
673          * @return Returns the keepProportion.
674          */
675         public boolean isKeepProportion() {
676             return keepProportion;
677         }
678 
679         /**
680          * @param keepProportion
681          *            The keepProportion to set.
682          */
683         public void setKeepProportion(final boolean keepProportion) {
684             this.keepProportion = keepProportion;
685         }
686 
687         /**
688          * set the new dimensions of the dest root
689          * 
690          * @param dest
691          *            the root to dimension
692          */
693         protected void setNewDimension(final TreeMapNode dest) {
694             dest.setX(JTreeMap.this.getRoot().getX());
695             dest.setY(JTreeMap.this.getRoot().getY());
696 
697             final int rootWidth = JTreeMap.this.getRoot().getWidth();
698             final int rootHeight = JTreeMap.this.getRoot().getHeight();
699 
700             if (isKeepProportion()) {
701                 final int destHeight = dest.getHeight();
702                 final int destWidth = dest.getWidth();
703                 final float divWidth = (float) destWidth / (float) rootWidth;
704                 final float divHeight = (float) destHeight / (float) rootHeight;
705 
706                 if (divWidth >= divHeight) {
707                     dest.setHeight(Math.round(destHeight / divWidth));
708                     dest.setWidth(rootWidth);
709                 } else {
710                     dest.setHeight(rootHeight);
711                     dest.setWidth(Math.round(destWidth / divHeight));
712                 }
713 
714             } else {
715                 dest.setHeight(rootHeight);
716                 dest.setWidth(rootWidth);
717             }
718         }
719 
720         /**
721          * undo the zoom.
722          */
723         public void undo() {
724             if (!this.enable) {
725                 JTreeMap.this.setDisplayedRoot(JTreeMap.this.getRoot());
726                 enable = true;
727             }
728         }
729     }
730 
731 
732 
733     public JTree getTreeView() {
734         return treeView;
735     }
736 
737     public void setTreeView(JTree treeView) {
738         this.treeView = treeView;
739     }
740 }
741 /*
742  *                 ObjectLab is supporing JTreeMap
743  * 
744  * Based in London, we are world leaders in the design and development 
745  * of bespoke applications for the securities financing markets.
746  * 
747  * <a href="http://www.objectlab.co.uk/open">Click here to learn more about us</a>
748  *           ___  _     _           _   _          _
749  *          / _ \| |__ (_) ___  ___| |_| |    __ _| |__
750  *         | | | | '_ \| |/ _ \/ __| __| |   / _` | '_ \
751  *         | |_| | |_) | |  __/ (__| |_| |__| (_| | |_) |
752  *          \___/|_.__// |\___|\___|\__|_____\__,_|_.__/
753  *                   |__/
754  *
755  *                     www.ObjectLab.co.uk
756  */