    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     * XYShapeRenderer.java
    029     * --------------------
    030     * (C) Copyright 2008-2011 by Andreas Haumer, xS+S and Contributors.
    031     *
    032     * Original Author:  Martin Hoeller (x Software + Systeme  xS+S - Andreas
    033     *                       Haumer);
    034     * Contributor(s):   David Gilbert (for Object Refinery Limited);
    035     *
    036     * Changes:
    037     * --------
    038     * 17-Sep-2008 : Version 1, based on a contribution from Martin Hoeller with
    039     *               amendments by David Gilbert (DG);
    040     * 16-Feb-2010 : Added findZBounds() (patch 2952086) (MH);
    041     * 19-Oct-2011 : Fixed NPE in findRangeBounds() (bug 3026341) (DG);
    042     *
    043     */
    044
    045    package org.jfree.chart.renderer.xy;
    046
    047    import java.awt.BasicStroke;
    048    import java.awt.Color;
    049    import java.awt.Graphics2D;
    050    import java.awt.Paint;
    051    import java.awt.Shape;
    052    import java.awt.Stroke;
    053    import java.awt.geom.Ellipse2D;
    054    import java.awt.geom.Line2D;
    055    import java.awt.geom.Rectangle2D;
    056    import java.io.IOException;
    057    import java.io.ObjectInputStream;
    058    import java.io.ObjectOutputStream;
    059    import java.io.Serializable;
    060
    061    import org.jfree.chart.axis.ValueAxis;
    062    import org.jfree.chart.entity.EntityCollection;
    063    import org.jfree.chart.event.RendererChangeEvent;
    064    import org.jfree.chart.plot.CrosshairState;
    065    import org.jfree.chart.plot.PlotOrientation;
    066    import org.jfree.chart.plot.PlotRenderingInfo;
    067    import org.jfree.chart.plot.XYPlot;
    068    import org.jfree.chart.renderer.LookupPaintScale;
    069    import org.jfree.chart.renderer.PaintScale;
    070    import org.jfree.data.Range;
    071    import org.jfree.data.general.DatasetUtilities;
    072    import org.jfree.data.xy.XYDataset;
    073    import org.jfree.data.xy.XYZDataset;
    074    import org.jfree.io.SerialUtilities;
    075    import org.jfree.util.PublicCloneable;
    076    import org.jfree.util.ShapeUtilities;
    077
    078    /**
    079     * A renderer that draws shapes at (x, y) coordinates and, if the dataset
    080     * is an instance of {@link XYZDataset}, fills the shapes with a paint that
    081     * is based on the z-value (the paint is obtained from a lookup table).  The
    082     * renderer also allows for optional guidelines, horizontal and vertical lines
    083     * connecting the shape to the edges of the plot.
    084     * <br><br>
    085     * The example shown here is generated by the
    086     * <code>XYShapeRendererDemo1.java</code> program included in the JFreeChart
    087     * demo collection:
    088     * <br><br>
    089     * <img src="../../../../../images/XYShapeRendererSample.png"
    090     * alt="XYShapeRendererSample.png" />
    091     * <br><br>
    092     * This renderer has similarities to, but also differences from, the
    093     * {@link XYLineAndShapeRenderer}.
    094     *
    095     * @since 1.0.11
    096     */
    097    public class XYShapeRenderer extends AbstractXYItemRenderer
    098            implements XYItemRenderer, Cloneable, Serializable {
    099
    100        /** Auto generated serial version id. */
    101        private static final long serialVersionUID = 8320552104211173221L;
    102
    103        /** The paint scale (never null). */
    104        private PaintScale paintScale;
    105
    106        /** A flag that controls whether or not the shape outlines are drawn. */
    107        private boolean drawOutlines;
    108
    109        /**
    110         * A flag that controls whether or not the outline paint is used (if not,
    111         * the regular paint is used).
    112         */
    113        private boolean useOutlinePaint;
    114
    115        /**
    116         * A flag that controls whether or not the fill paint is used (if not,
    117         * the fill paint is used).
    118         */
    119        private boolean useFillPaint;
    120
    121        /** Flag indicating if guide lines should be drawn for every item. */
    122        private boolean guideLinesVisible;
    123
    124        /** The paint used for drawing the guide lines (never null). */
    125        private transient Paint guideLinePaint;
    126
    127        /** The stroke used for drawing the guide lines (never null). */
    128        private transient Stroke guideLineStroke;
    129
    130        /**
    131         * Creates a new <code>XYShapeRenderer</code> instance with default
    132         * attributes.
    133         */
    134        public XYShapeRenderer() {
    135            this.paintScale = new LookupPaintScale();
    136            this.useFillPaint = false;
    137            this.drawOutlines = false;
    138            this.useOutlinePaint = true;
    139            this.guideLinesVisible = false;
    140            this.guideLinePaint = Color.darkGray;
    141            this.guideLineStroke = new BasicStroke();
    142            setBaseShape(new Ellipse2D.Double(-5.0, -5.0, 10.0, 10.0));
    143            setAutoPopulateSeriesShape(false);
    144        }
    145
    146        /**
    147         * Returns the paint scale used by the renderer.
    148         *
    149         * @return The paint scale (never <code>null</code>).
    150         *
    151         * @see #setPaintScale(PaintScale)
    152         */
    153        public PaintScale getPaintScale() {
    154            return this.paintScale;
    155        }
    156
    157        /**
    158         * Sets the paint scale used by the renderer and sends a
    159         * {@link RendererChangeEvent} to all registered listeners.
    160         *
    161         * @param scale  the scale (<code>null</code> not permitted).
    162         *
    163         * @see #getPaintScale()
    164         */
    165        public void setPaintScale(PaintScale scale) {
    166            if (scale == null) {
    167                throw new IllegalArgumentException("Null 'scale' argument.");
    168            }
    169            this.paintScale = scale;
    170            notifyListeners(new RendererChangeEvent(this));
    171        }
    172
    173        /**
    174         * Returns <code>true</code> if outlines should be drawn for shapes, and
    175         * <code>false</code> otherwise.
    176         *
    177         * @return A boolean.
    178         *
    179         * @see #setDrawOutlines(boolean)
    180         */
    181        public boolean getDrawOutlines() {
    182            return this.drawOutlines;
    183        }
    184
    185        /**
    186         * Sets the flag that controls whether outlines are drawn for
    187         * shapes, and sends a {@link RendererChangeEvent} to all registered
    188         * listeners.
    189         * <P>
    190         * In some cases, shapes look better if they do NOT have an outline, but
    191         * this flag allows you to set your own preference.
    192         *
    193         * @param flag  the flag.
    194         *
    195         * @see #getDrawOutlines()
    196         */
    197        public void setDrawOutlines(boolean flag) {
    198            this.drawOutlines = flag;
    199            fireChangeEvent();
    200        }
    201
    202        /**
    203         * Returns <code>true</code> if the renderer should use the fill paint
    204         * setting to fill shapes, and <code>false</code> if it should just
    205         * use the regular paint.
    206         * <p>
    207         * Refer to <code>XYLineAndShapeRendererDemo2.java</code> to see the
    208         * effect of this flag.
    209         *
    210         * @return A boolean.
    211         *
    212         * @see #setUseFillPaint(boolean)
    213         * @see #getUseOutlinePaint()
    214         */
    215        public boolean getUseFillPaint() {
    216            return this.useFillPaint;
    217        }
    218
    219        /**
    220         * Sets the flag that controls whether the fill paint is used to fill
    221         * shapes, and sends a {@link RendererChangeEvent} to all
    222         * registered listeners.
    223         *
    224         * @param flag  the flag.
    225         *
    226         * @see #getUseFillPaint()
    227         */
    228        public void setUseFillPaint(boolean flag) {
    229            this.useFillPaint = flag;
    230            fireChangeEvent();
    231        }
    232
    233        /**
    234         * Returns the flag that controls whether the outline paint is used for
    235         * shape outlines.  If not, the regular series paint is used.
    236         *
    237         * @return A boolean.
    238         *
    239         * @see #setUseOutlinePaint(boolean)
    240         */
    241        public boolean getUseOutlinePaint() {
    242            return this.useOutlinePaint;
    243        }
    244
    245        /**
    246         * Sets the flag that controls whether the outline paint is used for shape
    247         * outlines, and sends a {@link RendererChangeEvent} to all registered
    248         * listeners.
    249         *
    250         * @param use  the flag.
    251         *
    252         * @see #getUseOutlinePaint()
    253         */
    254        public void setUseOutlinePaint(boolean use) {
    255            this.useOutlinePaint = use;
    256            fireChangeEvent();
    257        }
    258
    259        /**
    260         * Returns a flag that controls whether or not guide lines are drawn for
    261         * each data item (the lines are horizontal and vertical "crosshairs"
    262         * linking the data point to the axes).
    263         *
    264         * @return A boolean.
    265         *
    266         * @see #setGuideLinesVisible(boolean)
    267         */
    268        public boolean isGuideLinesVisible() {
    269            return this.guideLinesVisible;
    270        }
    271
    272        /**
    273         * Sets the flag that controls whether or not guide lines are drawn for
    274         * each data item and sends a {@link RendererChangeEvent} to all registered
    275         * listeners.
    276         *
    277         * @param visible  the new flag value.
    278         *
    279         * @see #isGuideLinesVisible()
    280         */
    281        public void setGuideLinesVisible(boolean visible) {
    282            this.guideLinesVisible = visible;
    283            fireChangeEvent();
    284        }
    285
    286        /**
    287         * Returns the paint used to draw the guide lines.
    288         *
    289         * @return The paint (never <code>null</code>).
    290         *
    291         * @see #setGuideLinePaint(Paint)
    292         */
    293        public Paint getGuideLinePaint() {
    294            return this.guideLinePaint;
    295        }
    296
    297        /**
    298         * Sets the paint used to draw the guide lines and sends a
    299         * {@link RendererChangeEvent} to all registered listeners.
    300         *
    301         * @param paint  the paint (<code>null</code> not permitted).
    302         *
    303         * @see #getGuideLinePaint()
    304         */
    305        public void setGuideLinePaint(Paint paint) {
    306            if (paint == null) {
    307                throw new IllegalArgumentException("Null 'paint' argument.");
    308            }
    309            this.guideLinePaint = paint;
    310            fireChangeEvent();
    311        }
    312
    313        /**
    314         * Returns the stroke used to draw the guide lines.
    315         *
    316         * @return The stroke.
    317         *
    318         * @see #setGuideLineStroke(Stroke)
    319         */
    320        public Stroke getGuideLineStroke() {
    321            return this.guideLineStroke;
    322        }
    323
    324        /**
    325         * Sets the stroke used to draw the guide lines and sends a
    326         * {@link RendererChangeEvent} to all registered listeners.
    327         *
    328         * @param stroke  the stroke (<code>null</code> not permitted).
    329         *
    330         * @see #getGuideLineStroke()
    331         */
    332        public void setGuideLineStroke(Stroke stroke) {
    333            if (stroke == null) {
    334                throw new IllegalArgumentException("Null 'stroke' argument.");
    335            }
    336            this.guideLineStroke = stroke;
    337            fireChangeEvent();
    338        }
    339
    340        /**
    341         * Returns the lower and upper bounds (range) of the x-values in the
    342         * specified dataset.
    343         *
    344         * @param dataset  the dataset (<code>null</code> permitted).
    345         *
    346         * @return The range (<code>null</code> if the dataset is <code>null</code>
    347         *         or empty).
    348         */
    349        public Range findDomainBounds(XYDataset dataset) {
    350            if (dataset == null) {
    351                return null;
    352            }
    353            Range r = DatasetUtilities.findDomainBounds(dataset, false);
    354            if (r == null) {
    355                return null;
    356            }
    357            double offset = 0; // TODO getSeriesShape(n).getBounds().width / 2;
    358            return new Range(r.getLowerBound() + offset,
    359                             r.getUpperBound() + offset);
    360        }
    361
    362        /**
    363         * Returns the range of values the renderer requires to display all the
    364         * items from the specified dataset.
    365         *
    366         * @param dataset  the dataset (<code>null</code> permitted).
    367         *
    368         * @return The range (<code>null</code> if the dataset is <code>null</code>
    369         *         or empty).
    370         */
    371        public Range findRangeBounds(XYDataset dataset) {
    372            if (dataset == null) {
    373                return null;
    374            }
    375            Range r = DatasetUtilities.findRangeBounds(dataset, false);
    376            if (r == null) {
    377                return null;
    378            }
    379            double offset = 0; // TODO getSeriesShape(n).getBounds().height / 2;
    380            return new Range(r.getLowerBound() + offset, r.getUpperBound()
    381                    + offset);
    382        }
    383
    384        /**
    385         * Return the range of z-values in the specified dataset.
    386         *
    387         * @param dataset  the dataset (<code>null</code> permitted).
    388         *
    389         * @return The range (<code>null</code> if the dataset is <code>null</code>
    390         *         or empty).
    391         */
    392        public Range findZBounds(XYZDataset dataset) {
    393            if (dataset != null) {
    394                return DatasetUtilities.findZBounds(dataset);
    395            }
    396            else {
    397                return null;
    398            }
    399        }
    400
    401        /**
    402         * Returns the number of passes required by this renderer.
    403         *
    404         * @return <code>2</code>.
    405         */
    406        public int getPassCount() {
    407            return 2;
    408        }
    409
    410        /**
    411         * Draws the block representing the specified item.
    412         *
    413         * @param g2  the graphics device.
    414         * @param state  the state.
    415         * @param dataArea  the data area.
    416         * @param info  the plot rendering info.
    417         * @param plot  the plot.
    418         * @param domainAxis  the x-axis.
    419         * @param rangeAxis  the y-axis.
    420         * @param dataset  the dataset.
    421         * @param series  the series index.
    422         * @param item  the item index.
    423         * @param crosshairState  the crosshair state.
    424         * @param pass  the pass index.
    425         */
    426        public void drawItem(Graphics2D g2, XYItemRendererState state,
    427                Rectangle2D dataArea, PlotRenderingInfo info, XYPlot plot,
    428                ValueAxis domainAxis, ValueAxis rangeAxis, XYDataset dataset,
    429                int series, int item, CrosshairState crosshairState, int pass) {
    430
    431            Shape hotspot = null;
    432            EntityCollection entities = null;
    433            if (info != null) {
    434                entities = info.getOwner().getEntityCollection();
    435            }
    436
    437            double x = dataset.getXValue(series, item);
    438            double y = dataset.getYValue(series, item);
    439            if (Double.isNaN(x) || Double.isNaN(y)) {
    440                // can't draw anything
    441                return;
    442            }
    443
    444            double transX = domainAxis.valueToJava2D(x, dataArea,
    445                    plot.getDomainAxisEdge());
    446            double transY = rangeAxis.valueToJava2D(y, dataArea,
    447                    plot.getRangeAxisEdge());
    448
    449            PlotOrientation orientation = plot.getOrientation();
    450
    451            // draw optional guide lines
    452            if ((pass == 0) && this.guideLinesVisible) {
    453                g2.setStroke(this.guideLineStroke);
    454                g2.setPaint(this.guideLinePaint);
    455                if (orientation == PlotOrientation.HORIZONTAL) {
    456                    g2.draw(new Line2D.Double(transY, dataArea.getMinY(), transY,
    457                            dataArea.getMaxY()));
    458                    g2.draw(new Line2D.Double(dataArea.getMinX(), transX,
    459                            dataArea.getMaxX(), transX));
    460                }
    461                else {
    462                    g2.draw(new Line2D.Double(transX, dataArea.getMinY(), transX,
    463                            dataArea.getMaxY()));
    464                    g2.draw(new Line2D.Double(dataArea.getMinX(), transY,
    465                            dataArea.getMaxX(), transY));
    466                }
    467            }
    468            else if (pass == 1) {
    469                Shape shape = getItemShape(series, item);
    470                if (orientation == PlotOrientation.HORIZONTAL) {
    471                    shape = ShapeUtilities.createTranslatedShape(shape, transY,
    472                            transX);
    473                }
    474                else if (orientation == PlotOrientation.VERTICAL) {
    475                    shape = ShapeUtilities.createTranslatedShape(shape, transX,
    476                            transY);
    477                }
    478                hotspot = shape;
    479                if (shape.intersects(dataArea)) {
    480                    //if (getItemShapeFilled(series, item)) {
    481                        g2.setPaint(getPaint(dataset, series, item));
    482                        g2.fill(shape);
    483                   //}
    484                    if (this.drawOutlines) {
    485                        if (getUseOutlinePaint()) {
    486                            g2.setPaint(getItemOutlinePaint(series, item));
    487                        }
    488                        else {
    489                            g2.setPaint(getItemPaint(series, item));
    490                        }
    491                        g2.setStroke(getItemOutlineStroke(series, item));
    492                        g2.draw(shape);
    493                    }
    494                }
    495
    496                // add an entity for the item...
    497                if (entities != null) {
    498                    addEntity(entities, hotspot, dataset, series, item, transX,
    499                            transY);
    500                }
    501            }
    502        }
    503
    504        /**
    505         * Get the paint for a given series and item from a dataset.
    506         *
    507         * @param dataset  the dataset..
    508         * @param series  the series index.
    509         * @param item  the item index.
    510         *
    511         * @return The paint.
    512         */
    513        protected Paint getPaint(XYDataset dataset, int series, int item) {
    514            Paint p = null;
    515            if (dataset instanceof XYZDataset) {
    516                double z = ((XYZDataset) dataset).getZValue(series, item);
    517                p = this.paintScale.getPaint(z);
    518            }
    519            else {
    520                if (this.useFillPaint) {
    521                    p = getItemFillPaint(series, item);
    522                }
    523                else {
    524                    p = getItemPaint(series, item);
    525                }
    526            }
    527            return p;
    528        }
    529
    530        /**
    531         * Tests this instance for equality with an arbitrary object.  This method
    532         * returns <code>true</code> if and only if:
    533         * <ul>
    534         * <li><code>obj</code> is an instance of <code>XYShapeRenderer</code> (not
    535         *     <code>null</code>);</li>
    536         * <li><code>obj</code> has the same field values as this
    537         *     <code>XYShapeRenderer</code>;</li>
    538         * </ul>
    539         *
    540         * @param obj  the object (<code>null</code> permitted).
    541         *
    542         * @return A boolean.
    543         */
    544        public boolean equals(Object obj) {
    545            if (obj == this) {
    546                return true;
    547            }
    548            if (!(obj instanceof XYShapeRenderer)) {
    549                return false;
    550            }
    551            XYShapeRenderer that = (XYShapeRenderer) obj;
    552            if (!this.paintScale.equals(that.paintScale)) {
    553                return false;
    554            }
    555            if (this.drawOutlines != that.drawOutlines) {
    556                return false;
    557            }
    558            if (this.useOutlinePaint != that.useOutlinePaint) {
    559                return false;
    560            }
    561            if (this.useFillPaint != that.useFillPaint) {
    562                return false;
    563            }
    564            if (this.guideLinesVisible != that.guideLinesVisible) {
    565                return false;
    566            }
    567            if (!this.guideLinePaint.equals(that.guideLinePaint)) {
    568                return false;
    569            }
    570            if (!this.guideLineStroke.equals(that.guideLineStroke)) {
    571                return false;
    572            }
    573            return super.equals(obj);
    574        }
    575
    576        /**
    577         * Returns a clone of this renderer.
    578         *
    579         * @return A clone of this renderer.
    580         *
    581         * @throws CloneNotSupportedException if there is a problem creating the
    582         *     clone.
    583         */
    584        public Object clone() throws CloneNotSupportedException {
    585            XYShapeRenderer clone = (XYShapeRenderer) super.clone();
    586            if (this.paintScale instanceof PublicCloneable) {
    587                PublicCloneable pc = (PublicCloneable) this.paintScale;
    588                clone.paintScale = (PaintScale) pc.clone();
    589            }
    590            return clone;
    591        }
    592
    593        /**
    594         * Provides serialization support.
    595         *
    596         * @param stream  the input stream.
    597         *
    598         * @throws IOException  if there is an I/O error.
    599         * @throws ClassNotFoundException  if there is a classpath problem.
    600         */
    601        private void readObject(ObjectInputStream stream)
    602                throws IOException, ClassNotFoundException {
    603            stream.defaultReadObject();
    604            this.guideLinePaint = SerialUtilities.readPaint(stream);
    605            this.guideLineStroke = SerialUtilities.readStroke(stream);
    606        }
    607
    608        /**
    609         * Provides serialization support.
    610         *
    611         * @param stream  the output stream.
    612         *
    613         * @throws IOException  if there is an I/O error.
    614         */
    615        private void writeObject(ObjectOutputStream stream) throws IOException {
    616            stream.defaultWriteObject();
    617            SerialUtilities.writePaint(this.guideLinePaint, stream);
    618            SerialUtilities.writeStroke(this.guideLineStroke, stream);
    619        }
    620
    621    }