    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     * XYDotRenderer.java
    029     * ------------------
    030     * (C) Copyright 2002-2008, by Object Refinery Limited.
    031     *
    032     * Original Author:  David Gilbert (for Object Refinery Limited);
    033     * Contributor(s):   Christian W. Zuckschwerdt;
    034     *
    035     * Changes (from 29-Oct-2002)
    036     * --------------------------
    037     * 29-Oct-2002 : Added standard header (DG);
    038     * 25-Mar-2003 : Implemented Serializable (DG);
    039     * 01-May-2003 : Modified drawItem() method signature (DG);
    040     * 30-Jul-2003 : Modified entity constructor (CZ);
    041     * 20-Aug-2003 : Implemented Cloneable and PublicCloneable (DG);
    042     * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG);
    043     * 25-Feb-2004 : Replaced CrosshairInfo with CrosshairState (DG);
    044     * 19-Jan-2005 : Now uses only primitives from dataset (DG);
    045     * ------------- JFREECHART 1.0.x ---------------------------------------------
    046     * 10-Jul-2006 : Added dotWidth and dotHeight attributes (DG);
    047     * 06-Feb-2007 : Fixed bug 1086307, crosshairs with multiple axes (DG);
    048     * 09-Nov-2007 : Added legend shape attribute, plus override for
    049     *               getLegendItem() (DG);
    050     * 17-Jun-2008 : Apply legend shape, font and paint attributes (DG);
    051     *
    052     */
    053
    054    package org.jfree.chart.renderer.xy;
    055
    056    import java.awt.Graphics2D;
    057    import java.awt.Paint;
    058    import java.awt.Shape;
    059    import java.awt.geom.Rectangle2D;
    060    import java.io.IOException;
    061    import java.io.ObjectInputStream;
    062    import java.io.ObjectOutputStream;
    063
    064    import org.jfree.chart.LegendItem;
    065    import org.jfree.chart.axis.ValueAxis;
    066    import org.jfree.chart.event.RendererChangeEvent;
    067    import org.jfree.chart.plot.CrosshairState;
    068    import org.jfree.chart.plot.PlotOrientation;
    069    import org.jfree.chart.plot.PlotRenderingInfo;
    070    import org.jfree.chart.plot.XYPlot;
    071    import org.jfree.data.xy.XYDataset;
    072    import org.jfree.io.SerialUtilities;
    073    import org.jfree.ui.RectangleEdge;
    074    import org.jfree.util.PublicCloneable;
    075    import org.jfree.util.ShapeUtilities;
    076
    077    /**
    078     * A renderer that draws a small dot at each data point for an {@link XYPlot}.
    079     * The example shown here is generated by the
    080     * <code>ScatterPlotDemo4.java</code> program included in the JFreeChart
    081     * demo collection:
    082     * <br><br>
    083     * <img src="../../../../../images/XYDotRendererSample.png"
    084     * alt="XYDotRendererSample.png" />
    085     */
    086    public class XYDotRenderer extends AbstractXYItemRenderer
    087            implements XYItemRenderer, PublicCloneable {
    088
    089        /** For serialization. */
    090        private static final long serialVersionUID = -2764344339073566425L;
    091
    092        /** The dot width. */
    093        private int dotWidth;
    094
    095        /** The dot height. */
    096        private int dotHeight;
    097
    098        /**
    099         * The shape that is used to represent an item in the legend.
    100         *
    101         * @since 1.0.7
    102         */
    103        private transient Shape legendShape;
    104
    105        /**
    106         * Constructs a new renderer.
    107         */
    108        public XYDotRenderer() {
    109            super();
    110            this.dotWidth = 1;
    111            this.dotHeight = 1;
    112            this.legendShape = new Rectangle2D.Double(-3.0, -3.0, 6.0, 6.0);
    113        }
    114
    115        /**
    116         * Returns the dot width (the default value is 1).
    117         *
    118         * @return The dot width.
    119         *
    120         * @since 1.0.2
    121         * @see #setDotWidth(int)
    122         */
    123        public int getDotWidth() {
    124            return this.dotWidth;
    125        }
    126
    127        /**
    128         * Sets the dot width and sends a {@link RendererChangeEvent} to all
    129         * registered listeners.
    130         *
    131         * @param w  the new width (must be greater than zero).
    132         *
    133         * @throws IllegalArgumentException if <code>w</code> is less than one.
    134         *
    135         * @since 1.0.2
    136         * @see #getDotWidth()
    137         */
    138        public void setDotWidth(int w) {
    139            if (w < 1) {
    140                throw new IllegalArgumentException("Requires w > 0.");
    141            }
    142            this.dotWidth = w;
    143            fireChangeEvent();
    144        }
    145
    146        /**
    147         * Returns the dot height (the default value is 1).
    148         *
    149         * @return The dot height.
    150         *
    151         * @since 1.0.2
    152         * @see #setDotHeight(int)
    153         */
    154        public int getDotHeight() {
    155            return this.dotHeight;
    156        }
    157
    158        /**
    159         * Sets the dot height and sends a {@link RendererChangeEvent} to all
    160         * registered listeners.
    161         *
    162         * @param h  the new height (must be greater than zero).
    163         *
    164         * @throws IllegalArgumentException if <code>h</code> is less than one.
    165         *
    166         * @since 1.0.2
    167         * @see #getDotHeight()
    168         */
    169        public void setDotHeight(int h) {
    170            if (h < 1) {
    171                throw new IllegalArgumentException("Requires h > 0.");
    172            }
    173            this.dotHeight = h;
    174            fireChangeEvent();
    175        }
    176
    177        /**
    178         * Returns the shape used to represent an item in the legend.
    179         *
    180         * @return The legend shape (never <code>null</code>).
    181         *
    182         * @see #setLegendShape(Shape)
    183         *
    184         * @since 1.0.7
    185         */
    186        public Shape getLegendShape() {
    187            return this.legendShape;
    188        }
    189
    190        /**
    191         * Sets the shape used as a line in each legend item and sends a
    192         * {@link RendererChangeEvent} to all registered listeners.
    193         *
    194         * @param shape  the shape (<code>null</code> not permitted).
    195         *
    196         * @see #getLegendShape()
    197         *
    198         * @since 1.0.7
    199         */
    200        public void setLegendShape(Shape shape) {
    201            if (shape == null) {
    202                throw new IllegalArgumentException("Null 'shape' argument.");
    203            }
    204            this.legendShape = shape;
    205            fireChangeEvent();
    206        }
    207
    208        /**
    209         * Draws the visual representation of a single data item.
    210         *
    211         * @param g2  the graphics device.
    212         * @param state  the renderer state.
    213         * @param dataArea  the area within which the data is being drawn.
    214         * @param info  collects information about the drawing.
    215         * @param plot  the plot (can be used to obtain standard color
    216         *              information etc).
    217         * @param domainAxis  the domain (horizontal) axis.
    218         * @param rangeAxis  the range (vertical) axis.
    219         * @param dataset  the dataset.
    220         * @param series  the series index (zero-based).
    221         * @param item  the item index (zero-based).
    222         * @param crosshairState  crosshair information for the plot
    223         *                        (<code>null</code> permitted).
    224         * @param pass  the pass index.
    225         */
    226        public void drawItem(Graphics2D g2,
    227                             XYItemRendererState state,
    228                             Rectangle2D dataArea,
    229                             PlotRenderingInfo info,
    230                             XYPlot plot,
    231                             ValueAxis domainAxis,
    232                             ValueAxis rangeAxis,
    233                             XYDataset dataset,
    234                             int series,
    235                             int item,
    236                             CrosshairState crosshairState,
    237                             int pass) {
    238
    239            // do nothing if item is not visible
    240            if (!getItemVisible(series, item)) {
    241                return;
    242            }
    243
    244            // get the data point...
    245            double x = dataset.getXValue(series, item);
    246            double y = dataset.getYValue(series, item);
    247            double adjx = (this.dotWidth - 1) / 2.0;
    248            double adjy = (this.dotHeight - 1) / 2.0;
    249            if (!Double.isNaN(y)) {
    250                RectangleEdge xAxisLocation = plot.getDomainAxisEdge();
    251                RectangleEdge yAxisLocation = plot.getRangeAxisEdge();
    252                double transX = domainAxis.valueToJava2D(x, dataArea,
    253                        xAxisLocation) - adjx;
    254                double transY = rangeAxis.valueToJava2D(y, dataArea, yAxisLocation)
    255                        - adjy;
    256
    257                g2.setPaint(getItemPaint(series, item));
    258                PlotOrientation orientation = plot.getOrientation();
    259                if (orientation == PlotOrientation.HORIZONTAL) {
    260                    g2.fillRect((int) transY, (int) transX, this.dotHeight,
    261                            this.dotWidth);
    262                }
    263                else if (orientation == PlotOrientation.VERTICAL) {
    264                    g2.fillRect((int) transX, (int) transY, this.dotWidth,
    265                            this.dotHeight);
    266                }
    267
    268                int domainAxisIndex = plot.getDomainAxisIndex(domainAxis);
    269                int rangeAxisIndex = plot.getRangeAxisIndex(rangeAxis);
    270                updateCrosshairValues(crosshairState, x, y, domainAxisIndex,
    271                        rangeAxisIndex, transX, transY, orientation);
    272            }
    273
    274        }
    275
    276        /**
    277         * Returns a legend item for the specified series.
    278         *
    279         * @param datasetIndex  the dataset index (zero-based).
    280         * @param series  the series index (zero-based).
    281         *
    282         * @return A legend item for the series (possibly <code>null</code>).
    283         */
    284        public LegendItem getLegendItem(int datasetIndex, int series) {
    285
    286            // if the renderer isn't assigned to a plot, then we don't have a
    287            // dataset...
    288            XYPlot plot = getPlot();
    289            if (plot == null) {
    290                return null;
    291            }
    292
    293            XYDataset dataset = plot.getDataset(datasetIndex);
    294            if (dataset == null) {
    295                return null;
    296            }
    297
    298            LegendItem result = null;
    299            if (getItemVisible(series, 0)) {
    300                String label = getLegendItemLabelGenerator().generateLabel(dataset,
    301                        series);
    302                String description = label;
    303                String toolTipText = null;
    304                if (getLegendItemToolTipGenerator() != null) {
    305                    toolTipText = getLegendItemToolTipGenerator().generateLabel(
    306                            dataset, series);
    307                }
    308                String urlText = null;
    309                if (getLegendItemURLGenerator() != null) {
    310                    urlText = getLegendItemURLGenerator().generateLabel(
    311                            dataset, series);
    312                }
    313                Paint fillPaint = lookupSeriesPaint(series);
    314                result = new LegendItem(label, description, toolTipText, urlText,
    315                        getLegendShape(), fillPaint);
    316                result.setLabelFont(lookupLegendTextFont(series));
    317                Paint labelPaint = lookupLegendTextPaint(series);
    318                if (labelPaint != null) {
    319                    result.setLabelPaint(labelPaint);
    320                }
    321                result.setSeriesKey(dataset.getSeriesKey(series));
    322                result.setSeriesIndex(series);
    323                result.setDataset(dataset);
    324                result.setDatasetIndex(datasetIndex);
    325            }
    326
    327            return result;
    328
    329        }
    330
    331        /**
    332         * Tests this renderer for equality with an arbitrary object.  This method
    333         * returns <code>true</code> if and only if:
    334         *
    335         * <ul>
    336         * <li><code>obj</code> is not <code>null</code>;</li>
    337         * <li><code>obj</code> is an instance of <code>XYDotRenderer</code>;</li>
    338         * <li>both renderers have the same attribute values.
    339         * </ul>
    340         *
    341         * @param obj  the object (<code>null</code> permitted).
    342         *
    343         * @return A boolean.
    344         */
    345        public boolean equals(Object obj) {
    346            if (obj == this) {
    347                return true;
    348            }
    349            if (!(obj instanceof XYDotRenderer)) {
    350                return false;
    351            }
    352            XYDotRenderer that = (XYDotRenderer) obj;
    353            if (this.dotWidth != that.dotWidth) {
    354                return false;
    355            }
    356            if (this.dotHeight != that.dotHeight) {
    357                return false;
    358            }
    359            if (!ShapeUtilities.equal(this.legendShape, that.legendShape)) {
    360                return false;
    361            }
    362            return super.equals(obj);
    363        }
    364
    365        /**
    366         * Returns a clone of the renderer.
    367         *
    368         * @return A clone.
    369         *
    370         * @throws CloneNotSupportedException  if the renderer cannot be cloned.
    371         */
    372        public Object clone() throws CloneNotSupportedException {
    373            return super.clone();
    374        }
    375
    376        /**
    377         * Provides serialization support.
    378         *
    379         * @param stream  the input stream.
    380         *
    381         * @throws IOException  if there is an I/O error.
    382         * @throws ClassNotFoundException  if there is a classpath problem.
    383         */
    384        private void readObject(ObjectInputStream stream)
    385                throws IOException, ClassNotFoundException {
    386            stream.defaultReadObject();
    387            this.legendShape = SerialUtilities.readShape(stream);
    388        }
    389
    390        /**
    391         * Provides serialization support.
    392         *
    393         * @param stream  the output stream.
    394         *
    395         * @throws IOException  if there is an I/O error.
    396         */
    397        private void writeObject(ObjectOutputStream stream) throws IOException {
    398            stream.defaultWriteObject();
    399            SerialUtilities.writeShape(this.legendShape, stream);
    400        }
    401
    402    }