001    /*--------------------------------------------------------------------------+
002    $Id: CushionTreeMapRenderer.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    
024    
025    /**
026     * A tree map renderer using "cushions" as described in J. van Wijk, H. van de
027     * Wetering: "Cushion Treemaps: Visualization of Hierarchical Information".
028     *
029     * @author Benjamin Hummel
030     * @author $Author: besenreu $
031     * @version $Rev: 26931 $
032     * @levd.rating GREEN Hash: 8731C2C4BC1A38B13F743E451FBF6A43
033     */
034    public class CushionTreeMapRenderer implements ITreeMapRenderer {
035    
036            /** The height parameter for the cushions. */
037            private final double h;
038    
039            /** The height scale factor. */
040            private final double f;
041    
042            /**
043             * Constructor.
044             * 
045             * @param h
046             *            the height parameter giving the heigt of the cushions relative
047             *            to their size. 0.5 seems to be a reasonable value.
048             * @param f
049             *            the scale factor used to reduce the heights of nested
050             *            cushions. The value should be between 0 and 1, where smaller
051             *            values will reduce the cushion effect.
052             */
053            public CushionTreeMapRenderer(double h, double f) {
054                    this.h = h;
055                    this.f = f;
056            }
057    
058            /** {@inheritDoc} */
059            public <T> void renderTreeMap(ITreeMapNode<T> node, Graphics2D graphics) {
060                    // use loop here, to avoid adding cushion to top level node
061                    for (ITreeMapNode<T> child : node.getChildren()) {
062                            render(child, graphics, h, new double[4]);
063                    }
064            }
065    
066            /**
067             * Renders the given node.
068             * 
069             * @param node
070             *            the node to render.
071             * @param g
072             *            the graphics to render into.
073             * @param height
074             *            the current height (already scaled for this level).
075             * @param coefs
076             *            the coefficients of the local parabola. The incides 0 and 1
077             *            give the coefficients for x^2 and x, while 2 and 3 are for y^2
078             *            and y. The constant part is not needed.
079             */
080            private <T> void render(ITreeMapNode<T> node, Graphics2D g, double height,
081                            double[] coefs) {
082                    Rectangle2D rect = node.getLayoutRectangle();
083                    double[] myCoefs = addLocalParabola(height, coefs, rect);
084                    if (node.getChildren().isEmpty()) {
085                            renderCushion(rect, myCoefs, g, node.getColor(), node
086                                            .getPatternColor(), node.getDrawingPattern());
087                    } else if (node.getChildren().size() == 1) {
088                            // do not scale height or add cushion
089                            render(node.getChildren().get(0), g, height, coefs);
090                    } else {
091                            for (ITreeMapNode<T> child : node.getChildren()) {
092                                    render(child, g, height * f, myCoefs);
093                            }
094                    }
095            }
096    
097            /** Adds the local parabola to the given coefs and returns the result. */
098            private double[] addLocalParabola(double height, double[] coefs,
099                            Rectangle2D rect) {
100                    double[] myCoefs = new double[4];
101                    double x1 = rect.getMinX();
102                    double x2 = rect.getMaxX();
103                    double y1 = rect.getMinY();
104                    double y2 = rect.getMaxY();
105                    myCoefs[0] = coefs[0] - 4 * height / (x2 - x1);
106                    myCoefs[1] = coefs[1] + 4 * height * (x1 + x2) / (x2 - x1);
107                    myCoefs[2] = coefs[2] - 4 * height / (y2 - y1);
108                    myCoefs[3] = coefs[3] + 4 * height * (y1 + y2) / (y2 - y1);
109                    return myCoefs;
110            }
111    
112            /** Renders the given cushion. */
113            private void renderCushion(Rectangle2D rect, double[] coefs, Graphics2D g,
114                            Color baseColor, Color patternColor, IDrawingPattern drawingPattern) {
115    
116                    // light normal taken from the cited paper.
117                    final double lx = 0.09759;
118                    final double ly = 0.19518;
119                    final double lz = 0.9759;
120    
121                    int minX = (int) (rect.getMinX() + .5);
122                    int minY = (int) (rect.getMinY() + .5);
123                    int maxX = (int) (rect.getMaxX() + .5);
124                    int maxY = (int) (rect.getMaxY() + .5);
125    
126                    for (int x = minX; x < maxX; ++x) {
127                            for (int y = minY; y < maxY; ++y) {
128                                    double nx = -(2 * coefs[0] * (x + .5) + coefs[1]);
129                                    double ny = -(2 * coefs[2] * (y + .5) + coefs[3]);
130                                    double norm = Math.sqrt(nx * nx + ny * ny + 1);
131                                    double cosa = (nx * lx + ny * ly + lz) / norm;
132    
133                                    Color color = baseColor;
134                                    if (drawingPattern != null && drawingPattern.isForeground(x, y)) {
135                                            color = patternColor;
136                                    }
137    
138                                    g.setColor(shadeColor(color, .2 + .8 * Math.max(0, cosa)));
139                                    g.drawLine(x, y, x, y);
140                            }
141                    }
142            }
143    
144            /**
145             * Calculate the shaded color.
146             * 
147             * @param color
148             *            the base color.
149             * @param luminance
150             *            a parameter between 0 and 1, where 0 corresponds to black and
151             *            1 to white.
152             */
153            private Color shadeColor(Color color, double luminance) {
154                    int base = 0;
155                    luminance *= 2;
156                    if (luminance > 1) {
157                            luminance = 2 - luminance;
158                            base = (int) (255 * (1 - luminance));
159                    }
160    
161                    return new Color((int) (color.getRed() * luminance) + base,
162                                    (int) (color.getGreen() * luminance) + base, (int) (color
163                                                    .getBlue() * luminance)
164                                                    + base);
165            }
166    }