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