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
053package org.jfree.chart.renderer.xy;
054
055import java.awt.Graphics2D;
056import java.awt.Paint;
057import java.awt.Shape;
058import java.awt.geom.Rectangle2D;
059import java.io.IOException;
060import java.io.ObjectInputStream;
061import java.io.ObjectOutputStream;
062import java.io.Serializable;
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 */
080public 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        notifyListeners(new RendererChangeEvent(this));
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        notifyListeners(new RendererChangeEvent(this));
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        notifyListeners(new RendererChangeEvent(this));
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}