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    
053    package org.jfree.chart.renderer.xy;
054    
055    import java.awt.Graphics2D;
056    import java.awt.Paint;
057    import java.awt.Shape;
058    import java.awt.geom.Rectangle2D;
059    import java.io.IOException;
060    import java.io.ObjectInputStream;
061    import java.io.ObjectOutputStream;
062    import java.io.Serializable;
063    
064    import org.jfree.chart.LegendItem;
065    import org.jfree.chart.axis.ValueAxis;
066    import org.jfree.chart.event.RendererChangeEvent;
067    import org.jfree.chart.plot.CrosshairState;
068    import org.jfree.chart.plot.PlotOrientation;
069    import org.jfree.chart.plot.PlotRenderingInfo;
070    import org.jfree.chart.plot.XYPlot;
071    import org.jfree.data.xy.XYDataset;
072    import org.jfree.io.SerialUtilities;
073    import org.jfree.ui.RectangleEdge;
074    import org.jfree.util.PublicCloneable;
075    import org.jfree.util.ShapeUtilities;
076    
077    /**
078     * A renderer that draws a small dot at each data point for an {@link XYPlot}.
079     */
080    public 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            fireChangeEvent();
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            fireChangeEvent();
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            fireChangeEvent();
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    }