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