    001    /* ===========================================================
    002     * JFreeChart : a free chart library for the Java(tm) platform
    003     * ===========================================================
    004     *
    005     * (C) Copyright 2000-2007, 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-2007, 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     *
    063     */
    064
    065    package org.jfree.chart.renderer.xy;
    066
    067    import java.awt.GradientPaint;
    068    import java.awt.Graphics2D;
    069    import java.awt.Paint;
    070    import java.awt.geom.Rectangle2D;
    071    import java.io.Serializable;
    072
    073    import org.jfree.chart.axis.ValueAxis;
    074    import org.jfree.chart.entity.EntityCollection;
    075    import org.jfree.chart.labels.XYItemLabelGenerator;
    076    import org.jfree.chart.plot.CrosshairState;
    077    import org.jfree.chart.plot.PlotOrientation;
    078    import org.jfree.chart.plot.PlotRenderingInfo;
    079    import org.jfree.chart.plot.XYPlot;
    080    import org.jfree.data.Range;
    081    import org.jfree.data.xy.IntervalXYDataset;
    082    import org.jfree.data.xy.XYDataset;
    083    import org.jfree.ui.RectangleEdge;
    084    import org.jfree.util.PublicCloneable;
    085
    086    /**
    087     * An extension of {@link XYBarRenderer} that displays bars for different
    088     * series values at the same x next to each other. The assumption here is
    089     * that for each x (time or else) there is a y value for each series. If
    090     * this is not the case, there will be spaces between bars for a given x.
    091     * <P>
    092     * This renderer does not include code to calculate the crosshair point for the
    093     * plot.
    094     */
    095    public class ClusteredXYBarRenderer extends XYBarRenderer
    096            implements Cloneable, PublicCloneable, Serializable {
    097
    098        /** For serialization. */
    099        private static final long serialVersionUID = 5864462149177133147L;
    100
    101        /** Determines whether bar center should be interval start. */
    102        private boolean centerBarAtStartValue;
    103
    104        /**
    105         * Default constructor. Bar margin is set to 0.0.
    106         */
    107        public ClusteredXYBarRenderer() {
    108            this(0.0, false);
    109        }
    110
    111        /**
    112         * Constructs a new XY clustered bar renderer.
    113         *
    114         * @param margin  the percentage amount to trim from the width of each bar.
    115         * @param centerBarAtStartValue  if true, bars will be centered on the
    116         *         start of the time period.
    117         */
    118        public ClusteredXYBarRenderer(double margin,
    119                                      boolean centerBarAtStartValue) {
    120            super(margin);
    121            this.centerBarAtStartValue = centerBarAtStartValue;
    122        }
    123
    124        /**
    125         * Returns the x-value bounds for the specified dataset.
    126         *
    127         * @param dataset  the dataset (<code>null</code> permitted).
    128         *
    129         * @return The bounds (possibly <code>null</code>).
    130         */
    131        public Range findDomainBounds(XYDataset dataset) {
    132            if (dataset == null) {
    133                return null;
    134            }
    135            // need to handle cluster centering as a special case
    136            if (this.centerBarAtStartValue) {
    137                return findDomainBoundsWithOffset((IntervalXYDataset) dataset);
    138            }
    139            else {
    140                return super.findDomainBounds(dataset);
    141            }
    142        }
    143
    144        /**
    145         * Iterates over the items in an {@link IntervalXYDataset} to find
    146         * the range of x-values including the interval OFFSET so that it centers
    147         * the interval around the start value.
    148         *
    149         * @param dataset  the dataset (<code>null</code> not permitted).
    150         *
    151         * @return The range (possibly <code>null</code>).
    152         */
    153        protected Range findDomainBoundsWithOffset(IntervalXYDataset dataset) {
    154            if (dataset == null) {
    155                throw new IllegalArgumentException("Null 'dataset' argument.");
    156            }
    157            double minimum = Double.POSITIVE_INFINITY;
    158            double maximum = Double.NEGATIVE_INFINITY;
    159            int seriesCount = dataset.getSeriesCount();
    160            double lvalue;
    161            double uvalue;
    162            for (int series = 0; series < seriesCount; series++) {
    163                int itemCount = dataset.getItemCount(series);
    164                for (int item = 0; item < itemCount; item++) {
    165                    lvalue = dataset.getStartXValue(series, item);
    166                    uvalue = dataset.getEndXValue(series, item);
    167                    double offset = (uvalue - lvalue) / 2.0;
    168                    lvalue = lvalue - offset;
    169                    uvalue = uvalue - offset;
    170                    minimum = Math.min(minimum, lvalue);
    171                    maximum = Math.max(maximum, uvalue);
    172                }
    173            }
    174
    175            if (minimum > maximum) {
    176                return null;
    177            }
    178            else {
    179                return new Range(minimum, maximum);
    180            }
    181        }
    182
    183        /**
    184         * Draws the visual representation of a single data item. This method
    185         * is mostly copied from the superclass, the change is that in the
    186         * calculated space for a singe bar we draw bars for each series next to
    187         * each other. The width of each bar is the available width divided by
    188         * the number of series. Bars for each series are drawn in order left to
    189         * right.
    190         *
    191         * @param g2  the graphics device.
    192         * @param state  the renderer state.
    193         * @param dataArea  the area within which the plot is being drawn.
    194         * @param info  collects information about the drawing.
    195         * @param plot  the plot (can be used to obtain standard color
    196         *              information etc).
    197         * @param domainAxis  the domain axis.
    198         * @param rangeAxis  the range axis.
    199         * @param dataset  the dataset.
    200         * @param series  the series index.
    201         * @param item  the item index.
    202         * @param crosshairState  crosshair information for the plot
    203         *                        (<code>null</code> permitted).
    204         * @param pass  the pass index.
    205         */
    206        public void drawItem(Graphics2D g2,
    207                             XYItemRendererState state,
    208                             Rectangle2D dataArea,
    209                             PlotRenderingInfo info,
    210                             XYPlot plot,
    211                             ValueAxis domainAxis,
    212                             ValueAxis rangeAxis,
    213                             XYDataset dataset, int series, int item,
    214                             CrosshairState crosshairState,
    215                             int pass) {
    216
    217            IntervalXYDataset intervalDataset = (IntervalXYDataset) dataset;
    218
    219            double y0;
    220            double y1;
    221            if (getUseYInterval()) {
    222                y0 = intervalDataset.getStartYValue(series, item);
    223                y1 = intervalDataset.getEndYValue(series, item);
    224            }
    225            else {
    226                y0 = getBase();
    227                y1 = intervalDataset.getYValue(series, item);
    228            }
    229            if (Double.isNaN(y0) || Double.isNaN(y1)) {
    230                return;
    231            }
    232
    233            double yy0 = rangeAxis.valueToJava2D(y0, dataArea,
    234                    plot.getRangeAxisEdge());
    235            double yy1 = rangeAxis.valueToJava2D(y1, dataArea,
    236                    plot.getRangeAxisEdge());
    237
    238            RectangleEdge xAxisLocation = plot.getDomainAxisEdge();
    239            double x0 = intervalDataset.getStartXValue(series, item);
    240            double xx0 = domainAxis.valueToJava2D(x0, dataArea, xAxisLocation);
    241
    242            double x1 = intervalDataset.getEndXValue(series, item);
    243            double xx1 = domainAxis.valueToJava2D(x1, dataArea, xAxisLocation);
    244
    245            double intervalW = xx1 - xx0;  // this may be negative
    246            double baseX = xx0;
    247            if (this.centerBarAtStartValue) {
    248                baseX = baseX - intervalW / 2.0;
    249            }
    250            double m = getMargin();
    251            if (m > 0.0) {
    252                double cut = intervalW * getMargin();
    253                intervalW = intervalW - cut;
    254                baseX = baseX + (cut / 2);
    255            }
    256
    257            double intervalH = Math.abs(yy0 - yy1);  // we don't need the sign
    258
    259            PlotOrientation orientation = plot.getOrientation();
    260
    261            int numSeries = dataset.getSeriesCount();
    262            double seriesBarWidth = intervalW / numSeries;  // may be negative
    263
    264            Rectangle2D bar = null;
    265            if (orientation == PlotOrientation.HORIZONTAL) {
    266                double barY0 = baseX + (seriesBarWidth * series);
    267                double barY1 = barY0 + seriesBarWidth;
    268                double rx = Math.min(yy0, yy1);
    269                double rw = intervalH;
    270                double ry = Math.min(barY0, barY1);
    271                double rh = Math.abs(barY1 - barY0);
    272                bar = new Rectangle2D.Double(rx, ry, rw, rh);
    273            }
    274            else if (orientation == PlotOrientation.VERTICAL) {
    275                double barX0 = baseX + (seriesBarWidth * series);
    276                double barX1 = barX0 + seriesBarWidth;
    277                double rx = Math.min(barX0, barX1);
    278                double rw = Math.abs(barX1 - barX0);
    279                double ry = Math.min(yy0, yy1);;
    280                double rh = intervalH;
    281                bar = new Rectangle2D.Double(rx, ry, rw, rh);
    282            }
    283            Paint itemPaint = getItemPaint(series, item);
    284            if (getGradientPaintTransformer()
    285                    != null && itemPaint instanceof GradientPaint) {
    286                GradientPaint gp = (GradientPaint) itemPaint;
    287                itemPaint = getGradientPaintTransformer().transform(gp, bar);
    288            }
    289            g2.setPaint(itemPaint);
    290
    291            g2.fill(bar);
    292            if (isDrawBarOutline() && Math.abs(seriesBarWidth) > 3) {
    293                g2.setStroke(getItemOutlineStroke(series, item));
    294                g2.setPaint(getItemOutlinePaint(series, item));
    295                g2.draw(bar);
    296            }
    297
    298            if (isItemLabelVisible(series, item)) {
    299                XYItemLabelGenerator generator = getItemLabelGenerator(series,
    300                        item);
    301                drawItemLabel(g2, dataset, series, item, plot, generator, bar,
    302                        y1 < 0.0);
    303            }
    304
    305            // add an entity for the item...
    306            if (info != null) {
    307                EntityCollection entities = info.getOwner().getEntityCollection();
    308                if (entities != null) {
    309                    addEntity(entities, bar, dataset, series, item,
    310                            bar.getCenterX(), bar.getCenterY());
    311                }
    312            }
    313
    314        }
    315
    316        /**
    317         * Tests this renderer for equality with an arbitrary object, returning
    318         * <code>true</code> if <code>obj</code> is a
    319         * <code>ClusteredXYBarRenderer</code> with the same settings as this
    320         * renderer, and <code>false</code> otherwise.
    321         *
    322         * @param obj  the object (<code>null</code> permitted).
    323         *
    324         * @return A boolean.
    325         */
    326        public boolean equals(Object obj) {
    327            if (obj == this) {
    328                return true;
    329            }
    330            if (!(obj instanceof ClusteredXYBarRenderer)) {
    331                return false;
    332            }
    333            ClusteredXYBarRenderer that = (ClusteredXYBarRenderer) obj;
    334            if (this.centerBarAtStartValue != that.centerBarAtStartValue) {
    335                return false;
    336            }
    337            return super.equals(obj);
    338        }
    339
    340        /**
    341         * Returns a clone of the renderer.
    342         *
    343         * @return A clone.
    344         *
    345         * @throws CloneNotSupportedException  if the renderer cannot be cloned.
    346         */
    347        public Object clone() throws CloneNotSupportedException {
    348            return super.clone();
    349        }
    350
    351    }