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