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
054package org.jfree.chart.renderer.xy;
055
056import java.awt.Graphics2D;
057import java.awt.Paint;
058import java.awt.Shape;
059import java.awt.geom.Rectangle2D;
060import java.io.IOException;
061import java.io.ObjectInputStream;
062import java.io.ObjectOutputStream;
063
064import org.jfree.chart.LegendItem;
065import org.jfree.chart.axis.ValueAxis;
066import org.jfree.chart.event.RendererChangeEvent;
067import org.jfree.chart.plot.CrosshairState;
068import org.jfree.chart.plot.PlotOrientation;
069import org.jfree.chart.plot.PlotRenderingInfo;
070import org.jfree.chart.plot.XYPlot;
071import org.jfree.data.xy.XYDataset;
072import org.jfree.io.SerialUtilities;
073import org.jfree.ui.RectangleEdge;
074import org.jfree.util.PublicCloneable;
075import 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 */
086public 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}