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