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