    001/* ===========================================================
    002 * JFreeChart : a free chart library for the Java(tm) platform
    003 * ===========================================================
    004 *
    005 * (C) Copyright 2000-2011, 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 * ClusteredXYBarRenderer.java
    029 * ---------------------------
    030 * (C) Copyright 2003-2008, by Paolo Cova and Contributors.
    031 *
    032 * Original Author:  Paolo Cova;
    033 * Contributor(s):   David Gilbert (for Object Refinery Limited);
    034 *                   Christian W. Zuckschwerdt;
    035 *                   Matthias Rose;
    036 *
    037 * Changes
    038 * -------
    039 * 24-Jan-2003 : Version 1, contributed by Paolo Cova (DG);
    040 * 25-Mar-2003 : Implemented Serializable (DG);
    041 * 01-May-2003 : Modified drawItem() method signature (DG);
    042 * 30-Jul-2003 : Modified entity constructor (CZ);
    043 * 20-Aug-2003 : Implemented Cloneable and PublicCloneable (DG);
    044 * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG);
    045 * 07-Oct-2003 : Added renderer state (DG);
    046 * 03-Nov-2003 : In draw method added state parameter and y==null value
    047 *               handling (MR);
    048 * 25-Feb-2004 : Replaced CrosshairInfo with CrosshairState (DG);
    049 * 15-Jul-2004 : Switched getX() with getXValue() and getY() with
    050 *               getYValue() (DG);
    051 * 01-Oct-2004 : Fixed bug where 'drawBarOutline' flag is ignored (DG);
    052 * 16-May-2005 : Fixed to used outline stroke for bar outlines.  Removed some
    053 *               redundant code with the result that the renderer now respects
    054 *               the 'base' setting from the super-class. Added an equals()
    055 *               method (DG);
    056 * 19-May-2005 : Added minimal item label implementation - needs improving (DG);
    057 * ------------- JFREECHART 1.0.x ---------------------------------------------
    058 * 11-Dec-2006 : Added support for GradientPaint (DG);
    059 * 12-Jun-2007 : Added override to findDomainBounds() to handle cluster offset,
    060 *               fixed rendering to handle inverted axes, and simplified
    061 *               entity generation code (DG);
    062 * 24-Jun-2008 : Added new barPainter mechanism (DG);
    063 *
    064 */
    065
    066package org.jfree.chart.renderer.xy;
    067
    068import java.awt.Graphics2D;
    069import java.awt.geom.Rectangle2D;
    070import java.io.Serializable;
    071
    072import org.jfree.chart.axis.ValueAxis;
    073import org.jfree.chart.entity.EntityCollection;
    074import org.jfree.chart.labels.XYItemLabelGenerator;
    075import org.jfree.chart.plot.CrosshairState;
    076import org.jfree.chart.plot.PlotOrientation;
    077import org.jfree.chart.plot.PlotRenderingInfo;
    078import org.jfree.chart.plot.XYPlot;
    079import org.jfree.data.Range;
    080import org.jfree.data.xy.IntervalXYDataset;
    081import org.jfree.data.xy.XYDataset;
    082import org.jfree.ui.RectangleEdge;
    083import org.jfree.util.PublicCloneable;
    084
    085/**
    086 * An extension of {@link XYBarRenderer} that displays bars for different
    087 * series values at the same x next to each other. The assumption here is
    088 * that for each x (time or else) there is a y value for each series. If
    089 * this is not the case, there will be spaces between bars for a given x.
    090 * The example shown here is generated by the
    091 * <code>ClusteredXYBarRendererDemo1.java</code> program included in the
    092 * JFreeChart demo collection:
    093 * <br><br>
    094 * <img src="../../../../../images/ClusteredXYBarRendererSample.png"
    095 * alt="ClusteredXYBarRendererSample.png" />
    096 * <P>
    097 * This renderer does not include code to calculate the crosshair point for the
    098 * plot.
    099 */
    100public class ClusteredXYBarRenderer extends XYBarRenderer
    101        implements Cloneable, PublicCloneable, Serializable {
    102
    103    /** For serialization. */
    104    private static final long serialVersionUID = 5864462149177133147L;
    105
    106    /** Determines whether bar center should be interval start. */
    107    private boolean centerBarAtStartValue;
    108
    109    /**
    110     * Default constructor. Bar margin is set to 0.0.
    111     */
    112    public ClusteredXYBarRenderer() {
    113        this(0.0, false);
    114    }
    115
    116    /**
    117     * Constructs a new XY clustered bar renderer.
    118     *
    119     * @param margin  the percentage amount to trim from the width of each bar.
    120     * @param centerBarAtStartValue  if true, bars will be centered on the
    121     *         start of the time period.
    122     */
    123    public ClusteredXYBarRenderer(double margin,
    124                                  boolean centerBarAtStartValue) {
    125        super(margin);
    126        this.centerBarAtStartValue = centerBarAtStartValue;
    127    }
    128
    129    /**
    130     * Returns the number of passes through the dataset that this renderer
    131     * requires.  In this case, two passes are required, the first for drawing
    132     * the shadows (if visible), and the second for drawing the bars.
    133     *
    134     * @return <code>2</code>.
    135     */
    136    public int getPassCount() {
    137        return 2;
    138    }
    139
    140    /**
    141     * Returns the x-value bounds for the specified dataset.
    142     *
    143     * @param dataset  the dataset (<code>null</code> permitted).
    144     *
    145     * @return The bounds (possibly <code>null</code>).
    146     */
    147    public Range findDomainBounds(XYDataset dataset) {
    148        if (dataset == null) {
    149            return null;
    150        }
    151        // need to handle cluster centering as a special case
    152        if (this.centerBarAtStartValue) {
    153            return findDomainBoundsWithOffset((IntervalXYDataset) dataset);
    154        }
    155        else {
    156            return super.findDomainBounds(dataset);
    157        }
    158    }
    159
    160    /**
    161     * Iterates over the items in an {@link IntervalXYDataset} to find
    162     * the range of x-values including the interval OFFSET so that it centers
    163     * the interval around the start value.
    164     *
    165     * @param dataset  the dataset (<code>null</code> not permitted).
    166     *
    167     * @return The range (possibly <code>null</code>).
    168     */
    169    protected Range findDomainBoundsWithOffset(IntervalXYDataset dataset) {
    170        if (dataset == null) {
    171            throw new IllegalArgumentException("Null 'dataset' argument.");
    172        }
    173        double minimum = Double.POSITIVE_INFINITY;
    174        double maximum = Double.NEGATIVE_INFINITY;
    175        int seriesCount = dataset.getSeriesCount();
    176        double lvalue;
    177        double uvalue;
    178        for (int series = 0; series < seriesCount; series++) {
    179            int itemCount = dataset.getItemCount(series);
    180            for (int item = 0; item < itemCount; item++) {
    181                lvalue = dataset.getStartXValue(series, item);
    182                uvalue = dataset.getEndXValue(series, item);
    183                double offset = (uvalue - lvalue) / 2.0;
    184                lvalue = lvalue - offset;
    185                uvalue = uvalue - offset;
    186                minimum = Math.min(minimum, lvalue);
    187                maximum = Math.max(maximum, uvalue);
    188            }
    189        }
    190
    191        if (minimum > maximum) {
    192            return null;
    193        }
    194        else {
    195            return new Range(minimum, maximum);
    196        }
    197    }
    198
    199    /**
    200     * Draws the visual representation of a single data item. This method
    201     * is mostly copied from the superclass, the change is that in the
    202     * calculated space for a singe bar we draw bars for each series next to
    203     * each other. The width of each bar is the available width divided by
    204     * the number of series. Bars for each series are drawn in order left to
    205     * right.
    206     *
    207     * @param g2  the graphics device.
    208     * @param state  the renderer state.
    209     * @param dataArea  the area within which the plot is being drawn.
    210     * @param info  collects information about the drawing.
    211     * @param plot  the plot (can be used to obtain standard color
    212     *              information etc).
    213     * @param domainAxis  the domain axis.
    214     * @param rangeAxis  the range axis.
    215     * @param dataset  the dataset.
    216     * @param series  the series index.
    217     * @param item  the item index.
    218     * @param crosshairState  crosshair information for the plot
    219     *                        (<code>null</code> permitted).
    220     * @param pass  the pass index.
    221     */
    222    public void drawItem(Graphics2D g2,
    223                         XYItemRendererState state,
    224                         Rectangle2D dataArea,
    225                         PlotRenderingInfo info,
    226                         XYPlot plot,
    227                         ValueAxis domainAxis,
    228                         ValueAxis rangeAxis,
    229                         XYDataset dataset, int series, int item,
    230                         CrosshairState crosshairState,
    231                         int pass) {
    232
    233        IntervalXYDataset intervalDataset = (IntervalXYDataset) dataset;
    234
    235        double y0;
    236        double y1;
    237        if (getUseYInterval()) {
    238            y0 = intervalDataset.getStartYValue(series, item);
    239            y1 = intervalDataset.getEndYValue(series, item);
    240        }
    241        else {
    242            y0 = getBase();
    243            y1 = intervalDataset.getYValue(series, item);
    244        }
    245        if (Double.isNaN(y0) || Double.isNaN(y1)) {
    246            return;
    247        }
    248
    249        double yy0 = rangeAxis.valueToJava2D(y0, dataArea,
    250                plot.getRangeAxisEdge());
    251        double yy1 = rangeAxis.valueToJava2D(y1, dataArea,
    252                plot.getRangeAxisEdge());
    253
    254        RectangleEdge xAxisLocation = plot.getDomainAxisEdge();
    255        double x0 = intervalDataset.getStartXValue(series, item);
    256        double xx0 = domainAxis.valueToJava2D(x0, dataArea, xAxisLocation);
    257
    258        double x1 = intervalDataset.getEndXValue(series, item);
    259        double xx1 = domainAxis.valueToJava2D(x1, dataArea, xAxisLocation);
    260
    261        double intervalW = xx1 - xx0;  // this may be negative
    262        double baseX = xx0;
    263        if (this.centerBarAtStartValue) {
    264            baseX = baseX - intervalW / 2.0;
    265        }
    266        double m = getMargin();
    267        if (m > 0.0) {
    268            double cut = intervalW * getMargin();
    269            intervalW = intervalW - cut;
    270            baseX = baseX + (cut / 2);
    271        }
    272
    273        double intervalH = Math.abs(yy0 - yy1);  // we don't need the sign
    274
    275        PlotOrientation orientation = plot.getOrientation();
    276
    277        int numSeries = dataset.getSeriesCount();
    278        double seriesBarWidth = intervalW / numSeries;  // may be negative
    279
    280        Rectangle2D bar = null;
    281        if (orientation == PlotOrientation.HORIZONTAL) {
    282            double barY0 = baseX + (seriesBarWidth * series);
    283            double barY1 = barY0 + seriesBarWidth;
    284            double rx = Math.min(yy0, yy1);
    285            double rw = intervalH;
    286            double ry = Math.min(barY0, barY1);
    287            double rh = Math.abs(barY1 - barY0);
    288            bar = new Rectangle2D.Double(rx, ry, rw, rh);
    289        }
    290        else if (orientation == PlotOrientation.VERTICAL) {
    291            double barX0 = baseX + (seriesBarWidth * series);
    292            double barX1 = barX0 + seriesBarWidth;
    293            double rx = Math.min(barX0, barX1);
    294            double rw = Math.abs(barX1 - barX0);
    295            double ry = Math.min(yy0, yy1);
    296            double rh = intervalH;
    297            bar = new Rectangle2D.Double(rx, ry, rw, rh);
    298        }
    299        boolean positive = (y1 > 0.0);
    300        boolean inverted = rangeAxis.isInverted();
    301        RectangleEdge barBase;
    302        if (orientation == PlotOrientation.HORIZONTAL) {
    303            if (positive && inverted || !positive && !inverted) {
    304                barBase = RectangleEdge.RIGHT;
    305            }
    306            else {
    307                barBase = RectangleEdge.LEFT;
    308            }
    309        }
    310        else {
    311            if (positive && !inverted || !positive && inverted) {
    312                barBase = RectangleEdge.BOTTOM;
    313            }
    314            else {
    315                barBase = RectangleEdge.TOP;
    316            }
    317        }
    318        if (pass == 0 && getShadowsVisible()) {
    319            getBarPainter().paintBarShadow(g2, this, series, item, bar, barBase,
    320                !getUseYInterval());
    321        }
    322        if (pass == 1) {
    323            getBarPainter().paintBar(g2, this, series, item, bar, barBase);
    324
    325            if (isItemLabelVisible(series, item)) {
    326                XYItemLabelGenerator generator = getItemLabelGenerator(series,
    327                        item);
    328                drawItemLabel(g2, dataset, series, item, plot, generator, bar,
    329                        y1 < 0.0);
    330            }
    331
    332            // add an entity for the item...
    333            if (info != null) {
    334                EntityCollection entities
    335                        = info.getOwner().getEntityCollection();
    336                if (entities != null) {
    337                    addEntity(entities, bar, dataset, series, item,
    338                            bar.getCenterX(), bar.getCenterY());
    339                }
    340            }
    341        }
    342
    343    }
    344
    345    /**
    346     * Tests this renderer for equality with an arbitrary object, returning
    347     * <code>true</code> if <code>obj</code> is a
    348     * <code>ClusteredXYBarRenderer</code> with the same settings as this
    349     * renderer, and <code>false</code> otherwise.
    350     *
    351     * @param obj  the object (<code>null</code> permitted).
    352     *
    353     * @return A boolean.
    354     */
    355    public boolean equals(Object obj) {
    356        if (obj == this) {
    357            return true;
    358        }
    359        if (!(obj instanceof ClusteredXYBarRenderer)) {
    360            return false;
    361        }
    362        ClusteredXYBarRenderer that = (ClusteredXYBarRenderer) obj;
    363        if (this.centerBarAtStartValue != that.centerBarAtStartValue) {
    364            return false;
    365        }
    366        return super.equals(obj);
    367    }
    368
    369    /**
    370     * Returns a clone of the renderer.
    371     *
    372     * @return A clone.
    373     *
    374     * @throws CloneNotSupportedException  if the renderer cannot be cloned.
    375     */
    376    public Object clone() throws CloneNotSupportedException {
    377        return super.clone();
    378    }
    379
    380}