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 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 import 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 */ 074 public 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 and 124 * sends a {@link RendererChangeEvent} to all registered listeners. 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 fireChangeEvent(); 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 and 153 * sends a {@link RendererChangeEvent} to all registered listeners. 154 * 155 * @param height the new height, in data/axis units (must be > 0.0). 156 * 157 * @see #getBlockHeight() 158 */ 159 public void setBlockHeight(double height) { 160 if (height <= 0.0) { 161 throw new IllegalArgumentException( 162 "The 'height' argument must be > 0.0"); 163 } 164 this.blockHeight = height; 165 updateOffsets(); 166 fireChangeEvent(); 167 } 168 169 /** 170 * Returns the anchor point used to align a block at its (x, y) location. 171 * The default values is {@link RectangleAnchor#CENTER}. 172 * 173 * @return The anchor point (never <code>null</code>). 174 * 175 * @see #setBlockAnchor(RectangleAnchor) 176 */ 177 public RectangleAnchor getBlockAnchor() { 178 return this.blockAnchor; 179 } 180 181 /** 182 * Sets the anchor point used to align a block at its (x, y) location and 183 * sends a {@link RendererChangeEvent} to all registered listeners. 184 * 185 * @param anchor the anchor. 186 * 187 * @see #getBlockAnchor() 188 */ 189 public void setBlockAnchor(RectangleAnchor anchor) { 190 if (anchor == null) { 191 throw new IllegalArgumentException("Null 'anchor' argument."); 192 } 193 if (this.blockAnchor.equals(anchor)) { 194 return; // no change 195 } 196 this.blockAnchor = anchor; 197 updateOffsets(); 198 fireChangeEvent(); 199 } 200 201 /** 202 * Returns the paint scale used by the renderer. 203 * 204 * @return The paint scale (never <code>null</code>). 205 * 206 * @see #setPaintScale(PaintScale) 207 * @since 1.0.4 208 */ 209 public PaintScale getPaintScale() { 210 return this.paintScale; 211 } 212 213 /** 214 * Sets the paint scale used by the renderer and sends a 215 * {@link RendererChangeEvent} to all registered listeners. 216 * 217 * @param scale the scale (<code>null</code> not permitted). 218 * 219 * @see #getPaintScale() 220 * @since 1.0.4 221 */ 222 public void setPaintScale(PaintScale scale) { 223 if (scale == null) { 224 throw new IllegalArgumentException("Null 'scale' argument."); 225 } 226 this.paintScale = scale; 227 fireChangeEvent(); 228 } 229 230 /** 231 * Updates the offsets to take into account the block width, height and 232 * anchor. 233 */ 234 private void updateOffsets() { 235 if (this.blockAnchor.equals(RectangleAnchor.BOTTOM_LEFT)) { 236 this.xOffset = 0.0; 237 this.yOffset = 0.0; 238 } 239 else if (this.blockAnchor.equals(RectangleAnchor.BOTTOM)) { 240 this.xOffset = -this.blockWidth / 2.0; 241 this.yOffset = 0.0; 242 } 243 else if (this.blockAnchor.equals(RectangleAnchor.BOTTOM_RIGHT)) { 244 this.xOffset = -this.blockWidth; 245 this.yOffset = 0.0; 246 } 247 else if (this.blockAnchor.equals(RectangleAnchor.LEFT)) { 248 this.xOffset = 0.0; 249 this.yOffset = -this.blockHeight / 2.0; 250 } 251 else if (this.blockAnchor.equals(RectangleAnchor.CENTER)) { 252 this.xOffset = -this.blockWidth / 2.0; 253 this.yOffset = -this.blockHeight / 2.0; 254 } 255 else if (this.blockAnchor.equals(RectangleAnchor.RIGHT)) { 256 this.xOffset = -this.blockWidth; 257 this.yOffset = -this.blockHeight / 2.0; 258 } 259 else if (this.blockAnchor.equals(RectangleAnchor.TOP_LEFT)) { 260 this.xOffset = 0.0; 261 this.yOffset = -this.blockHeight; 262 } 263 else if (this.blockAnchor.equals(RectangleAnchor.TOP)) { 264 this.xOffset = -this.blockWidth / 2.0; 265 this.yOffset = -this.blockHeight; 266 } 267 else if (this.blockAnchor.equals(RectangleAnchor.TOP_RIGHT)) { 268 this.xOffset = -this.blockWidth; 269 this.yOffset = -this.blockHeight; 270 } 271 } 272 273 /** 274 * Returns the lower and upper bounds (range) of the x-values in the 275 * specified dataset. 276 * 277 * @param dataset the dataset (<code>null</code> permitted). 278 * 279 * @return The range (<code>null</code> if the dataset is <code>null</code> 280 * or empty). 281 * 282 * @see #findRangeBounds(XYDataset) 283 */ 284 public Range findDomainBounds(XYDataset dataset) { 285 if (dataset != null) { 286 Range r = DatasetUtilities.findDomainBounds(dataset, false); 287 if (r == null) { 288 return null; 289 } 290 else { 291 return new Range(r.getLowerBound() + this.xOffset, 292 r.getUpperBound() + this.blockWidth + this.xOffset); 293 } 294 } 295 else { 296 return null; 297 } 298 } 299 300 /** 301 * Returns the range of values the renderer requires to display all the 302 * items from the specified dataset. 303 * 304 * @param dataset the dataset (<code>null</code> permitted). 305 * 306 * @return The range (<code>null</code> if the dataset is <code>null</code> 307 * or empty). 308 * 309 * @see #findDomainBounds(XYDataset) 310 */ 311 public Range findRangeBounds(XYDataset dataset) { 312 if (dataset != null) { 313 Range r = DatasetUtilities.findRangeBounds(dataset, false); 314 if (r == null) { 315 return null; 316 } 317 else { 318 return new Range(r.getLowerBound() + this.yOffset, 319 r.getUpperBound() + this.blockHeight + this.yOffset); 320 } 321 } 322 else { 323 return null; 324 } 325 } 326 327 /** 328 * Draws the block representing the specified item. 329 * 330 * @param g2 the graphics device. 331 * @param state the state. 332 * @param dataArea the data area. 333 * @param info the plot rendering info. 334 * @param plot the plot. 335 * @param domainAxis the x-axis. 336 * @param rangeAxis the y-axis. 337 * @param dataset the dataset. 338 * @param series the series index. 339 * @param item the item index. 340 * @param crosshairState the crosshair state. 341 * @param pass the pass index. 342 */ 343 public void drawItem(Graphics2D g2, XYItemRendererState state, 344 Rectangle2D dataArea, PlotRenderingInfo info, XYPlot plot, 345 ValueAxis domainAxis, ValueAxis rangeAxis, XYDataset dataset, 346 int series, int item, CrosshairState crosshairState, int pass) { 347 348 double x = dataset.getXValue(series, item); 349 double y = dataset.getYValue(series, item); 350 double z = 0.0; 351 if (dataset instanceof XYZDataset) { 352 z = ((XYZDataset) dataset).getZValue(series, item); 353 } 354 Paint p = this.paintScale.getPaint(z); 355 double xx0 = domainAxis.valueToJava2D(x + this.xOffset, dataArea, 356 plot.getDomainAxisEdge()); 357 double yy0 = rangeAxis.valueToJava2D(y + this.yOffset, dataArea, 358 plot.getRangeAxisEdge()); 359 double xx1 = domainAxis.valueToJava2D(x + this.blockWidth 360 + this.xOffset, dataArea, plot.getDomainAxisEdge()); 361 double yy1 = rangeAxis.valueToJava2D(y + this.blockHeight 362 + this.yOffset, dataArea, plot.getRangeAxisEdge()); 363 Rectangle2D block; 364 PlotOrientation orientation = plot.getOrientation(); 365 if (orientation.equals(PlotOrientation.HORIZONTAL)) { 366 block = new Rectangle2D.Double(Math.min(yy0, yy1), 367 Math.min(xx0, xx1), Math.abs(yy1 - yy0), 368 Math.abs(xx0 - xx1)); 369 } 370 else { 371 block = new Rectangle2D.Double(Math.min(xx0, xx1), 372 Math.min(yy0, yy1), Math.abs(xx1 - xx0), 373 Math.abs(yy1 - yy0)); 374 } 375 g2.setPaint(p); 376 g2.fill(block); 377 g2.setStroke(new BasicStroke(1.0f)); 378 g2.draw(block); 379 } 380 381 /** 382 * Tests this <code>XYBlockRenderer</code> for equality with an arbitrary 383 * object. This method returns <code>true</code> if and only if: 384 * <ul> 385 * <li><code>obj</code> is an instance of <code>XYBlockRenderer</code> (not 386 * <code>null</code>);</li> 387 * <li><code>obj</code> has the same field values as this 388 * <code>XYBlockRenderer</code>;</li> 389 * </ul> 390 * 391 * @param obj the object (<code>null</code> permitted). 392 * 393 * @return A boolean. 394 */ 395 public boolean equals(Object obj) { 396 if (obj == this) { 397 return true; 398 } 399 if (!(obj instanceof XYBlockRenderer)) { 400 return false; 401 } 402 XYBlockRenderer that = (XYBlockRenderer) obj; 403 if (this.blockHeight != that.blockHeight) { 404 return false; 405 } 406 if (this.blockWidth != that.blockWidth) { 407 return false; 408 } 409 if (!this.blockAnchor.equals(that.blockAnchor)) { 410 return false; 411 } 412 if (!this.paintScale.equals(that.paintScale)) { 413 return false; 414 } 415 return super.equals(obj); 416 } 417 418 /** 419 * Returns a clone of this renderer. 420 * 421 * @return A clone of this renderer. 422 * 423 * @throws CloneNotSupportedException if there is a problem creating the 424 * clone. 425 */ 426 public Object clone() throws CloneNotSupportedException { 427 XYBlockRenderer clone = (XYBlockRenderer) super.clone(); 428 if (this.paintScale instanceof PublicCloneable) { 429 PublicCloneable pc = (PublicCloneable) this.paintScale; 430 clone.paintScale = (PaintScale) pc.clone(); 431 } 432 return clone; 433 } 434 435 }