    001    /* ===========================================================
    002     * JFreeChart : a free chart library for the Java(tm) platform
    003     * ===========================================================
    004     *
    005     * (C) Copyright 2000-2005, 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-2005, 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     * $Id: ClusteredXYBarRenderer.java,v 1.8.2.1 2005/10/25 20:56:21 mungady Exp $
    038     *
    039     * Changes
    040     * -------
    041     * 24-Jan-2003 : Version 1, contributed by Paolo Cova (DG);
    042     * 25-Mar-2003 : Implemented Serializable (DG);
    043     * 01-May-2003 : Modified drawItem() method signature (DG);
    044     * 30-Jul-2003 : Modified entity constructor (CZ);
    045     * 20-Aug-2003 : Implemented Cloneable and PublicCloneable (DG);
    046     * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG);
    047     * 07-Oct-2003 : Added renderer state (DG);
    048     * 03-Nov-2003 : In draw method added state parameter and y==null value
    049     *               handling (MR);
    050     * 25-Feb-2004 : Replaced CrosshairInfo with CrosshairState (DG);
    051     * 15-Jul-2004 : Switched getX() with getXValue() and getY() with
    052     *               getYValue() (DG);
    053     * 01-Oct-2004 : Fixed bug where 'drawBarOutline' flag is ignored (DG);
    054     * 16-May-2005 : Fixed to used outline stroke for bar outlines.  Removed some
    055     *               redundant code with the result that the renderer now respects
    056     *               the 'base' setting from the super-class. Added an equals()
    057     *               method (DG);
    058     * 19-May-2005 : Added minimal item label implementation - needs improving (DG);
    059     *
    060     */
    061
    062    package org.jfree.chart.renderer.xy;
    063
    064    import java.awt.Graphics2D;
    065    import java.awt.Paint;
    066    import java.awt.geom.Rectangle2D;
    067    import java.io.Serializable;
    068
    069    import org.jfree.chart.axis.ValueAxis;
    070    import org.jfree.chart.entity.EntityCollection;
    071    import org.jfree.chart.entity.XYItemEntity;
    072    import org.jfree.chart.labels.XYToolTipGenerator;
    073    import org.jfree.chart.plot.CrosshairState;
    074    import org.jfree.chart.plot.PlotOrientation;
    075    import org.jfree.chart.plot.PlotRenderingInfo;
    076    import org.jfree.chart.plot.XYPlot;
    077    import org.jfree.data.xy.IntervalXYDataset;
    078    import org.jfree.data.xy.XYDataset;
    079    import org.jfree.ui.RectangleEdge;
    080    import org.jfree.util.PublicCloneable;
    081
    082    /**
    083     * An extension of {@link XYBarRenderer} that displays bars for different
    084     * series values at the same x next to each other. The assumption here is
    085     * that for each x (time or else) there is a y value for each series. If
    086     * this is not the case, there will be spaces between bars for a given x.
    087     * <P>
    088     * This renderer does not include code to calculate the crosshair point for the
    089     * plot.
    090     *
    091     * @author Paolo Cova
    092     */
    093    public class ClusteredXYBarRenderer extends XYBarRenderer
    094                                        implements Cloneable, PublicCloneable,
    095                                                   Serializable {
    096
    097        /** For serialization. */
    098        private static final long serialVersionUID = 5864462149177133147L;
    099
    100        /** Determines whether bar center should be interval start. */
    101        private boolean centerBarAtStartValue;
    102
    103        /**
    104         * Default constructor. Bar margin is set to 0.0.
    105        */
    106        public ClusteredXYBarRenderer() {
    107            this(0.0, false);
    108        }
    109
    110        /**
    111        * Constructs a new XY clustered bar renderer.
    112        *
    113        * @param margin  the percentage amount to trim from the width of each bar.
    114        * @param centerBarAtStartValue  if true, bars will be centered on the start
    115        *                               of the time period.
    116        */
    117        public ClusteredXYBarRenderer(double margin,
    118                                      boolean centerBarAtStartValue) {
    119            super(margin);
    120            this.centerBarAtStartValue = centerBarAtStartValue;
    121        }
    122
    123        /**
    124         * Draws the visual representation of a single data item. This method
    125         * is mostly copied from the superclass, the change is that in the
    126         * calculated space for a singe bar we draw bars for each series next to
    127         * each other. The width of each bar is the available width divided by
    128         * the number of series. Bars for each series are drawn in order left to
    129         * right.
    130         *
    131         * @param g2  the graphics device.
    132         * @param state  the renderer state.
    133         * @param dataArea  the area within which the plot is being drawn.
    134         * @param info  collects information about the drawing.
    135         * @param plot  the plot (can be used to obtain standard color
    136         *              information etc).
    137         * @param domainAxis  the domain axis.
    138         * @param rangeAxis  the range axis.
    139         * @param dataset  the dataset.
    140         * @param series  the series index.
    141         * @param item  the item index.
    142         * @param crosshairState  crosshair information for the plot
    143         *                        (<code>null</code> permitted).
    144         * @param pass  the pass index.
    145         */
    146        public void drawItem(Graphics2D g2,
    147                             XYItemRendererState state,
    148                             Rectangle2D dataArea,
    149                             PlotRenderingInfo info,
    150                             XYPlot plot,
    151                             ValueAxis domainAxis,
    152                             ValueAxis rangeAxis,
    153                             XYDataset dataset, int series, int item,
    154                             CrosshairState crosshairState,
    155                             int pass) {
    156
    157            IntervalXYDataset intervalDataset = (IntervalXYDataset) dataset;
    158
    159            Paint seriesPaint = getItemPaint(series, item);
    160
    161            double value0;
    162            double value1;
    163            if (getUseYInterval()) {
    164                value0 = intervalDataset.getStartYValue(series, item);
    165                value1 = intervalDataset.getEndYValue(series, item);
    166            }
    167            else {
    168                value0 = getBase();
    169                value1 = intervalDataset.getYValue(series, item);
    170            }
    171            if (Double.isNaN(value0) || Double.isNaN(value1)) {
    172                return;
    173            }
    174
    175            double translatedValue0 = rangeAxis.valueToJava2D(
    176                value0, dataArea, plot.getRangeAxisEdge()
    177            );
    178            double translatedValue1 = rangeAxis.valueToJava2D(
    179                value1, dataArea, plot.getRangeAxisEdge()
    180            );
    181
    182            RectangleEdge xAxisLocation = plot.getDomainAxisEdge();
    183            double x1 = intervalDataset.getStartXValue(series, item);
    184            double translatedX1 = domainAxis.valueToJava2D(
    185                x1, dataArea, xAxisLocation
    186            );
    187
    188            double x2 = intervalDataset.getEndXValue(series, item);
    189            double translatedX2 = domainAxis.valueToJava2D(
    190                x2, dataArea, xAxisLocation
    191            );
    192
    193            double translatedWidth = Math.max(
    194                1, Math.abs(translatedX2 - translatedX1)
    195            );
    196            double translatedHeight = Math.abs(translatedValue0 - translatedValue1);
    197
    198            if (this.centerBarAtStartValue) {
    199                translatedX1 -= translatedWidth / 2;
    200            }
    201
    202            if (getMargin() > 0.0) {
    203                double cut = translatedWidth * getMargin();
    204                translatedWidth = translatedWidth - cut;
    205                translatedX1 = translatedX1 + cut / 2;
    206            }
    207
    208            int numSeries = dataset.getSeriesCount();
    209            double seriesBarWidth = translatedWidth / numSeries;
    210
    211            Rectangle2D bar = null;
    212            PlotOrientation orientation = plot.getOrientation();
    213            if (orientation == PlotOrientation.HORIZONTAL) {
    214                bar = new Rectangle2D.Double(
    215                    Math.min(translatedValue0, translatedValue1),
    216                    translatedX1 - seriesBarWidth * (numSeries - series),
    217                    translatedHeight, seriesBarWidth
    218                );
    219            }
    220            else if (orientation == PlotOrientation.VERTICAL) {
    221
    222                bar = new Rectangle2D.Double(
    223                    translatedX1 + seriesBarWidth * series,
    224                    Math.min(translatedValue0, translatedValue1),
    225                    seriesBarWidth, translatedHeight
    226                );
    227
    228            }
    229            g2.setPaint(seriesPaint);
    230            g2.fill(bar);
    231            if (isDrawBarOutline() && Math.abs(translatedX2 - translatedX1) > 3) {
    232                g2.setStroke(getItemOutlineStroke(series, item));
    233                g2.setPaint(getItemOutlinePaint(series, item));
    234                g2.draw(bar);
    235            }
    236
    237            // TODO: we need something better for the item labels
    238            if (isItemLabelVisible(series, item)) {
    239                drawItemLabel(
    240                    g2, orientation, dataset, series, item, bar.getCenterX(),
    241                    bar.getY(), value1 < 0.0
    242                );
    243            }
    244
    245            // add an entity for the item...
    246            if (info != null) {
    247                EntityCollection entities = info.getOwner().getEntityCollection();
    248                if (entities != null) {
    249                    String tip = null;
    250                    XYToolTipGenerator generator
    251                        = getToolTipGenerator(series, item);
    252                    if (generator != null) {
    253                        tip = generator.generateToolTip(dataset, series, item);
    254                    }
    255                    String url = null;
    256                    if (getURLGenerator() != null) {
    257                        url = getURLGenerator().generateURL(dataset, series, item);
    258                    }
    259                    XYItemEntity entity = new XYItemEntity(
    260                        bar, dataset, series, item, tip, url
    261                    );
    262                    entities.add(entity);
    263                }
    264            }
    265
    266        }
    267
    268        /**
    269         * Tests this renderer for equality with an arbitrary object, returning
    270         * <code>true</code> if <code>obj</code> is a
    271         * <code>ClusteredXYBarRenderer</code> with the same settings as this
    272         * renderer, and <code>false</code> otherwise.
    273         *
    274         * @param obj  the object (<code>null</code> permitted).
    275         *
    276         * @return A boolean.
    277         */
    278        public boolean equals(Object obj) {
    279            if (obj == this) {
    280                return true;
    281            }
    282            if (!(obj instanceof ClusteredXYBarRenderer)) {
    283                return false;
    284            }
    285            if (!super.equals(obj)) {
    286                return false;
    287            }
    288            ClusteredXYBarRenderer that = (ClusteredXYBarRenderer) obj;
    289            if (this.centerBarAtStartValue != that.centerBarAtStartValue) {
    290                return false;
    291            }
    292            return true;
    293        }
    294
    295        /**
    296         * Returns a clone of the renderer.
    297         *
    298         * @return A clone.
    299         *
    300         * @throws CloneNotSupportedException  if the renderer cannot be cloned.
    301         */
    302        public Object clone() throws CloneNotSupportedException {
    303            return super.clone();
    304        }
    305
    306    }
    307