001/* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2008, by Object Refinery Limited and Contributors.
006 *
007 * Project Info:  http://www.jfree.org/jfreechart/index.html
008 *
009 * This library is free software; you can redistribute it and/or modify it
010 * under the terms of the GNU Lesser General Public License as published by
011 * the Free Software Foundation; either version 2.1 of the License, or
012 * (at your option) any later version.
013 *
014 * This library is distributed in the hope that it will be useful, but
015 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
016 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
017 * License for more details.
018 *
019 * You should have received a copy of the GNU Lesser General Public
020 * License along with this library; if not, write to the Free Software
021 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
022 * USA.
023 *
024 * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
025 * in the United States and other countries.]
026 *
027 * -----------------------
028 * GradientBarPainter.java
029 * -----------------------
030 * (C) Copyright 2008, by Object Refinery Limited.
031 *
032 * Original Author:  David Gilbert (for Object Refinery Limited);
033 * Contributor(s):   -;
034 *
035 * Changes:
036 * --------
037 * 19-Jun-2008 : Version 1 (DG);
038 * 15-Aug-2008 : Use outline paint and shadow paint (DG);
039 *
040 */
041
042package org.jfree.chart.renderer.category;
043
044import java.awt.Color;
045import java.awt.GradientPaint;
046import java.awt.Graphics2D;
047import java.awt.Paint;
048import java.awt.Stroke;
049import java.awt.geom.Rectangle2D;
050import java.awt.geom.RectangularShape;
051import java.io.Serializable;
052
053import org.jfree.chart.HashUtilities;
054import org.jfree.ui.RectangleEdge;
055
056/**
057 * An implementation of the {@link BarPainter} interface that uses several
058 * gradient fills to enrich the appearance of the bars.
059 *
060 * @since 1.0.11
061 */
062public class GradientBarPainter implements BarPainter, Serializable {
063
064    /** The division point between the first and second gradient regions. */
065    private double g1;
066
067    /** The division point between the second and third gradient regions. */
068    private double g2;
069
070    /** The division point between the third and fourth gradient regions. */
071    private double g3;
072
073    /**
074     * Creates a new instance.
075     */
076    public GradientBarPainter() {
077        this(0.10, 0.20, 0.80);
078    }
079
080    /**
081     * Creates a new instance.
082     *
083     * @param g1
084     * @param g2
085     * @param g3
086     */
087    public GradientBarPainter(double g1, double g2, double g3) {
088        this.g1 = g1;
089        this.g2 = g2;
090        this.g3 = g3;
091    }
092
093    /**
094     * Paints a single bar instance.
095     *
096     * @param g2  the graphics target.
097     * @param renderer  the renderer.
098     * @param row  the row index.
099     * @param column  the column index.
100     * @param bar  the bar
101     * @param base  indicates which side of the rectangle is the base of the
102     *              bar.
103     */
104    public void paintBar(Graphics2D g2, BarRenderer renderer, int row,
105            int column, RectangularShape bar, RectangleEdge base) {
106
107        Paint itemPaint = renderer.getItemPaint(row, column);
108
109        Color c0, c1;
110        if (itemPaint instanceof Color) {
111            c0 = (Color) itemPaint;
112            c1 = c0.brighter();
113        }
114        else if (itemPaint instanceof GradientPaint) {
115            GradientPaint gp = (GradientPaint) itemPaint;
116            c0 = gp.getColor1();
117            c1 = gp.getColor2();
118        }
119        else {
120            c0 = Color.blue;
121            c1 = Color.blue.brighter();
122        }
123
124        // as a special case, if the bar colour has alpha == 0, we draw
125        // nothing.
126        if (c0.getAlpha() == 0) {
127            return;
128        }
129
130        if (base == RectangleEdge.TOP || base == RectangleEdge.BOTTOM) {
131            Rectangle2D[] regions = splitVerticalBar(bar, this.g1, this.g2,
132                    this.g3);
133            GradientPaint gp = new GradientPaint((float) regions[0].getMinX(),
134                    0.0f, c0, (float) regions[0].getMaxX(), 0.0f, Color.white);
135            g2.setPaint(gp);
136            g2.fill(regions[0]);
137
138            gp = new GradientPaint((float) regions[1].getMinX(), 0.0f,
139                    Color.white, (float) regions[1].getMaxX(), 0.0f, c0);
140            g2.setPaint(gp);
141            g2.fill(regions[1]);
142
143            gp = new GradientPaint((float) regions[2].getMinX(), 0.0f, c0,
144                    (float) regions[2].getMaxX(), 0.0f, c1);
145            g2.setPaint(gp);
146            g2.fill(regions[2]);
147
148            gp = new GradientPaint((float) regions[3].getMinX(), 0.0f, c1,
149                     (float) regions[3].getMaxX(), 0.0f, c0);
150            g2.setPaint(gp);
151            g2.fill(regions[3]);
152        }
153        else if (base == RectangleEdge.LEFT || base == RectangleEdge.RIGHT) {
154            Rectangle2D[] regions = splitHorizontalBar(bar, this.g1, this.g2,
155                    this.g3);
156            GradientPaint gp = new GradientPaint(0.0f,
157                    (float) regions[0].getMinY(), c0, 0.0f,
158                    (float) regions[0].getMaxX(), Color.white);
159            g2.setPaint(gp);
160            g2.fill(regions[0]);
161
162            gp = new GradientPaint(0.0f, (float) regions[1].getMinY(),
163                    Color.white, 0.0f, (float) regions[1].getMaxY(), c0);
164            g2.setPaint(gp);
165            g2.fill(regions[1]);
166
167            gp = new GradientPaint(0.0f, (float) regions[2].getMinY(), c0,
168                    0.0f, (float) regions[2].getMaxY(), c1);
169            g2.setPaint(gp);
170            g2.fill(regions[2]);
171
172            gp = new GradientPaint(0.0f, (float) regions[3].getMinY(), c1,
173                     0.0f, (float) regions[3].getMaxY(), c0);
174            g2.setPaint(gp);
175            g2.fill(regions[3]);
176
177        }
178
179        // draw the outline...
180        if (renderer.isDrawBarOutline()
181            /*&& state.getBarWidth() > renderer.BAR_OUTLINE_WIDTH_THRESHOLD*/) {
182            Stroke stroke = renderer.getItemOutlineStroke(row, column);
183            Paint paint = renderer.getItemOutlinePaint(row, column);
184            if (stroke != null && paint != null) {
185                g2.setStroke(stroke);
186                g2.setPaint(paint);
187                g2.draw(bar);
188            }
189        }
190
191    }
192
193    /**
194     * Paints a single bar instance.
195     *
196     * @param g2  the graphics target.
197     * @param renderer  the renderer.
198     * @param row  the row index.
199     * @param column  the column index.
200     * @param bar  the bar
201     * @param base  indicates which side of the rectangle is the base of the
202     *              bar.
203     * @param pegShadow  peg the shadow to the base of the bar?
204     */
205    public void paintBarShadow(Graphics2D g2, BarRenderer renderer, int row,
206            int column, RectangularShape bar, RectangleEdge base,
207            boolean pegShadow) {
208
209        // handle a special case - if the bar colour has alpha == 0, it is
210        // invisible so we shouldn't draw any shadow
211        Paint itemPaint = renderer.getItemPaint(row, column);
212        if (itemPaint instanceof Color) {
213            Color c = (Color) itemPaint;
214            if (c.getAlpha() == 0) {
215                return;
216            }
217        }
218
219        RectangularShape shadow = createShadow(bar, renderer.getShadowXOffset(),
220                renderer.getShadowYOffset(), base, pegShadow);
221        g2.setPaint(renderer.getShadowPaint());
222        g2.fill(shadow);
223
224    }
225
226    /**
227     * Creates a shadow for the bar.
228     *
229     * @param bar  the bar shape.
230     * @param xOffset  the x-offset for the shadow.
231     * @param yOffset  the y-offset for the shadow.
232     * @param base  the edge that is the base of the bar.
233     * @param pegShadow  peg the shadow to the base?
234     *
235     * @return A rectangle for the shadow.
236     */
237    private Rectangle2D createShadow(RectangularShape bar, double xOffset,
238            double yOffset, RectangleEdge base, boolean pegShadow) {
239        double x0 = bar.getMinX();
240        double x1 = bar.getMaxX();
241        double y0 = bar.getMinY();
242        double y1 = bar.getMaxY();
243        if (base == RectangleEdge.TOP) {
244            x0 += xOffset;
245            x1 += xOffset;
246            if (!pegShadow) {
247                y0 += yOffset;
248            }
249            y1 += yOffset;
250        }
251        else if (base == RectangleEdge.BOTTOM) {
252            x0 += xOffset;
253            x1 += xOffset;
254            y0 += yOffset;
255            if (!pegShadow) {
256                y1 += yOffset;
257            }
258        }
259        else if (base == RectangleEdge.LEFT) {
260            if (!pegShadow) {
261                x0 += xOffset;
262            }
263            x1 += xOffset;
264            y0 += yOffset;
265            y1 += yOffset;
266        }
267        else if (base == RectangleEdge.RIGHT) {
268            x0 += xOffset;
269            if (!pegShadow) {
270                x1 += xOffset;
271            }
272            y0 += yOffset;
273            y1 += yOffset;
274        }
275        return new Rectangle2D.Double(x0, y0, (x1 - x0), (y1 - y0));
276    }
277
278    /**
279     * Splits a bar into subregions (elsewhere, these subregions will have
280     * different gradients applied to them).
281     *
282     * @param bar  the bar shape.
283     * @param a  the first division.
284     * @param b  the second division.
285     * @param c  the third division.
286     *
287     * @return An array containing four subregions.
288     */
289    private Rectangle2D[] splitVerticalBar(RectangularShape bar, double a,
290            double b, double c) {
291        Rectangle2D[] result = new Rectangle2D[4];
292        double x0 = bar.getMinX();
293        double x1 = Math.rint(x0 + (bar.getWidth() * a));
294        double x2 = Math.rint(x0 + (bar.getWidth() * b));
295        double x3 = Math.rint(x0 + (bar.getWidth() * c));
296        result[0] = new Rectangle2D.Double(bar.getMinX(), bar.getMinY(),
297                x1 - x0, bar.getHeight());
298        result[1] = new Rectangle2D.Double(x1, bar.getMinY(), x2 - x1,
299                bar.getHeight());
300        result[2] = new Rectangle2D.Double(x2, bar.getMinY(), x3 - x2,
301                bar.getHeight());
302        result[3] = new Rectangle2D.Double(x3, bar.getMinY(),
303                bar.getMaxX() - x3, bar.getHeight());
304        return result;
305    }
306
307    /**
308     * Splits a bar into subregions (elsewhere, these subregions will have
309     * different gradients applied to them).
310     *
311     * @param bar  the bar shape.
312     * @param a  the first division.
313     * @param b  the second division.
314     * @param c  the third division.
315     *
316     * @return An array containing four subregions.
317     */
318    private Rectangle2D[] splitHorizontalBar(RectangularShape bar, double a,
319            double b, double c) {
320        Rectangle2D[] result = new Rectangle2D[4];
321        double y0 = bar.getMinY();
322        double y1 = Math.rint(y0 + (bar.getHeight() * a));
323        double y2 = Math.rint(y0 + (bar.getHeight() * b));
324        double y3 = Math.rint(y0 + (bar.getHeight() * c));
325        result[0] = new Rectangle2D.Double(bar.getMinX(), bar.getMinY(),
326                bar.getWidth(), y1 - y0);
327        result[1] = new Rectangle2D.Double(bar.getMinX(), y1, bar.getWidth(),
328                y2 - y1);
329        result[2] = new Rectangle2D.Double(bar.getMinX(), y2, bar.getWidth(),
330                y3 - y2);
331        result[3] = new Rectangle2D.Double(bar.getMinX(), y3, bar.getWidth(),
332                bar.getMaxY() - y3);
333        return result;
334    }
335
336    /**
337     * Tests this instance for equality with an arbitrary object.
338     *
339     * @param obj  the obj (<code>null</code> permitted).
340     *
341     * @return A boolean.
342     */
343    public boolean equals(Object obj) {
344        if (obj == this) {
345            return true;
346        }
347        if (!(obj instanceof GradientBarPainter)) {
348            return false;
349        }
350        GradientBarPainter that = (GradientBarPainter) obj;
351        if (this.g1 != that.g1) {
352            return false;
353        }
354        if (this.g2 != that.g2) {
355            return false;
356        }
357        if (this.g3 != that.g3) {
358            return false;
359        }
360        return true;
361    }
362
363    /**
364     * Returns a hash code for this instance.
365     *
366     * @return A hash code.
367     */
368    public int hashCode() {
369        int hash = 37;
370        hash = HashUtilities.hashCode(hash, this.g1);
371        hash = HashUtilities.hashCode(hash, this.g2);
372        hash = HashUtilities.hashCode(hash, this.g3);
373        return hash;
374    }
375
376}