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