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 }