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