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 * XYBlockRenderer.java 029 * -------------------- 030 * (C) Copyright 2006-2014, by Object Refinery Limited. 031 * 032 * Original Author: David Gilbert (for Object Refinery Limited); 033 * Contributor(s): -; 034 * 035 * Changes 036 * ------- 037 * 05-Jul-2006 : Version 1 (DG); 038 * 02-Feb-2007 : Added getPaintScale() method (DG); 039 * 09-Mar-2007 : Fixed cloning (DG); 040 * 03-Aug-2007 : Fix for bug 1766646 (DG); 041 * 07-Apr-2008 : Added entity collection code (DG); 042 * 22-Apr-2008 : Implemented PublicCloneable (DG); 043 * 03-Jul-2013 : Use ParamChecks (DG); 044 * 045 */ 046 047package org.jfree.chart.renderer.xy; 048 049import java.awt.BasicStroke; 050import java.awt.Graphics2D; 051import java.awt.Paint; 052import java.awt.geom.Rectangle2D; 053import java.io.Serializable; 054 055import org.jfree.chart.axis.ValueAxis; 056import org.jfree.chart.entity.EntityCollection; 057import org.jfree.chart.event.RendererChangeEvent; 058import org.jfree.chart.plot.CrosshairState; 059import org.jfree.chart.plot.PlotOrientation; 060import org.jfree.chart.plot.PlotRenderingInfo; 061import org.jfree.chart.plot.XYPlot; 062import org.jfree.chart.renderer.LookupPaintScale; 063import org.jfree.chart.renderer.PaintScale; 064import org.jfree.chart.util.ParamChecks; 065import org.jfree.data.Range; 066import org.jfree.data.general.DatasetUtilities; 067import org.jfree.data.xy.XYDataset; 068import org.jfree.data.xy.XYZDataset; 069import org.jfree.ui.RectangleAnchor; 070import org.jfree.util.PublicCloneable; 071 072/** 073 * A renderer that represents data from an {@link XYZDataset} by drawing a 074 * color block at each (x, y) point, where the color is a function of the 075 * z-value from the dataset. The example shown here is generated by the 076 * <code>XYBlockChartDemo1.java</code> program included in the JFreeChart 077 * demo collection: 078 * <br><br> 079 * <img src="../../../../../images/XYBlockRendererSample.png" 080 * alt="XYBlockRendererSample.png"> 081 * 082 * @since 1.0.4 083 */ 084public class XYBlockRenderer extends AbstractXYItemRenderer 085 implements XYItemRenderer, Cloneable, PublicCloneable, Serializable { 086 087 /** 088 * The block width (defaults to 1.0). 089 */ 090 private double blockWidth = 1.0; 091 092 /** 093 * The block height (defaults to 1.0). 094 */ 095 private double blockHeight = 1.0; 096 097 /** 098 * The anchor point used to align each block to its (x, y) location. The 099 * default value is <code>RectangleAnchor.CENTER</code>. 100 */ 101 private RectangleAnchor blockAnchor = RectangleAnchor.CENTER; 102 103 /** Temporary storage for the x-offset used to align the block anchor. */ 104 private double xOffset; 105 106 /** Temporary storage for the y-offset used to align the block anchor. */ 107 private double yOffset; 108 109 /** The paint scale. */ 110 private PaintScale paintScale; 111 112 /** 113 * Creates a new <code>XYBlockRenderer</code> instance with default 114 * attributes. 115 */ 116 public XYBlockRenderer() { 117 updateOffsets(); 118 this.paintScale = new LookupPaintScale(); 119 } 120 121 /** 122 * Returns the block width, in data/axis units. 123 * 124 * @return The block width. 125 * 126 * @see #setBlockWidth(double) 127 */ 128 public double getBlockWidth() { 129 return this.blockWidth; 130 } 131 132 /** 133 * Sets the width of the blocks used to represent each data item and 134 * sends a {@link RendererChangeEvent} to all registered listeners. 135 * 136 * @param width the new width, in data/axis units (must be > 0.0). 137 * 138 * @see #getBlockWidth() 139 */ 140 public void setBlockWidth(double width) { 141 if (width <= 0.0) { 142 throw new IllegalArgumentException( 143 "The 'width' argument must be > 0.0"); 144 } 145 this.blockWidth = width; 146 updateOffsets(); 147 fireChangeEvent(); 148 } 149 150 /** 151 * Returns the block height, in data/axis units. 152 * 153 * @return The block height. 154 * 155 * @see #setBlockHeight(double) 156 */ 157 public double getBlockHeight() { 158 return this.blockHeight; 159 } 160 161 /** 162 * Sets the height of the blocks used to represent each data item and 163 * sends a {@link RendererChangeEvent} to all registered listeners. 164 * 165 * @param height the new height, in data/axis units (must be > 0.0). 166 * 167 * @see #getBlockHeight() 168 */ 169 public void setBlockHeight(double height) { 170 if (height <= 0.0) { 171 throw new IllegalArgumentException( 172 "The 'height' argument must be > 0.0"); 173 } 174 this.blockHeight = height; 175 updateOffsets(); 176 fireChangeEvent(); 177 } 178 179 /** 180 * Returns the anchor point used to align a block at its (x, y) location. 181 * The default values is {@link RectangleAnchor#CENTER}. 182 * 183 * @return The anchor point (never <code>null</code>). 184 * 185 * @see #setBlockAnchor(RectangleAnchor) 186 */ 187 public RectangleAnchor getBlockAnchor() { 188 return this.blockAnchor; 189 } 190 191 /** 192 * Sets the anchor point used to align a block at its (x, y) location and 193 * sends a {@link RendererChangeEvent} to all registered listeners. 194 * 195 * @param anchor the anchor. 196 * 197 * @see #getBlockAnchor() 198 */ 199 public void setBlockAnchor(RectangleAnchor anchor) { 200 ParamChecks.nullNotPermitted(anchor, "anchor"); 201 if (this.blockAnchor.equals(anchor)) { 202 return; // no change 203 } 204 this.blockAnchor = anchor; 205 updateOffsets(); 206 fireChangeEvent(); 207 } 208 209 /** 210 * Returns the paint scale used by the renderer. 211 * 212 * @return The paint scale (never <code>null</code>). 213 * 214 * @see #setPaintScale(PaintScale) 215 * @since 1.0.4 216 */ 217 public PaintScale getPaintScale() { 218 return this.paintScale; 219 } 220 221 /** 222 * Sets the paint scale used by the renderer and sends a 223 * {@link RendererChangeEvent} to all registered listeners. 224 * 225 * @param scale the scale (<code>null</code> not permitted). 226 * 227 * @see #getPaintScale() 228 * @since 1.0.4 229 */ 230 public void setPaintScale(PaintScale scale) { 231 ParamChecks.nullNotPermitted(scale, "scale"); 232 this.paintScale = scale; 233 fireChangeEvent(); 234 } 235 236 /** 237 * Updates the offsets to take into account the block width, height and 238 * anchor. 239 */ 240 private void updateOffsets() { 241 if (this.blockAnchor.equals(RectangleAnchor.BOTTOM_LEFT)) { 242 this.xOffset = 0.0; 243 this.yOffset = 0.0; 244 } 245 else if (this.blockAnchor.equals(RectangleAnchor.BOTTOM)) { 246 this.xOffset = -this.blockWidth / 2.0; 247 this.yOffset = 0.0; 248 } 249 else if (this.blockAnchor.equals(RectangleAnchor.BOTTOM_RIGHT)) { 250 this.xOffset = -this.blockWidth; 251 this.yOffset = 0.0; 252 } 253 else if (this.blockAnchor.equals(RectangleAnchor.LEFT)) { 254 this.xOffset = 0.0; 255 this.yOffset = -this.blockHeight / 2.0; 256 } 257 else if (this.blockAnchor.equals(RectangleAnchor.CENTER)) { 258 this.xOffset = -this.blockWidth / 2.0; 259 this.yOffset = -this.blockHeight / 2.0; 260 } 261 else if (this.blockAnchor.equals(RectangleAnchor.RIGHT)) { 262 this.xOffset = -this.blockWidth; 263 this.yOffset = -this.blockHeight / 2.0; 264 } 265 else if (this.blockAnchor.equals(RectangleAnchor.TOP_LEFT)) { 266 this.xOffset = 0.0; 267 this.yOffset = -this.blockHeight; 268 } 269 else if (this.blockAnchor.equals(RectangleAnchor.TOP)) { 270 this.xOffset = -this.blockWidth / 2.0; 271 this.yOffset = -this.blockHeight; 272 } 273 else if (this.blockAnchor.equals(RectangleAnchor.TOP_RIGHT)) { 274 this.xOffset = -this.blockWidth; 275 this.yOffset = -this.blockHeight; 276 } 277 } 278 279 /** 280 * Returns the lower and upper bounds (range) of the x-values in the 281 * specified dataset. 282 * 283 * @param dataset the dataset (<code>null</code> permitted). 284 * 285 * @return The range (<code>null</code> if the dataset is <code>null</code> 286 * or empty). 287 * 288 * @see #findRangeBounds(XYDataset) 289 */ 290 @Override 291 public Range findDomainBounds(XYDataset dataset) { 292 if (dataset == null) { 293 return null; 294 } 295 Range r = DatasetUtilities.findDomainBounds(dataset, false); 296 if (r == null) { 297 return null; 298 } 299 return new Range(r.getLowerBound() + this.xOffset, 300 r.getUpperBound() + this.blockWidth + this.xOffset); 301 } 302 303 /** 304 * Returns the range of values the renderer requires to display all the 305 * items from the specified dataset. 306 * 307 * @param dataset the dataset (<code>null</code> permitted). 308 * 309 * @return The range (<code>null</code> if the dataset is <code>null</code> 310 * or empty). 311 * 312 * @see #findDomainBounds(XYDataset) 313 */ 314 @Override 315 public Range findRangeBounds(XYDataset dataset) { 316 if (dataset != null) { 317 Range r = DatasetUtilities.findRangeBounds(dataset, false); 318 if (r == null) { 319 return null; 320 } 321 else { 322 return new Range(r.getLowerBound() + this.yOffset, 323 r.getUpperBound() + this.blockHeight + this.yOffset); 324 } 325 } 326 else { 327 return null; 328 } 329 } 330 331 /** 332 * Draws the block representing the specified item. 333 * 334 * @param g2 the graphics device. 335 * @param state the state. 336 * @param dataArea the data area. 337 * @param info the plot rendering info. 338 * @param plot the plot. 339 * @param domainAxis the x-axis. 340 * @param rangeAxis the y-axis. 341 * @param dataset the dataset. 342 * @param series the series index. 343 * @param item the item index. 344 * @param crosshairState the crosshair state. 345 * @param pass the pass index. 346 */ 347 @Override 348 public void drawItem(Graphics2D g2, XYItemRendererState state, 349 Rectangle2D dataArea, PlotRenderingInfo info, XYPlot plot, 350 ValueAxis domainAxis, ValueAxis rangeAxis, XYDataset dataset, 351 int series, int item, CrosshairState crosshairState, int pass) { 352 353 double x = dataset.getXValue(series, item); 354 double y = dataset.getYValue(series, item); 355 double z = 0.0; 356 if (dataset instanceof XYZDataset) { 357 z = ((XYZDataset) dataset).getZValue(series, item); 358 } 359 Paint p = this.paintScale.getPaint(z); 360 double xx0 = domainAxis.valueToJava2D(x + this.xOffset, dataArea, 361 plot.getDomainAxisEdge()); 362 double yy0 = rangeAxis.valueToJava2D(y + this.yOffset, dataArea, 363 plot.getRangeAxisEdge()); 364 double xx1 = domainAxis.valueToJava2D(x + this.blockWidth 365 + this.xOffset, dataArea, plot.getDomainAxisEdge()); 366 double yy1 = rangeAxis.valueToJava2D(y + this.blockHeight 367 + this.yOffset, dataArea, plot.getRangeAxisEdge()); 368 Rectangle2D block; 369 PlotOrientation orientation = plot.getOrientation(); 370 if (orientation.equals(PlotOrientation.HORIZONTAL)) { 371 block = new Rectangle2D.Double(Math.min(yy0, yy1), 372 Math.min(xx0, xx1), Math.abs(yy1 - yy0), 373 Math.abs(xx0 - xx1)); 374 } 375 else { 376 block = new Rectangle2D.Double(Math.min(xx0, xx1), 377 Math.min(yy0, yy1), Math.abs(xx1 - xx0), 378 Math.abs(yy1 - yy0)); 379 } 380 g2.setPaint(p); 381 g2.fill(block); 382 g2.setStroke(new BasicStroke(1.0f)); 383 g2.draw(block); 384 385 EntityCollection entities = state.getEntityCollection(); 386 if (entities != null) { 387 addEntity(entities, block, dataset, series, item, 0.0, 0.0); 388 } 389 390 } 391 392 /** 393 * Tests this <code>XYBlockRenderer</code> for equality with an arbitrary 394 * object. This method returns <code>true</code> if and only if: 395 * <ul> 396 * <li><code>obj</code> is an instance of <code>XYBlockRenderer</code> (not 397 * <code>null</code>);</li> 398 * <li><code>obj</code> has the same field values as this 399 * <code>XYBlockRenderer</code>;</li> 400 * </ul> 401 * 402 * @param obj the object (<code>null</code> permitted). 403 * 404 * @return A boolean. 405 */ 406 @Override 407 public boolean equals(Object obj) { 408 if (obj == this) { 409 return true; 410 } 411 if (!(obj instanceof XYBlockRenderer)) { 412 return false; 413 } 414 XYBlockRenderer that = (XYBlockRenderer) obj; 415 if (this.blockHeight != that.blockHeight) { 416 return false; 417 } 418 if (this.blockWidth != that.blockWidth) { 419 return false; 420 } 421 if (!this.blockAnchor.equals(that.blockAnchor)) { 422 return false; 423 } 424 if (!this.paintScale.equals(that.paintScale)) { 425 return false; 426 } 427 return super.equals(obj); 428 } 429 430 /** 431 * Returns a clone of this renderer. 432 * 433 * @return A clone of this renderer. 434 * 435 * @throws CloneNotSupportedException if there is a problem creating the 436 * clone. 437 */ 438 @Override 439 public Object clone() throws CloneNotSupportedException { 440 XYBlockRenderer clone = (XYBlockRenderer) super.clone(); 441 if (this.paintScale instanceof PublicCloneable) { 442 PublicCloneable pc = (PublicCloneable) this.paintScale; 443 clone.paintScale = (PaintScale) pc.clone(); 444 } 445 return clone; 446 } 447 448}