Open Source Repository

Home /jfreechart/jfreechart-1.0.14 | Repository Home



org/jfree/chart/renderer/category/WaterfallBarRenderer.java
/* ===========================================================
 * JFreeChart : a free chart library for the Java(tm) platform
 * ===========================================================
 *
 * (C) Copyright 2000-2011, by Object Refinery Limited and Contributors.
 *
 * Project Info:  http://www.jfree.org/jfreechart/index.html
 *
 * This library is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
 * (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
 * License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
 * USA.
 *
 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. 
 * Other names may be trademarks of their respective owners.]
 *
 * -------------------------
 * WaterfallBarRenderer.java
 * -------------------------
 * (C) Copyright 2003-2009, by Object Refinery Limited and Contributors.
 *
 * Original Author:  Darshan Shah;
 * Contributor(s):   David Gilbert (for Object Refinery Limited);
 *
 * Changes
 * -------
 * 20-Oct-2003 : Version 1, contributed by Darshan Shah (DG);
 * 06-Nov-2003 : Changed order of parameters in constructor, and added support
 *               for GradientPaint (DG);
 * 10-Feb-2004 : Updated drawItem() method to make cut-and-paste overriding
 *               easier.  Also fixed a bug that meant the minimum bar length
 *               was being ignored (DG);
 * 04-Oct-2004 : Reworked equals() method and renamed PaintUtils
 *               --> PaintUtilities (DG);
 * 05-Nov-2004 : Modified drawItem() signature (DG);
 * 07-Jan-2005 : Renamed getRangeExtent() --> findRangeBounds (DG);
 * 23-Feb-2005 : Added argument checking (DG);
 * 20-Apr-2005 : Renamed CategoryLabelGenerator
 *               --> CategoryItemLabelGenerator (DG);
 * 09-Jun-2005 : Use addItemEntity() from superclass (DG);
 * 27-Mar-2008 : Fixed error in findRangeBounds() method (DG);
 * 26-Sep-2008 : Fixed bug with bar alignment when maximumBarWidth is
 *               applied (DG);
 * 04-Feb-2009 : Updated findRangeBounds to handle null dataset consistently
 *               with other renderers (DG);
 * 19-May-2009 : Fixed FindBugs warnings, patch by Michal Wozniak (DG);
 *
 */

package org.jfree.chart.renderer.category;

import java.awt.Color;
import java.awt.GradientPaint;
import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.Stroke;
import java.awt.geom.Rectangle2D;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

import org.jfree.chart.axis.CategoryAxis;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.entity.EntityCollection;
import org.jfree.chart.event.RendererChangeEvent;
import org.jfree.chart.labels.CategoryItemLabelGenerator;
import org.jfree.chart.plot.CategoryPlot;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.renderer.AbstractRenderer;
import org.jfree.data.Range;
import org.jfree.data.category.CategoryDataset;
import org.jfree.io.SerialUtilities;
import org.jfree.ui.GradientPaintTransformType;
import org.jfree.ui.RectangleEdge;
import org.jfree.ui.StandardGradientPaintTransformer;
import org.jfree.util.PaintUtilities;

/**
 * A renderer that handles the drawing of waterfall bar charts, for use with
 * the {@link CategoryPlot} class.  Some quirks to note:
 <ul>
 <li>the value in the last category of the dataset should be (redundantly)
 *   specified as the sum of the items in the preceding categories - otherwise
 *   the final bar in the plot will be incorrectly plotted;</li>
 <li>the bar colors are defined using special methods in this class - the
 *   inherited methods (for example,
 *   {@link AbstractRenderer#setSeriesPaint(int, Paint)}) are ignored;</li>
 </ul>
 * The example shown here is generated by the
 <code>WaterfallChartDemo1.java</code> program included in the JFreeChart
 * Demo Collection:
 <br><br>
 <img src="../../../../../images/WaterfallBarRendererSample.png"
 * alt="WaterfallBarRendererSample.png" />
 */
public class WaterfallBarRenderer extends BarRenderer {

    /** For serialization. */
    private static final long serialVersionUID = -2482910643727230911L;

    /** The paint used to draw the first bar. */
    private transient Paint firstBarPaint;

    /** The paint used to draw the last bar. */
    private transient Paint lastBarPaint;

    /** The paint used to draw bars having positive values. */
    private transient Paint positiveBarPaint;

