    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     * XYAreaRenderer2.java
    029     * --------------------
    030     * (C) Copyright 2004-2007, by Hari and Contributors.
    031     *
    032     * Original Author:  Hari (ourhari@hotmail.com);
    033     * Contributor(s):   David Gilbert (for Object Refinery Limited);
    034     *                   Richard Atkinson;
    035     *                   Christian W. Zuckschwerdt;
    036     *
    037     * Changes:
    038     * --------
    039     * 03-Apr-2002 : Version 1, contributed by Hari.  This class is based on the
    040     *               StandardXYItemRenderer class (DG);
    041     * 09-Apr-2002 : Removed the translated zero from the drawItem method -
    042     *               overridden the initialise() method to calculate it (DG);
    043     * 30-May-2002 : Added tool tip generator to constructor to match super
    044     *               class (DG);
    045     * 25-Jun-2002 : Removed unnecessary local variable (DG);
    046     * 05-Aug-2002 : Small modification to drawItem method to support URLs for
    047     *               HTML image maps (RA);
    048     * 01-Oct-2002 : Fixed errors reported by Checkstyle (DG);
    049     * 07-Nov-2002 : Renamed AreaXYItemRenderer --> XYAreaRenderer (DG);
    050     * 25-Mar-2003 : Implemented Serializable (DG);
    051     * 01-May-2003 : Modified drawItem() method signature (DG);
    052     * 27-Jul-2003 : Made line and polygon properties protected rather than
    053     *               private (RA);
    054     * 30-Jul-2003 : Modified entity constructor (CZ);
    055     * 20-Aug-2003 : Implemented Cloneable and PublicCloneable (DG);
    056     * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG);
    057     * 07-Oct-2003 : Added renderer state (DG);
    058     * 08-Dec-2003 : Modified hotspot for chart entity (DG);
    059     * 10-Feb-2004 : Changed the drawItem() method to make cut-and-paste
    060     *               overriding easier.  Also moved state class into this
    061     *               class (DG);
    062     * 25-Feb-2004 : Replaced CrosshairInfo with CrosshairState.  Renamed
    063     *               XYToolTipGenerator --> XYItemLabelGenerator (DG);
    064     * 15-Jul-2004 : Switched getX() with getXValue() and getY() with
    065     *               getYValue() (DG);
    066     * 11-Nov-2004 : Now uses ShapeUtilities to translate shapes (DG);
    067     * 19-Jan-2005 : Now accesses only primitives from the dataset (DG);
    068     * 21-Mar-2005 : Override getLegendItem() (DG);
    069     * 20-Apr-2005 : Use generators for legend tooltips and URLs (DG);
    070     * ------------- JFREECHART 1.0.x ---------------------------------------------
    071     * 30-Nov-2006 : Fixed equals() and clone() implementations (DG);
    072     * 06-Feb-2007 : Fixed bug 1086307, crosshairs with multiple axes (DG);
    073     * 20-Apr-2007 : Updated getLegendItem() and drawItem() for renderer
    074     *               change (DG);
    075     * 17-May-2007 : Set datasetIndex and seriesIndex in getLegendItem() (DG);
    076     * 18-May-2007 : Set dataset and seriesKey for LegendItem (DG);
    077     *
    078     */
    079
    080    package org.jfree.chart.renderer.xy;
    081
    082
    083    import java.awt.Graphics2D;
    084    import java.awt.Paint;
    085    import java.awt.Polygon;
    086    import java.awt.Shape;
    087    import java.awt.Stroke;
    088    import java.awt.geom.GeneralPath;
    089    import java.awt.geom.Rectangle2D;
    090    import java.io.IOException;
    091    import java.io.ObjectInputStream;
    092    import java.io.ObjectOutputStream;
    093    import java.io.Serializable;
    094
    095    import org.jfree.chart.LegendItem;
    096    import org.jfree.chart.axis.ValueAxis;
    097    import org.jfree.chart.entity.EntityCollection;
    098    import org.jfree.chart.entity.XYItemEntity;
    099    import org.jfree.chart.event.RendererChangeEvent;
    100    import org.jfree.chart.labels.XYSeriesLabelGenerator;
    101    import org.jfree.chart.labels.XYToolTipGenerator;
    102    import org.jfree.chart.plot.CrosshairState;
    103    import org.jfree.chart.plot.PlotOrientation;
    104    import org.jfree.chart.plot.PlotRenderingInfo;
    105    import org.jfree.chart.plot.XYPlot;
    106    import org.jfree.chart.urls.XYURLGenerator;
    107    import org.jfree.data.xy.XYDataset;
    108    import org.jfree.io.SerialUtilities;
    109    import org.jfree.util.PublicCloneable;
    110    import org.jfree.util.ShapeUtilities;
    111
    112    /**
    113     * Area item renderer for an {@link XYPlot}.
    114     */
    115    public class XYAreaRenderer2 extends AbstractXYItemRenderer
    116                                 implements XYItemRenderer,
    117                                            Cloneable,
    118                                            PublicCloneable,
    119                                            Serializable {
    120
    121        /** For serialization. */
    122        private static final long serialVersionUID = -7378069681579984133L;
    123
    124        /** A flag that controls whether or not the outline is shown. */
    125        private boolean showOutline;
    126
    127        /**
    128         * The shape used to represent an area in each legend item (this should
    129         * never be <code>null</code>).
    130         */
    131        private transient Shape legendArea;
    132
    133        /**
    134         * Constructs a new renderer.
    135         */
    136        public XYAreaRenderer2() {
    137            this(null, null);
    138        }
    139
    140        /**
    141         * Constructs a new renderer.
    142         *
    143         * @param labelGenerator  the tool tip generator to use.  <code>null</code>
    144         *                        is none.
    145         * @param urlGenerator  the URL generator (null permitted).
    146         */
    147        public XYAreaRenderer2(XYToolTipGenerator labelGenerator,
    148                               XYURLGenerator urlGenerator) {
    149            super();
    150            this.showOutline = false;
    151            setBaseToolTipGenerator(labelGenerator);
    152            setURLGenerator(urlGenerator);
    153            GeneralPath area = new GeneralPath();
    154            area.moveTo(0.0f, -4.0f);
    155            area.lineTo(3.0f, -2.0f);
    156            area.lineTo(4.0f, 4.0f);
    157            area.lineTo(-4.0f, 4.0f);
    158            area.lineTo(-3.0f, -2.0f);
    159            area.closePath();
    160            this.legendArea = area;
    161        }
    162
    163        /**
    164         * Returns a flag that controls whether or not outlines of the areas are
    165         * drawn.
    166         *
    167         * @return The flag.
    168         *
    169         * @see #setOutline(boolean)
    170         */
    171        public boolean isOutline() {
    172            return this.showOutline;
    173        }
    174
    175        /**
    176         * Sets a flag that controls whether or not outlines of the areas are
    177         * drawn, and sends a {@link RendererChangeEvent} to all registered
    178         * listeners.
    179         *
    180         * @param show  the flag.
    181         *
    182         * @see #isOutline()
    183         */
    184        public void setOutline(boolean show) {
    185            this.showOutline = show;
    186            fireChangeEvent();
    187        }
    188
    189        /**
    190         * This method should not be used.
    191         *
    192         * @return <code>false</code> always.
    193         *
    194         * @deprecated This method was included in the API by mistake and serves
    195         *     no useful purpose.  It has always returned <code>false</code>.
    196         *
    197         */
    198        public boolean getPlotLines() {
    199            return false;
    200        }
    201
    202        /**
    203         * Returns the shape used to represent an area in the legend.
    204         *
    205         * @return The legend area (never <code>null</code>).
    206         *
    207         * @see #setLegendArea(Shape)
    208         */
    209        public Shape getLegendArea() {
    210            return this.legendArea;
    211        }
    212
    213        /**
    214         * Sets the shape used as an area in each legend item and sends a
    215         * {@link RendererChangeEvent} to all registered listeners.
    216         *
    217         * @param area  the area (<code>null</code> not permitted).
    218         *
    219         * @see #getLegendArea()
    220         */
    221        public void setLegendArea(Shape area) {
    222            if (area == null) {
    223                throw new IllegalArgumentException("Null 'area' argument.");
    224            }
    225            this.legendArea = area;
    226            fireChangeEvent();
    227        }
    228
    229        /**
    230         * Returns a default legend item for the specified series.  Subclasses
    231         * should override this method to generate customised items.
    232         *
    233         * @param datasetIndex  the dataset index (zero-based).
    234         * @param series  the series index (zero-based).
    235         *
    236         * @return A legend item for the series.
    237         */
    238        public LegendItem getLegendItem(int datasetIndex, int series) {
    239            LegendItem result = null;
    240            XYPlot xyplot = getPlot();
    241            if (xyplot != null) {
    242                XYDataset dataset = xyplot.getDataset(datasetIndex);
    243                if (dataset != null) {
    244                    XYSeriesLabelGenerator lg = getLegendItemLabelGenerator();
    245                    String label = lg.generateLabel(dataset, series);
    246                    String description = label;
    247                    String toolTipText = null;
    248                    if (getLegendItemToolTipGenerator() != null) {
    249                        toolTipText = getLegendItemToolTipGenerator().generateLabel(
    250                                dataset, series);
    251                    }
    252                    String urlText = null;
    253                    if (getLegendItemURLGenerator() != null) {
    254                        urlText = getLegendItemURLGenerator().generateLabel(
    255                                dataset, series);
    256                    }
    257                    Paint paint = lookupSeriesPaint(series);
    258                    result = new LegendItem(label, description, toolTipText,
    259                            urlText, this.legendArea, paint);
    260                    result.setDataset(dataset);
    261                    result.setDatasetIndex(datasetIndex);
    262                    result.setSeriesKey(dataset.getSeriesKey(series));
    263                    result.setSeriesIndex(series);
    264                }
    265            }
    266            return result;
    267        }
    268
    269        /**
    270         * Draws the visual representation of a single data item.
    271         *
    272         * @param g2  the graphics device.
    273         * @param state  the renderer state.
    274         * @param dataArea  the area within which the data is being drawn.
    275         * @param info  collects information about the drawing.
    276         * @param plot  the plot (can be used to obtain standard color
    277         *              information etc).
    278         * @param domainAxis  the domain axis.
    279         * @param rangeAxis  the range axis.
    280         * @param dataset  the dataset.
    281         * @param series  the series index (zero-based).
    282         * @param item  the item index (zero-based).
    283         * @param crosshairState  crosshair information for the plot
    284         *                        (<code>null</code> permitted).
    285         * @param pass  the pass index.
    286         */
    287        public void drawItem(Graphics2D g2,
    288                             XYItemRendererState state,
    289                             Rectangle2D dataArea,
    290                             PlotRenderingInfo info,
    291                             XYPlot plot,
    292                             ValueAxis domainAxis,
    293                             ValueAxis rangeAxis,
    294                             XYDataset dataset,
    295                             int series,
    296                             int item,
    297                             CrosshairState crosshairState,
    298                             int pass) {
    299
    300            if (!getItemVisible(series, item)) {
    301                return;
    302            }
    303            // get the data point...
    304            double x1 = dataset.getXValue(series, item);
    305            double y1 = dataset.getYValue(series, item);
    306            if (Double.isNaN(y1)) {
    307                y1 = 0.0;
    308            }
    309
    310            double transX1 = domainAxis.valueToJava2D(x1, dataArea,
    311                    plot.getDomainAxisEdge());
    312            double transY1 = rangeAxis.valueToJava2D(y1, dataArea,
    313                    plot.getRangeAxisEdge());
    314
    315            // get the previous point and the next point so we can calculate a
    316            // "hot spot" for the area (used by the chart entity)...
    317            double x0 = dataset.getXValue(series, Math.max(item - 1, 0));
    318            double y0 = dataset.getYValue(series, Math.max(item - 1, 0));
    319            if (Double.isNaN(y0)) {
    320                y0 = 0.0;
    321            }
    322            double transX0 = domainAxis.valueToJava2D(x0, dataArea,
    323                    plot.getDomainAxisEdge());
    324            double transY0 = rangeAxis.valueToJava2D(y0, dataArea,
    325                    plot.getRangeAxisEdge());
    326
    327            int itemCount = dataset.getItemCount(series);
    328            double x2 = dataset.getXValue(series, Math.min(item + 1,
    329                    itemCount - 1));
    330            double y2 = dataset.getYValue(series, Math.min(item + 1,
    331                    itemCount - 1));
    332            if (Double.isNaN(y2)) {
    333                y2 = 0.0;
    334            }
    335            double transX2 = domainAxis.valueToJava2D(x2, dataArea,
    336                    plot.getDomainAxisEdge());
    337            double transY2 = rangeAxis.valueToJava2D(y2, dataArea,
    338                    plot.getRangeAxisEdge());
    339
    340            double transZero = rangeAxis.valueToJava2D(0.0, dataArea,
    341                    plot.getRangeAxisEdge());
    342            Polygon hotspot = null;
    343            if (plot.getOrientation() == PlotOrientation.HORIZONTAL) {
    344                hotspot = new Polygon();
    345                hotspot.addPoint((int) transZero,
    346                        (int) ((transX0 + transX1) / 2.0));
    347                hotspot.addPoint((int) ((transY0 + transY1) / 2.0),
    348                        (int) ((transX0 + transX1) / 2.0));
    349                hotspot.addPoint((int) transY1, (int) transX1);
    350                hotspot.addPoint((int) ((transY1 + transY2) / 2.0),
    351                        (int) ((transX1 + transX2) / 2.0));
    352                hotspot.addPoint((int) transZero,
    353                        (int) ((transX1 + transX2) / 2.0));
    354            }
    355            else {  // vertical orientation
    356                hotspot = new Polygon();
    357                hotspot.addPoint((int) ((transX0 + transX1) / 2.0),
    358                        (int) transZero);
    359                hotspot.addPoint((int) ((transX0 + transX1) / 2.0),
    360                        (int) ((transY0 + transY1) / 2.0));
    361                hotspot.addPoint((int) transX1, (int) transY1);
    362                hotspot.addPoint((int) ((transX1 + transX2) / 2.0),
    363                        (int) ((transY1 + transY2) / 2.0));
    364                hotspot.addPoint((int) ((transX1 + transX2) / 2.0),
    365                        (int) transZero);
    366            }
    367
    368            PlotOrientation orientation = plot.getOrientation();
    369            Paint paint = getItemPaint(series, item);
    370            Stroke stroke = getItemStroke(series, item);
    371            g2.setPaint(paint);
    372            g2.setStroke(stroke);
    373
    374            if (getPlotLines()) {
    375                if (item > 0) {
    376                    if (plot.getOrientation() == PlotOrientation.VERTICAL) {
    377                        state.workingLine.setLine(transX0, transY0, transX1,
    378                                transY1);
    379                    }
    380                    else if (plot.getOrientation() == PlotOrientation.HORIZONTAL) {
    381                        state.workingLine.setLine(transY0, transX0, transY1,
    382                                transX1);
    383                    }
    384                    g2.draw(state.workingLine);
    385                }
    386            }
    387
    388            // Check if the item is the last item for the series.
    389            // and number of items > 0.  We can't draw an area for a single point.
    390            g2.fill(hotspot);
    391
    392            // draw an outline around the Area.
    393            if (isOutline()) {
    394                g2.setStroke(lookupSeriesOutlineStroke(series));
    395                g2.setPaint(lookupSeriesOutlinePaint(series));
    396                g2.draw(hotspot);
    397            }
    398            int domainAxisIndex = plot.getDomainAxisIndex(domainAxis);
    399            int rangeAxisIndex = plot.getRangeAxisIndex(rangeAxis);
    400            updateCrosshairValues(crosshairState, x1, y1, domainAxisIndex,
    401                    rangeAxisIndex, transX1, transY1, orientation);
    402
    403            // collect entity and tool tip information...
    404            if (state.getInfo() != null) {
    405                EntityCollection entities = state.getEntityCollection();
    406                if (entities != null && hotspot != null) {
    407                    String tip = null;
    408                    XYToolTipGenerator generator = getToolTipGenerator(
    409                        series, item
    410                    );
    411                    if (generator != null) {
    412                        tip = generator.generateToolTip(dataset, series, item);
    413                    }
    414                    String url = null;
    415                    if (getURLGenerator() != null) {
    416                        url = getURLGenerator().generateURL(dataset, series, item);
    417                    }
    418                    XYItemEntity entity = new XYItemEntity(hotspot, dataset,
    419                            series, item, tip, url);
    420                    entities.add(entity);
    421                }
    422            }
    423
    424        }
    425
    426        /**
    427         * Tests this renderer for equality with an arbitrary object.
    428         *
    429         * @param obj  the object (<code>null</code> not permitted).
    430         *
    431         * @return A boolean.
    432         */
    433        public boolean equals(Object obj) {
    434            if (obj == this) {
    435                return true;
    436            }
    437            if (!(obj instanceof XYAreaRenderer2)) {
    438                return false;
    439            }
    440            XYAreaRenderer2 that = (XYAreaRenderer2) obj;
    441            if (this.showOutline != that.showOutline) {
    442                return false;
    443            }
    444            if (!ShapeUtilities.equal(this.legendArea, that.legendArea)) {
    445                return false;
    446            }
    447            return super.equals(obj);
    448        }
    449
    450        /**
    451         * Returns a clone of the renderer.
    452         *
    453         * @return A clone.
    454         *
    455         * @throws CloneNotSupportedException  if the renderer cannot be cloned.
    456         */
    457        public Object clone() throws CloneNotSupportedException {
    458            XYAreaRenderer2 clone = (XYAreaRenderer2) super.clone();
    459            clone.legendArea = ShapeUtilities.clone(this.legendArea);
    460            return clone;
    461        }
    462
    463        /**
    464         * Provides serialization support.
    465         *
    466         * @param stream  the input stream.
    467         *
    468         * @throws IOException  if there is an I/O error.
    469         * @throws ClassNotFoundException  if there is a classpath problem.
    470         */
    471        private void readObject(ObjectInputStream stream)
    472                throws IOException, ClassNotFoundException {
    473            stream.defaultReadObject();
    474            this.legendArea = SerialUtilities.readShape(stream);
    475        }
    476
    477        /**
    478         * Provides serialization support.
    479         *
    480         * @param stream  the output stream.
    481         *
    482         * @throws IOException  if there is an I/O error.
    483         */
    484        private void writeObject(ObjectOutputStream stream) throws IOException {
    485            stream.defaultWriteObject();
    486            SerialUtilities.writeShape(this.legendArea, stream);
    487        }
    488
    489    }
    490