001    /*--------------------------------------------------------------------------+
002    $Id: NodeTextRenderer.java 26931 2010-03-17 14:53:13Z besenreu $
003    |                                                                          |
004    | Copyright 2005-2010 Technische Universitaet Muenchen                     |
005    |                                                                          |
006    | Licensed under the Apache License, Version 2.0 (the "License");          |
007    | you may not use this file except in compliance with the License.         |
008    | You may obtain a copy of the License at                                  |
009    |                                                                          |
010    |    http://www.apache.org/licenses/LICENSE-2.0                            |
011    |                                                                          |
012    | Unless required by applicable law or agreed to in writing, software      |
013    | distributed under the License is distributed on an "AS IS" BASIS,        |
014    | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
015    | See the License for the specific language governing permissions and      |
016    | limitations under the License.                                           |
017    +--------------------------------------------------------------------------*/
018    package edu.tum.cs.commons.treemap;
019    
020    import java.awt.Color;
021    import java.awt.Graphics2D;
022    import java.awt.geom.Rectangle2D;
023    import java.util.regex.Pattern;
024    
025    
026    /**
027     * A simple renderer that draws tree map node texts into the tree map.
028     * 
029     * @author juergens
030     * @author $Author: besenreu $
031     * @version $Rev: 26931 $
032     * @levd.rating GREEN Hash: 592D3A4F3823417DAADE575320937157
033     */
034    public class NodeTextRenderer implements ITreeMapRenderer {
035    
036            /** Padding between a node's text label and its rectangle border */
037            private static final int TEXT_PADDING = 5;
038    
039            /** Color in which text is drawn */
040            private final Color textColor;
041    
042            /** Separation pattern used to isolate local name */
043            private final Pattern separationPattern;
044    
045            /** Constructor */
046            public NodeTextRenderer(Color textColor, Pattern separationPattern) {
047                    this.textColor = textColor;
048                    this.separationPattern = separationPattern;
049            }
050    
051            /** {@inheritDoc} */
052            public <T> void renderTreeMap(ITreeMapNode<T> node, Graphics2D graphics) {
053                    if (node.getChildren().isEmpty()) {
054                            Rectangle2D nodeArea = node.getLayoutRectangle();
055                            if (enoughSpace(nodeArea)) {
056                                    drawText(node.getText(), nodeArea, graphics);
057                            }
058                    } else {
059                            for (ITreeMapNode<T> child : node.getChildren()) {
060                                    renderTreeMap(child, graphics);
061                            }
062                    }
063            }
064    
065            /** Determines if node area is large enough */
066            private boolean enoughSpace(Rectangle2D nodeArea) {
067                    return nodeArea.getWidth() > 3 * TEXT_PADDING
068                                    && nodeArea.getHeight() > 3 * TEXT_PADDING;
069            }
070    
071            /** Draws the node text */
072            private <T> void drawText(String text, Rectangle2D availableSpace,
073                            Graphics2D graphics) {
074    
075                    // cut text to size
076                    String fittedText = clipTextToWidth(text, availableSpace.getWidth(),
077                                    graphics);
078                    if (fittedText.length() < text.length()) {
079                            fittedText += "...";
080                    }
081    
082                    // compute text position
083                    int x = (int) availableSpace.getCenterX()
084                                    - (actualWidth(fittedText, graphics) / 2);
085                    int y = (int) availableSpace.getCenterY()
086                                    + (actualHeight(fittedText, graphics) / 2);
087    
088                    // draw label
089                    graphics.setColor(textColor);
090                    graphics.drawString(fittedText, x, y);
091            }
092    
093            /** Clips a string to a certain width */
094            private String clipTextToWidth(String text, double width,
095                            Graphics2D graphics) {
096                    double availableWidth = width - 2 * TEXT_PADDING;
097    
098                    // try to prune to last name part
099                    if (separationPattern != null
100                                    && actualWidth(text, graphics) > availableWidth) {
101                            String[] parts = separationPattern.split(text);
102                            if (parts.length > 0) {
103                                    text = parts[parts.length - 1];
104                            }
105                    }
106    
107                    // reserve space for trailing "..."
108                    if (actualWidth(text, graphics) > availableWidth) {
109                            availableWidth -= actualWidth("...", graphics);
110                    }
111    
112                    // clip until small enough
113                    while (text.length() > 0
114                                    && actualWidth(text, graphics) > availableWidth) {
115                            text = text.substring(0, text.length() - 1);
116                    }
117                    return text;
118            }
119    
120            /** Determines the width a string requires in the current font */
121            private int actualWidth(String label, Graphics2D graphics) {
122                    return (int) actualBounds(label, graphics).getWidth();
123            }
124    
125            /** Determines the height a string requires in the current font */
126            private int actualHeight(String label, Graphics2D graphics) {
127                    return (int) actualBounds(label, graphics).getHeight();
128            }
129    
130            /** Computes the bounds of a string in the current font */
131            private Rectangle2D actualBounds(String label, Graphics2D graphics) {
132                    return graphics.getFont().getStringBounds(label,
133                                    graphics.getFontRenderContext());
134            }
135    
136    }