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 }