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 }