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