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 274 } 275 276 /** 277 * Returns a legend item for the specified series. 278 * 279 * @param datasetIndex the dataset index (zero-based). 280 * @param series the series index (zero-based). 281 * 282 * @return A legend item for the series (possibly <code>null</code>). 283 */ 284 public LegendItem getLegendItem(int datasetIndex, int series) { 285 286 // if the renderer isn't assigned to a plot, then we don't have a 287 // dataset... 288 XYPlot plot = getPlot(); 289 if (plot == null) { 290 return null; 291 } 292 293 XYDataset dataset = plot.getDataset(datasetIndex); 294 if (dataset == null) { 295 return null; 296 } 297 298 LegendItem result = null; 299 if (getItemVisible(series, 0)) { 300 String label = getLegendItemLabelGenerator().generateLabel(dataset, 301 series); 302 String description = label; 303 String toolTipText = null; 304 if (getLegendItemToolTipGenerator() != null) { 305 toolTipText = getLegendItemToolTipGenerator().generateLabel( 306 dataset, series); 307 } 308 String urlText = null; 309 if (getLegendItemURLGenerator() != null) { 310 urlText = getLegendItemURLGenerator().generateLabel( 311 dataset, series); 312 } 313 Paint fillPaint = lookupSeriesPaint(series); 314 result = new LegendItem(label, description, toolTipText, urlText, 315 getLegendShape(), fillPaint); 316 result.setLabelFont(lookupLegendTextFont(series)); 317 Paint labelPaint = lookupLegendTextPaint(series); 318 if (labelPaint != null) { 319 result.setLabelPaint(labelPaint); 320 } 321 result.setSeriesKey(dataset.getSeriesKey(series)); 322 result.setSeriesIndex(series); 323 result.setDataset(dataset); 324 result.setDatasetIndex(datasetIndex); 325 } 326 327 return result; 328 329 } 330 331 /** 332 * Tests this renderer for equality with an arbitrary object. This method 333 * returns <code>true</code> if and only if: 334 * 335 * <ul> 336 * <li><code>obj</code> is not <code>null</code>;</li> 337 * <li><code>obj</code> is an instance of <code>XYDotRenderer</code>;</li> 338 * <li>both renderers have the same attribute values. 339 * </ul> 340 * 341 * @param obj the object (<code>null</code> permitted). 342 * 343 * @return A boolean. 344 */ 345 public boolean equals(Object obj) { 346 if (obj == this) { 347 return true; 348 } 349 if (!(obj instanceof XYDotRenderer)) { 350 return false; 351 } 352 XYDotRenderer that = (XYDotRenderer) obj; 353 if (this.dotWidth != that.dotWidth) { 354 return false; 355 } 356 if (this.dotHeight != that.dotHeight) { 357 return false; 358 } 359 if (!ShapeUtilities.equal(this.legendShape, that.legendShape)) { 360 return false; 361 } 362 return super.equals(obj); 363 } 364 365 /** 366 * Returns a clone of the renderer. 367 * 368 * @return A clone. 369 * 370 * @throws CloneNotSupportedException if the renderer cannot be cloned. 371 */ 372 public Object clone() throws CloneNotSupportedException { 373 return super.clone(); 374 } 375 376 /** 377 * Provides serialization support. 378 * 379 * @param stream the input stream. 380 * 381 * @throws IOException if there is an I/O error. 382 * @throws ClassNotFoundException if there is a classpath problem. 383 */ 384 private void readObject(ObjectInputStream stream) 385 throws IOException, ClassNotFoundException { 386 stream.defaultReadObject(); 387 this.legendShape = SerialUtilities.readShape(stream); 388 } 389 390 /** 391 * Provides serialization support. 392 * 393 * @param stream the output stream. 394 * 395 * @throws IOException if there is an I/O error. 396 */ 397 private void writeObject(ObjectOutputStream stream) throws IOException { 398 stream.defaultWriteObject(); 399 SerialUtilities.writeShape(this.legendShape, stream); 400 } 401 402 }