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