    /** The paint used to draw bars having negative values. */
    private transient Paint negativeBarPaint;

    /**
     * Constructs a new renderer with default values for the bar colors.
     */
    public WaterfallBarRenderer() {
        this(new GradientPaint(0.0f0.0fnew Color(0x220x220xFF),
                0.0f0.0fnew Color(0x660x660xFF)),
                new GradientPaint(0.0f0.0fnew Color(0x220xFF0x22),
                0.0f0.0fnew Color(0x660xFF0x66)),
                new GradientPaint(0.0f0.0fnew Color(0xFF0x220x22),
                0.0f0.0fnew Color(0xFF0x660x66)),
                new GradientPaint(0.0f0.0fnew Color(0xFF0xFF0x22),
                0.0f0.0fnew Color(0xFF0xFF0x66)));
    }

    /**
     * Constructs a new waterfall renderer.
     *
     @param firstBarPaint  the color of the first bar (<code>null</code> not
     *                       permitted).
     @param positiveBarPaint  the color for bars with positive values
     *                          (<code>null</code> not permitted).
     @param negativeBarPaint  the color for bars with negative values
     *                          (<code>null</code> not permitted).
     @param lastBarPaint  the color of the last bar (<code>null</code> not
     *                      permitted).
     */
    public WaterfallBarRenderer(Paint firstBarPaint,
                                Paint positiveBarPaint,
                                Paint negativeBarPaint,
                                Paint lastBarPaint) {
        super();
        if (firstBarPaint == null) {
            throw new IllegalArgumentException("Null 'firstBarPaint' argument");
        }
        if (positiveBarPaint == null) {
            throw new IllegalArgumentException(
                    "Null 'positiveBarPaint' argument");
        }
        if (negativeBarPaint == null) {
            throw new IllegalArgumentException(
                    "Null 'negativeBarPaint' argument");
        }
        if (lastBarPaint == null) {
            throw new IllegalArgumentException("Null 'lastBarPaint' argument");
        }
        this.firstBarPaint = firstBarPaint;
        this.lastBarPaint = lastBarPaint;
        this.positiveBarPaint = positiveBarPaint;
        this.negativeBarPaint = negativeBarPaint;
        setGradientPaintTransformer(new StandardGradientPaintTransformer(
                GradientPaintTransformType.CENTER_VERTICAL));
        setMinimumBarLength(1.0);
    }

    /**
     * Returns the paint used to draw the first bar.
     *
     @return The paint (never <code>null</code>).
     */
    public Paint getFirstBarPaint() {
        return this.firstBarPaint;
    }

    /**
     * Sets the paint that will be used to draw the first bar and sends a
     {@link RendererChangeEvent} to all registered listeners.
     *
     @param paint  the paint (<code>null</code> not permitted).
     */
    public void setFirstBarPaint(Paint paint) {
        if (paint == null) {
            throw new IllegalArgumentException("Null 'paint' argument");
        }
        this.firstBarPaint = paint;
        fireChangeEvent();
    }

    /**
     * Returns the paint used to draw the last bar.
     *
     @return The paint (never <code>null</code>).
     */
    public Paint getLastBarPaint() {
        return this.lastBarPaint;
    }

    /**
     * Sets the paint that will be used to draw the last bar and sends a
     {@link RendererChangeEvent} to all registered listeners.
     *
     @param paint  the paint (<code>null</code> not permitted).
     */
    public void setLastBarPaint(Paint paint) {
        if (paint == null) {
            throw new IllegalArgumentException("Null 'paint' argument");
        }
        this.lastBarPaint = paint;
        fireChangeEvent();
    }

    /**
     * Returns the paint used to draw bars with positive values.
     *
     @return The paint (never <code>null</code>).
     */
    public Paint getPositiveBarPaint() {
        return this.positiveBarPaint;
    }

    /**
     * Sets the paint that will be used to draw bars having positive values.
     *
     @param paint  the paint (<code>null</code> not permitted).
     */
    public void setPositiveBarPaint(Paint paint) {
        if (paint == null) {
            throw new IllegalArgumentException("Null 'paint' argument");
        }
        this.positiveBarPaint = paint;
        fireChangeEvent();
    }

