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