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 }