    001    /* ===========================================================
    002     * JFreeChart : a free chart library for the Java(tm) platform
    003     * ===========================================================
    004     *
    005     * (C) Copyright 2000-2008, 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     * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
    025     * in the United States and other countries.]
    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
    066    package org.jfree.chart.renderer.xy;
    067
    068    import java.awt.Graphics2D;
    069    import java.awt.geom.Rectangle2D;
    070    import java.io.Serializable;
    071
    072    import org.jfree.chart.axis.ValueAxis;
    073    import org.jfree.chart.entity.EntityCollection;
    074    import org.jfree.chart.labels.XYItemLabelGenerator;
    075    import org.jfree.chart.plot.CrosshairState;
    076    import org.jfree.chart.plot.PlotOrientation;
    077    import org.jfree.chart.plot.PlotRenderingInfo;
    078    import org.jfree.chart.plot.XYPlot;
    079    import org.jfree.data.Range;
    080    import org.jfree.data.xy.IntervalXYDataset;
    081    import org.jfree.data.xy.XYDataset;
    082    import org.jfree.ui.RectangleEdge;
    083    import 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     */
    100    public 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    }