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