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