    /**
     * Returns the paint used to draw bars with negative values.
     *
     @return The paint (never <code>null</code>).
     */
    public Paint getNegativeBarPaint() {
        return this.negativeBarPaint;
    }

    /**
     * Sets the paint that will be used to draw bars having negative values,
     * and sends a {@link RendererChangeEvent} to all registered listeners.
     *
     @param paint  the paint (<code>null</code> not permitted).
     */
    public void setNegativeBarPaint(Paint paint) {
        if (paint == null) {
            throw new IllegalArgumentException("Null 'paint' argument");
        }
        this.negativeBarPaint = paint;
        fireChangeEvent();
    }

    /**
     * Returns the range of values the renderer requires to display all the
     * items from the specified dataset.
     *
     @param dataset  the dataset (<code>null</code> not permitted).
     *
     @return The range (or <code>null</code> if the dataset is empty).
     */
    public Range findRangeBounds(CategoryDataset dataset) {
        if (dataset == null) {
            return null;
        }
        boolean allItemsNull = true// we'll set this to false if there is at
                                     // least one non-null data item...
        double minimum = 0.0;
        double maximum = 0.0;
        int columnCount = dataset.getColumnCount();
        for (int row = 0; row < dataset.getRowCount(); row++) {
            double runningTotal = 0.0;
            for (int column = 0; column <= columnCount - 1; column++) {
                Number n = dataset.getValue(row, column);
                if (n != null) {
                    allItemsNull = false;
                    double value = n.doubleValue();
                    if (column == columnCount - 1) {
                        // treat the last column value as an absolute
                        runningTotal = value;
                    }
                    else {
                        runningTotal = runningTotal + value;
                    }
                    minimum = Math.min(minimum, runningTotal);
                    maximum = Math.max(maximum, runningTotal);
                }
            }

        }
        if (!allItemsNull) {
            return new Range(minimum, maximum);
        }
        else {
            return null;
        }

    }

