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