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