001/* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2009, 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 * GradientXYBarPainter.java
029 * -------------------------
030 * (C) Copyright 2008, 2009, 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 * 22-Feb-2009 : Fixed bug drawing outlines (DG);
039 *
040 */
041
042package org.jfree.chart.renderer.xy;
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 XYBarPainter} interface that uses several
058 * gradient fills to enrich the appearance of the bars.
059 *
060 * @since 1.0.11
061 */
062public class GradientXYBarPainter implements XYBarPainter, 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 GradientXYBarPainter() {
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 GradientXYBarPainter(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, XYBarRenderer 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            Stroke stroke = renderer.getItemOutlineStroke(row, column);
182            Paint paint = renderer.getItemOutlinePaint(row, column);
183            if (stroke != null && paint != null) {
184                g2.setStroke(stroke);
185                g2.setPaint(paint);
186                g2.draw(bar);
187            }
188        }
189
190    }
191
192    /**
193     * Paints a single bar instance.
194     *
195     * @param g2  the graphics target.
196     * @param renderer  the renderer.
197     * @param row  the row index.
198     * @param column  the column index.
199     * @param bar  the bar
200     * @param base  indicates which side of the rectangle is the base of the
201     *              bar.
202     * @param pegShadow  peg the shadow to the base of the bar?
203     */
204    public void paintBarShadow(Graphics2D g2, XYBarRenderer renderer, int row,
205            int column, RectangularShape bar, RectangleEdge base,
206            boolean pegShadow) {
207
208        // handle a special case - if the bar colour has alpha == 0, it is
209        // invisible so we shouldn't draw any shadow
210        Paint itemPaint = renderer.getItemPaint(row, column);
211        if (itemPaint instanceof Color) {
212            Color c = (Color) itemPaint;
213            if (c.getAlpha() == 0) {
214                return;
215            }
216        }
217
218        RectangularShape shadow = createShadow(bar, renderer.getShadowXOffset(),
219                renderer.getShadowYOffset(), base, pegShadow);
220        g2.setPaint(Color.gray);
221        g2.fill(shadow);
222
223    }
224
225    /**
226     * Creates a shadow for the bar.
227     *
228     * @param bar  the bar shape.
229     * @param xOffset  the x-offset for the shadow.
230     * @param yOffset  the y-offset for the shadow.
231     * @param base  the edge that is the base of the bar.
232     * @param pegShadow  peg the shadow to the base?
233     *
234     * @return A rectangle for the shadow.
235     */
236    private Rectangle2D createShadow(RectangularShape bar, double xOffset,
237            double yOffset, RectangleEdge base, boolean pegShadow) {
238        double x0 = bar.getMinX();
239        double x1 = bar.getMaxX();
240        double y0 = bar.getMinY();
241        double y1 = bar.getMaxY();
242        if (base == RectangleEdge.TOP) {
243            x0 += xOffset;
244            x1 += xOffset;
245            if (!pegShadow) {
246                y0 += yOffset;
247            }
248            y1 += yOffset;
249        }
250        else if (base == RectangleEdge.BOTTOM) {
251            x0 += xOffset;
252            x1 += xOffset;
253            y0 += yOffset;
254            if (!pegShadow) {
255                y1 += yOffset;
256            }
257        }
258        else if (base == RectangleEdge.LEFT) {
259            if (!pegShadow) {
260                x0 += xOffset;
261            }
262            x1 += xOffset;
263            y0 += yOffset;
264            y1 += yOffset;
265        }
266        else if (base == RectangleEdge.RIGHT) {
267            x0 += xOffset;
268            if (!pegShadow) {
269                x1 += xOffset;
270            }
271            y0 += yOffset;
272            y1 += yOffset;
273        }
274        return new Rectangle2D.Double(x0, y0, (x1 - x0), (y1 - y0));
275    }
276
277    /**
278     * Splits a bar into subregions (elsewhere, these subregions will have
279     * different gradients applied to them).
280     *
281     * @param bar  the bar shape.
282     * @param a  the first division.
283     * @param b  the second division.
284     * @param c  the third division.
285     *
286     * @return An array containing four subregions.
287     */
288    private Rectangle2D[] splitVerticalBar(RectangularShape bar, double a,
289            double b, double c) {
290        Rectangle2D[] result = new Rectangle2D[4];
291        double x0 = bar.getMinX();
292        double x1 = Math.rint(x0 + (bar.getWidth() * a));
293        double x2 = Math.rint(x0 + (bar.getWidth() * b));
294        double x3 = Math.rint(x0 + (bar.getWidth() * c));
295        result[0] = new Rectangle2D.Double(bar.getMinX(), bar.getMinY(),
296                x1 - x0, bar.getHeight());
297        result[1] = new Rectangle2D.Double(x1, bar.getMinY(), x2 - x1,
298                bar.getHeight());
299        result[2] = new Rectangle2D.Double(x2, bar.getMinY(), x3 - x2,
300                bar.getHeight());
301        result[3] = new Rectangle2D.Double(x3, bar.getMinY(),
302                bar.getMaxX() - x3, bar.getHeight());
303        return result;
304    }
305
306    /**
307     * Splits a bar into subregions (elsewhere, these subregions will have
308     * different gradients applied to them).
309     *
310     * @param bar  the bar shape.
311     * @param a  the first division.
312     * @param b  the second division.
313     * @param c  the third division.
314     *
315     * @return An array containing four subregions.
316     */
317    private Rectangle2D[] splitHorizontalBar(RectangularShape bar, double a,
318            double b, double c) {
319        Rectangle2D[] result = new Rectangle2D[4];
320        double y0 = bar.getMinY();
321        double y1 = Math.rint(y0 + (bar.getHeight() * a));
322        double y2 = Math.rint(y0 + (bar.getHeight() * b));
323        double y3 = Math.rint(y0 + (bar.getHeight() * c));
324        result[0] = new Rectangle2D.Double(bar.getMinX(), bar.getMinY(),
325                bar.getWidth(), y1 - y0);
326        result[1] = new Rectangle2D.Double(bar.getMinX(), y1, bar.getWidth(),
327                y2 - y1);
328        result[2] = new Rectangle2D.Double(bar.getMinX(), y2, bar.getWidth(),
329                y3 - y2);
330        result[3] = new Rectangle2D.Double(bar.getMinX(), y3, bar.getWidth(),
331                bar.getMaxY() - y3);
332        return result;
333    }
334
335    /**
336     * Tests this instance for equality with an arbitrary object.
337     *
338     * @param obj  the obj (<code>null</code> permitted).
339     *
340     * @return A boolean.
341     */
342    public boolean equals(Object obj) {
343        if (obj == this) {
344            return true;
345        }
346        if (!(obj instanceof GradientXYBarPainter)) {
347            return false;
348        }
349        GradientXYBarPainter that = (GradientXYBarPainter) obj;
350        if (this.g1 != that.g1) {
351            return false;
352        }
353        if (this.g2 != that.g2) {
354            return false;
355        }
356        if (this.g3 != that.g3) {
357            return false;
358        }
359        return true;
360    }
361
362    /**
363     * Returns a hash code for this instance.
364     *
365     * @return A hash code.
366     */
367    public int hashCode() {
368        int hash = 37;
369        hash = HashUtilities.hashCode(hash, this.g1);
370        hash = HashUtilities.hashCode(hash, this.g2);
371        hash = HashUtilities.hashCode(hash, this.g3);
372        return hash;
373    }
374
375}