001/* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2014, 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 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. 025 * Other names may be trademarks of their respective owners.] 026 * 027 * ------------------ 028 * XYDotRenderer.java 029 * ------------------ 030 * (C) Copyright 2002-2014, 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 * 03-Jul-2013 : Use ParamChecks (DG); 052 * 053 */ 054 055package org.jfree.chart.renderer.xy; 056 057import java.awt.Graphics2D; 058import java.awt.Paint; 059import java.awt.Shape; 060import java.awt.geom.Rectangle2D; 061import java.io.IOException; 062import java.io.ObjectInputStream; 063import java.io.ObjectOutputStream; 064 065import org.jfree.chart.LegendItem; 066import org.jfree.chart.axis.ValueAxis; 067import org.jfree.chart.event.RendererChangeEvent; 068import org.jfree.chart.plot.CrosshairState; 069import org.jfree.chart.plot.PlotOrientation; 070import org.jfree.chart.plot.PlotRenderingInfo; 071import org.jfree.chart.plot.XYPlot; 072import org.jfree.chart.util.ParamChecks; 073import org.jfree.data.xy.XYDataset; 074import org.jfree.io.SerialUtilities; 075import org.jfree.ui.RectangleEdge; 076import org.jfree.util.PublicCloneable; 077import org.jfree.util.ShapeUtilities; 078 079/** 080 * A renderer that draws a small dot at each data point for an {@link XYPlot}. 081 * The example shown here is generated by the 082 * <code>ScatterPlotDemo4.java</code> program included in the JFreeChart 083 * demo collection: 084 * <br><br> 085 * <img src="../../../../../images/XYDotRendererSample.png" 086 * alt="XYDotRendererSample.png"> 087 */ 088public class XYDotRenderer extends AbstractXYItemRenderer 089 implements XYItemRenderer, PublicCloneable { 090 091 /** For serialization. */ 092 private static final long serialVersionUID = -2764344339073566425L; 093 094 /** The dot width. */ 095 private int dotWidth; 096 097 /** The dot height. */ 098 private int dotHeight; 099 100 /** 101 * The shape that is used to represent an item in the legend. 102 * 103 * @since 1.0.7 104 */ 105 private transient Shape legendShape; 106 107 /** 108 * Constructs a new renderer. 109 */ 110 public XYDotRenderer() { 111 super(); 112 this.dotWidth = 1; 113 this.dotHeight = 1; 114 this.legendShape = new Rectangle2D.Double(-3.0, -3.0, 6.0, 6.0); 115 } 116 117 /** 118 * Returns the dot width (the default value is 1). 119 * 120 * @return The dot width. 121 * 122 * @since 1.0.2 123 * @see #setDotWidth(int) 124 */ 125 public int getDotWidth() { 126 return this.dotWidth; 127 } 128 129 /** 130 * Sets the dot width and sends a {@link RendererChangeEvent} to all 131 * registered listeners. 132 * 133 * @param w the new width (must be greater than zero). 134 * 135 * @throws IllegalArgumentException if <code>w</code> is less than one. 136 * 137 * @since 1.0.2 138 * @see #getDotWidth() 139 */ 140 public void setDotWidth(int w) { 141 if (w < 1) { 142 throw new IllegalArgumentException("Requires w > 0."); 143 } 144 this.dotWidth = w; 145 fireChangeEvent(); 146 } 147 148 /** 149 * Returns the dot height (the default value is 1). 150 * 151 * @return The dot height. 152 * 153 * @since 1.0.2 154 * @see #setDotHeight(int) 155 */ 156 public int getDotHeight() { 157 return this.dotHeight; 158 } 159 160 /** 161 * Sets the dot height and sends a {@link RendererChangeEvent} to all 162 * registered listeners. 163 * 164 * @param h the new height (must be greater than zero). 165 * 166 * @throws IllegalArgumentException if <code>h</code> is less than one. 167 * 168 * @since 1.0.2 169 * @see #getDotHeight() 170 */ 171 public void setDotHeight(int h) { 172 if (h < 1) { 173 throw new IllegalArgumentException("Requires h > 0."); 174 } 175 this.dotHeight = h; 176 fireChangeEvent(); 177 } 178 179 /** 180 * Returns the shape used to represent an item in the legend. 181 * 182 * @return The legend shape (never <code>null</code>). 183 * 184 * @see #setLegendShape(Shape) 185 * 186 * @since 1.0.7 187 */ 188 public Shape getLegendShape() { 189 return this.legendShape; 190 } 191 192 /** 193 * Sets the shape used as a line in each legend item and sends a 194 * {@link RendererChangeEvent} to all registered listeners. 195 * 196 * @param shape the shape (<code>null</code> not permitted). 197 * 198 * @see #getLegendShape() 199 * 200 * @since 1.0.7 201 */ 202 public void setLegendShape(Shape shape) { 203 ParamChecks.nullNotPermitted(shape, "shape"); 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 @Override 227 public void drawItem(Graphics2D g2, XYItemRendererState state, 228 Rectangle2D dataArea, PlotRenderingInfo info, XYPlot plot, 229 ValueAxis domainAxis, ValueAxis rangeAxis, XYDataset dataset, 230 int series, int item, CrosshairState crosshairState, int pass) { 231 232 // do nothing if item is not visible 233 if (!getItemVisible(series, item)) { 234 return; 235 } 236 237 // get the data point... 238 double x = dataset.getXValue(series, item); 239 double y = dataset.getYValue(series, item); 240 double adjx = (this.dotWidth - 1) / 2.0; 241 double adjy = (this.dotHeight - 1) / 2.0; 242 if (!Double.isNaN(y)) { 243 RectangleEdge xAxisLocation = plot.getDomainAxisEdge(); 244 RectangleEdge yAxisLocation = plot.getRangeAxisEdge(); 245 double transX = domainAxis.valueToJava2D(x, dataArea, 246 xAxisLocation) - adjx; 247 double transY = rangeAxis.valueToJava2D(y, dataArea, yAxisLocation) 248 - adjy; 249 250 g2.setPaint(getItemPaint(series, item)); 251 PlotOrientation orientation = plot.getOrientation(); 252 if (orientation == PlotOrientation.HORIZONTAL) { 253 g2.fillRect((int) transY, (int) transX, this.dotHeight, 254 this.dotWidth); 255 } 256 else if (orientation == PlotOrientation.VERTICAL) { 257 g2.fillRect((int) transX, (int) transY, this.dotWidth, 258 this.dotHeight); 259 } 260 261 int domainAxisIndex = plot.getDomainAxisIndex(domainAxis); 262 int rangeAxisIndex = plot.getRangeAxisIndex(rangeAxis); 263 updateCrosshairValues(crosshairState, x, y, domainAxisIndex, 264 rangeAxisIndex, transX, transY, orientation); 265 } 266 267 } 268 269 /** 270 * Returns a legend item for the specified series. 271 * 272 * @param datasetIndex the dataset index (zero-based). 273 * @param series the series index (zero-based). 274 * 275 * @return A legend item for the series (possibly <code>null</code>). 276 */ 277 @Override 278 public LegendItem getLegendItem(int datasetIndex, int series) { 279 280 // if the renderer isn't assigned to a plot, then we don't have a 281 // dataset... 282 XYPlot plot = getPlot(); 283 if (plot == null) { 284 return null; 285 } 286 287 XYDataset dataset = plot.getDataset(datasetIndex); 288 if (dataset == null) { 289 return null; 290 } 291 292 LegendItem result = null; 293 if (getItemVisible(series, 0)) { 294 String label = getLegendItemLabelGenerator().generateLabel(dataset, 295 series); 296 String description = label; 297 String toolTipText = null; 298 if (getLegendItemToolTipGenerator() != null) { 299 toolTipText = getLegendItemToolTipGenerator().generateLabel( 300 dataset, series); 301 } 302 String urlText = null; 303 if (getLegendItemURLGenerator() != null) { 304 urlText = getLegendItemURLGenerator().generateLabel( 305 dataset, series); 306 } 307 Paint fillPaint = lookupSeriesPaint(series); 308 result = new LegendItem(label, description, toolTipText, urlText, 309 getLegendShape(), fillPaint); 310 result.setLabelFont(lookupLegendTextFont(series)); 311 Paint labelPaint = lookupLegendTextPaint(series); 312 if (labelPaint != null) { 313 result.setLabelPaint(labelPaint); 314 } 315 result.setSeriesKey(dataset.getSeriesKey(series)); 316 result.setSeriesIndex(series); 317 result.setDataset(dataset); 318 result.setDatasetIndex(datasetIndex); 319 } 320 321 return result; 322 323 } 324 325 /** 326 * Tests this renderer for equality with an arbitrary object. This method 327 * returns <code>true</code> if and only if: 328 * 329 * <ul> 330 * <li><code>obj</code> is not <code>null</code>;</li> 331 * <li><code>obj</code> is an instance of <code>XYDotRenderer</code>;</li> 332 * <li>both renderers have the same attribute values. 333 * </ul> 334 * 335 * @param obj the object (<code>null</code> permitted). 336 * 337 * @return A boolean. 338 */ 339 @Override 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 @Override 368 public Object clone() throws CloneNotSupportedException { 369 return super.clone(); 370 } 371 372 /** 373 * Provides serialization support. 374 * 375 * @param stream the input stream. 376 * 377 * @throws IOException if there is an I/O error. 378 * @throws ClassNotFoundException if there is a classpath problem. 379 */ 380 private void readObject(ObjectInputStream stream) 381 throws IOException, ClassNotFoundException { 382 stream.defaultReadObject(); 383 this.legendShape = SerialUtilities.readShape(stream); 384 } 385 386 /** 387 * Provides serialization support. 388 * 389 * @param stream the output stream. 390 * 391 * @throws IOException if there is an I/O error. 392 */ 393 private void writeObject(ObjectOutputStream stream) throws IOException { 394 stream.defaultWriteObject(); 395 SerialUtilities.writeShape(this.legendShape, stream); 396 } 397 398}