001/* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2020, 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-2020, by Object Refinery Limited. 031 * 032 * Original Author: David Gilbert (for Object Refinery Limited); 033 * Contributor(s): Christian W. Zuckschwerdt; 034 * 035 */ 036 037package org.jfree.chart.renderer.xy; 038 039import java.awt.Graphics2D; 040import java.awt.Paint; 041import java.awt.Shape; 042import java.awt.geom.Rectangle2D; 043import java.io.IOException; 044import java.io.ObjectInputStream; 045import java.io.ObjectOutputStream; 046 047import org.jfree.chart.LegendItem; 048import org.jfree.chart.axis.ValueAxis; 049import org.jfree.chart.event.RendererChangeEvent; 050import org.jfree.chart.plot.CrosshairState; 051import org.jfree.chart.plot.PlotOrientation; 052import org.jfree.chart.plot.PlotRenderingInfo; 053import org.jfree.chart.plot.XYPlot; 054import org.jfree.chart.ui.RectangleEdge; 055import org.jfree.chart.util.Args; 056import org.jfree.chart.util.PublicCloneable; 057import org.jfree.chart.util.SerialUtils; 058import org.jfree.chart.util.ShapeUtils; 059import org.jfree.data.xy.XYDataset; 060 061/** 062 * A renderer that draws a small dot at each data point for an {@link XYPlot}. 063 * The example shown here is generated by the 064 * {@code ScatterPlotDemo4.java} program included in the JFreeChart 065 * demo collection: 066 * <br><br> 067 * <img src="doc-files/XYDotRendererSample.png" alt="XYDotRendererSample.png"> 068 */ 069public class XYDotRenderer extends AbstractXYItemRenderer 070 implements XYItemRenderer, PublicCloneable { 071 072 /** For serialization. */ 073 private static final long serialVersionUID = -2764344339073566425L; 074 075 /** The dot width. */ 076 private int dotWidth; 077 078 /** The dot height. */ 079 private int dotHeight; 080 081 /** 082 * The shape that is used to represent an item in the legend. 083 * 084 * @since 1.0.7 085 */ 086 private transient Shape legendShape; 087 088 /** 089 * Constructs a new renderer. 090 */ 091 public XYDotRenderer() { 092 super(); 093 this.dotWidth = 1; 094 this.dotHeight = 1; 095 this.legendShape = new Rectangle2D.Double(-3.0, -3.0, 6.0, 6.0); 096 } 097 098 /** 099 * Returns the dot width (the default value is 1). 100 * 101 * @return The dot width. 102 * 103 * @since 1.0.2 104 * @see #setDotWidth(int) 105 */ 106 public int getDotWidth() { 107 return this.dotWidth; 108 } 109 110 /** 111 * Sets the dot width and sends a {@link RendererChangeEvent} to all 112 * registered listeners. 113 * 114 * @param w the new width (must be greater than zero). 115 * 116 * @throws IllegalArgumentException if {@code w} is less than one. 117 * 118 * @since 1.0.2 119 * @see #getDotWidth() 120 */ 121 public void setDotWidth(int w) { 122 if (w < 1) { 123 throw new IllegalArgumentException("Requires w > 0."); 124 } 125 this.dotWidth = w; 126 fireChangeEvent(); 127 } 128 129 /** 130 * Returns the dot height (the default value is 1). 131 * 132 * @return The dot height. 133 * 134 * @since 1.0.2 135 * @see #setDotHeight(int) 136 */ 137 public int getDotHeight() { 138 return this.dotHeight; 139 } 140 141 /** 142 * Sets the dot height and sends a {@link RendererChangeEvent} to all 143 * registered listeners. 144 * 145 * @param h the new height (must be greater than zero). 146 * 147 * @throws IllegalArgumentException if {@code h} is less than one. 148 * 149 * @since 1.0.2 150 * @see #getDotHeight() 151 */ 152 public void setDotHeight(int h) { 153 if (h < 1) { 154 throw new IllegalArgumentException("Requires h > 0."); 155 } 156 this.dotHeight = h; 157 fireChangeEvent(); 158 } 159 160 /** 161 * Returns the shape used to represent an item in the legend. 162 * 163 * @return The legend shape (never {@code null}). 164 * 165 * @see #setLegendShape(Shape) 166 * 167 * @since 1.0.7 168 */ 169 public Shape getLegendShape() { 170 return this.legendShape; 171 } 172 173 /** 174 * Sets the shape used as a line in each legend item and sends a 175 * {@link RendererChangeEvent} to all registered listeners. 176 * 177 * @param shape the shape ({@code null} not permitted). 178 * 179 * @see #getLegendShape() 180 * 181 * @since 1.0.7 182 */ 183 public void setLegendShape(Shape shape) { 184 Args.nullNotPermitted(shape, "shape"); 185 this.legendShape = shape; 186 fireChangeEvent(); 187 } 188 189 /** 190 * Draws the visual representation of a single data item. 191 * 192 * @param g2 the graphics device. 193 * @param state the renderer state. 194 * @param dataArea the area within which the data is being drawn. 195 * @param info collects information about the drawing. 196 * @param plot the plot (can be used to obtain standard color 197 * information etc). 198 * @param domainAxis the domain (horizontal) axis. 199 * @param rangeAxis the range (vertical) axis. 200 * @param dataset the dataset. 201 * @param series the series index (zero-based). 202 * @param item the item index (zero-based). 203 * @param crosshairState crosshair information for the plot 204 * ({@code null} permitted). 205 * @param pass the pass index. 206 */ 207 @Override 208 public void drawItem(Graphics2D g2, XYItemRendererState state, 209 Rectangle2D dataArea, PlotRenderingInfo info, XYPlot plot, 210 ValueAxis domainAxis, ValueAxis rangeAxis, XYDataset dataset, 211 int series, int item, CrosshairState crosshairState, int pass) { 212 213 // do nothing if item is not visible 214 if (!getItemVisible(series, item)) { 215 return; 216 } 217 218 // get the data point... 219 double x = dataset.getXValue(series, item); 220 double y = dataset.getYValue(series, item); 221 double adjx = (this.dotWidth - 1) / 2.0; 222 double adjy = (this.dotHeight - 1) / 2.0; 223 if (!Double.isNaN(y)) { 224 RectangleEdge xAxisLocation = plot.getDomainAxisEdge(); 225 RectangleEdge yAxisLocation = plot.getRangeAxisEdge(); 226 double transX = domainAxis.valueToJava2D(x, dataArea, 227 xAxisLocation) - adjx; 228 double transY = rangeAxis.valueToJava2D(y, dataArea, yAxisLocation) 229 - adjy; 230 231 g2.setPaint(getItemPaint(series, item)); 232 PlotOrientation orientation = plot.getOrientation(); 233 if (orientation == PlotOrientation.HORIZONTAL) { 234 g2.fillRect((int) transY, (int) transX, this.dotHeight, 235 this.dotWidth); 236 } 237 else if (orientation == PlotOrientation.VERTICAL) { 238 g2.fillRect((int) transX, (int) transY, this.dotWidth, 239 this.dotHeight); 240 } 241 242 int datasetIndex = plot.indexOf(dataset); 243 updateCrosshairValues(crosshairState, x, y, datasetIndex, 244 transX, transY, orientation); 245 } 246 247 } 248 249 /** 250 * Returns a legend item for the specified series. 251 * 252 * @param datasetIndex the dataset index (zero-based). 253 * @param series the series index (zero-based). 254 * 255 * @return A legend item for the series (possibly {@code null}). 256 */ 257 @Override 258 public LegendItem getLegendItem(int datasetIndex, int series) { 259 260 // if the renderer isn't assigned to a plot, then we don't have a 261 // dataset... 262 XYPlot plot = getPlot(); 263 if (plot == null) { 264 return null; 265 } 266 267 XYDataset dataset = plot.getDataset(datasetIndex); 268 if (dataset == null) { 269 return null; 270 } 271 272 LegendItem result = null; 273 if (getItemVisible(series, 0)) { 274 String label = getLegendItemLabelGenerator().generateLabel(dataset, 275 series); 276 String description = label; 277 String toolTipText = null; 278 if (getLegendItemToolTipGenerator() != null) { 279 toolTipText = getLegendItemToolTipGenerator().generateLabel( 280 dataset, series); 281 } 282 String urlText = null; 283 if (getLegendItemURLGenerator() != null) { 284 urlText = getLegendItemURLGenerator().generateLabel( 285 dataset, series); 286 } 287 Paint fillPaint = lookupSeriesPaint(series); 288 result = new LegendItem(label, description, toolTipText, urlText, 289 getLegendShape(), fillPaint); 290 result.setLabelFont(lookupLegendTextFont(series)); 291 Paint labelPaint = lookupLegendTextPaint(series); 292 if (labelPaint != null) { 293 result.setLabelPaint(labelPaint); 294 } 295 result.setSeriesKey(dataset.getSeriesKey(series)); 296 result.setSeriesIndex(series); 297 result.setDataset(dataset); 298 result.setDatasetIndex(datasetIndex); 299 } 300 301 return result; 302 303 } 304 305 /** 306 * Tests this renderer for equality with an arbitrary object. This method 307 * returns {@code true} if and only if: 308 * 309 * <ul> 310 * <li>{@code obj} is not {@code null};</li> 311 * <li>{@code obj} is an instance of {@code XYDotRenderer};</li> 312 * <li>both renderers have the same attribute values. 313 * </ul> 314 * 315 * @param obj the object ({@code null} permitted). 316 * 317 * @return A boolean. 318 */ 319 @Override 320 public boolean equals(Object obj) { 321 if (obj == this) { 322 return true; 323 } 324 if (!(obj instanceof XYDotRenderer)) { 325 return false; 326 } 327 XYDotRenderer that = (XYDotRenderer) obj; 328 if (this.dotWidth != that.dotWidth) { 329 return false; 330 } 331 if (this.dotHeight != that.dotHeight) { 332 return false; 333 } 334 if (!ShapeUtils.equal(this.legendShape, that.legendShape)) { 335 return false; 336 } 337 return super.equals(obj); 338 } 339 340 /** 341 * Returns a clone of the renderer. 342 * 343 * @return A clone. 344 * 345 * @throws CloneNotSupportedException if the renderer cannot be cloned. 346 */ 347 @Override 348 public Object clone() throws CloneNotSupportedException { 349 return super.clone(); 350 } 351 352 /** 353 * Provides serialization support. 354 * 355 * @param stream the input stream. 356 * 357 * @throws IOException if there is an I/O error. 358 * @throws ClassNotFoundException if there is a classpath problem. 359 */ 360 private void readObject(ObjectInputStream stream) 361 throws IOException, ClassNotFoundException { 362 stream.defaultReadObject(); 363 this.legendShape = SerialUtils.readShape(stream); 364 } 365 366 /** 367 * Provides serialization support. 368 * 369 * @param stream the output stream. 370 * 371 * @throws IOException if there is an I/O error. 372 */ 373 private void writeObject(ObjectOutputStream stream) throws IOException { 374 stream.defaultWriteObject(); 375 SerialUtils.writeShape(this.legendShape, stream); 376 } 377 378}