    /**
     * Draws the bar for a single (series, category) data item.
     *
     @param g2  the graphics device.
     @param state  the renderer state.
     @param dataArea  the data area.
     @param plot  the plot.
     @param domainAxis  the domain axis.
     @param rangeAxis  the range axis.
     @param dataset  the dataset.
     @param row  the row index (zero-based).
     @param column  the column index (zero-based).
     @param pass  the pass index.
     */
    public void drawItem(Graphics2D g2,
                         CategoryItemRendererState state,
                         Rectangle2D dataArea,
                         CategoryPlot plot,
                         CategoryAxis domainAxis,
                         ValueAxis rangeAxis,
                         CategoryDataset dataset,
                         int row,
                         int column,
                         int pass) {

        double previous = state.getSeriesRunningTotal();
        if (column == dataset.getColumnCount() 1) {
            previous = 0.0;
        }
        double current = 0.0;
        Number n = dataset.getValue(row, column);
        if (n != null) {
            current = previous + n.doubleValue();
        }
        state.setSeriesRunningTotal(current);

        int categoryCount = getColumnCount();
        PlotOrientation orientation = plot.getOrientation();

        double rectX = 0.0;
        double rectY = 0.0;

        RectangleEdge rangeAxisLocation = plot.getRangeAxisEdge();

        // Y0
        double j2dy0 = rangeAxis.valueToJava2D(previous, dataArea,
                rangeAxisLocation);

        // Y1
        double j2dy1 = rangeAxis.valueToJava2D(current, dataArea,
                rangeAxisLocation);

        double valDiff = current - previous;
        if (j2dy1 < j2dy0) {
            double temp = j2dy1;
            j2dy1 = j2dy0;
            j2dy0 = temp;
        }

        // BAR WIDTH
        double rectWidth = state.getBarWidth();

        // BAR HEIGHT
        double rectHeight = Math.max(getMinimumBarLength(),
                Math.abs(j2dy1 - j2dy0));

        Comparable seriesKey = dataset.getRowKey(row);
        Comparable categoryKey = dataset.getColumnKey(column);
        if (orientation == PlotOrientation.HORIZONTAL) {
            rectY = domainAxis.getCategorySeriesMiddle(categoryKey, seriesKey,
                    dataset, getItemMargin(), dataArea, RectangleEdge.LEFT);

            rectX = j2dy0;
            rectHeight = state.getBarWidth();
            rectY = rectY - rectHeight / 2.0;
            rectWidth = Math.max(getMinimumBarLength(),
                    Math.abs(j2dy1 - j2dy0));

        }
        else if (orientation == PlotOrientation.VERTICAL) {
            rectX = domainAxis.getCategorySeriesMiddle(categoryKey, seriesKey,
                    dataset, getItemMargin(), dataArea, RectangleEdge.TOP);
            rectX = rectX - rectWidth / 2.0;
            rectY = j2dy0;
        }
        Rectangle2D bar = new Rectangle2D.Double(rectX, rectY, rectWidth,
                rectHeight);
        Paint seriesPaint;
        if (column == 0) {
            seriesPaint = getFirstBarPaint();
        }
        else if (column == categoryCount - 1) {
            seriesPaint = getLastBarPaint();
        }
        else {
            if (valDiff < 0.0) {
                seriesPaint = getNegativeBarPaint();
            }
            else if (valDiff > 0.0) {
                seriesPaint = getPositiveBarPaint();
            }
            else {
                seriesPaint = getLastBarPaint();
            }
        }
        if (getGradientPaintTransformer() != null
                && seriesPaint instanceof GradientPaint) {
            GradientPaint gp = (GradientPaintseriesPaint;
            seriesPaint = getGradientPaintTransformer().transform(gp, bar);
        }
        g2.setPaint(seriesPaint);
        g2.fill(bar);

        // draw the outline...
        if (isDrawBarOutline()
                && state.getBarWidth() > BAR_OUTLINE_WIDTH_THRESHOLD) {
            Stroke stroke = getItemOutlineStroke(row, column);
            Paint paint = getItemOutlinePaint(row, column);
            if (stroke != null && paint != null) {
                g2.setStroke(stroke);
                g2.setPaint(paint);
                g2.draw(bar);
            }
        }

        CategoryItemLabelGenerator generator
            = getItemLabelGenerator(row, column);
        if (generator != null && isItemLabelVisible(row, column)) {
            drawItemLabel(g2, dataset, row, column, plot, generator, bar,
                    (valDiff < 0.0));
        }

        // add an item entity, if this information is being collected
        EntityCollection entities = state.getEntityCollection();
        if (entities != null) {
            addItemEntity(entities, dataset, row, column, bar);
        }

    }

    /**
     * Tests an object for equality with this instance.
     *
     @param obj  the object (<code>null</code> permitted).
     *
     @return A boolean.
     */
    public boolean equals(Object obj) {

        if (obj == this) {
            return true;
        }
        if (!super.equals(obj)) {
            return false;
        }
        if (!(obj instanceof WaterfallBarRenderer)) {
            return false;
        }
        WaterfallBarRenderer that = (WaterfallBarRendererobj;
        if (!PaintUtilities.equal(this.firstBarPaint, that.firstBarPaint)) {
            return false;
        }
        if (!PaintUtilities.equal(this.lastBarPaint, that.lastBarPaint)) {
            return false;
        }
        if (!PaintUtilities.equal(this.positiveBarPaint,
                that.positiveBarPaint)) {
            return false;
        }
        if (!PaintUtilities.equal(this.negativeBarPaint,
                that.negativeBarPaint)) {
            return false;
        }
        return true;

    }

    /**
     * Provides serialization support.
     *
     @param stream  the output stream.
     *
     @throws IOException  if there is an I/O error.
     */
    private void writeObject(ObjectOutputStream streamthrows IOException {
        stream.defaultWriteObject();
        SerialUtilities.writePaint(this.firstBarPaint, stream);
        SerialUtilities.writePaint(this.lastBarPaint, stream);
        SerialUtilities.writePaint(this.positiveBarPaint, stream);
        SerialUtilities.writePaint(this.negativeBarPaint, stream);
    }

    /**
     * Provides serialization support.
     *
     @param stream  the input stream.
     *
     @throws IOException  if there is an I/O error.
     @throws ClassNotFoundException  if there is a classpath problem.
     */
    private void readObject(ObjectInputStream stream)
        throws IOException, ClassNotFoundException {
        stream.defaultReadObject();
        this.firstBarPaint = SerialUtilities.readPaint(stream);
        this.lastBarPaint = SerialUtilities.readPaint(stream);
        this.positiveBarPaint = SerialUtilities.readPaint(stream);
        this.negativeBarPaint = SerialUtilities.readPaint(stream);
    }

}