Frames | No Frames |
1: /* =========================================================== 2: * JFreeChart : a free chart library for the Java(tm) platform 3: * =========================================================== 4: * 5: * (C) Copyright 2000-2007, by Object Refinery Limited and Contributors. 6: * 7: * Project Info: http://www.jfree.org/jfreechart/index.html 8: * 9: * This library is free software; you can redistribute it and/or modify it 10: * under the terms of the GNU Lesser General Public License as published by 11: * the Free Software Foundation; either version 2.1 of the License, or 12: * (at your option) any later version. 13: * 14: * This library is distributed in the hope that it will be useful, but 15: * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 16: * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 17: * License for more details. 18: * 19: * You should have received a copy of the GNU Lesser General Public 20: * License along with this library; if not, write to the Free Software 21: * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 22: * USA. 23: * 24: * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 25: * in the United States and other countries.] 26: * 27: * -------------------- 28: * XYBlockRenderer.java 29: * -------------------- 30: * (C) Copyright 2006, 2007, by Object Refinery Limited. 31: * 32: * Original Author: David Gilbert (for Object Refinery Limited); 33: * Contributor(s): -; 34: * 35: * Changes 36: * ------- 37: * 05-Jul-2006 : Version 1 (DG); 38: * 02-Feb-2007 : Added getPaintScale() method (DG); 39: * 09-Mar-2007 : Fixed cloning (DG); 40: * 03-Aug-2007 : Fix for bug 1766646 (DG); 41: * 42: */ 43: 44: package org.jfree.chart.renderer.xy; 45: 46: import java.awt.BasicStroke; 47: import java.awt.Graphics2D; 48: import java.awt.Paint; 49: import java.awt.geom.Rectangle2D; 50: import java.io.Serializable; 51: 52: import org.jfree.chart.axis.ValueAxis; 53: import org.jfree.chart.event.RendererChangeEvent; 54: import org.jfree.chart.plot.CrosshairState; 55: import org.jfree.chart.plot.PlotOrientation; 56: import org.jfree.chart.plot.PlotRenderingInfo; 57: import org.jfree.chart.plot.XYPlot; 58: import org.jfree.chart.renderer.LookupPaintScale; 59: import org.jfree.chart.renderer.PaintScale; 60: import org.jfree.data.Range; 61: import org.jfree.data.general.DatasetUtilities; 62: import org.jfree.data.xy.XYDataset; 63: import org.jfree.data.xy.XYZDataset; 64: import org.jfree.ui.RectangleAnchor; 65: import org.jfree.util.PublicCloneable; 66: 67: /** 68: * A renderer that represents data from an {@link XYZDataset} by drawing a 69: * color block at each (x, y) point, where the color is a function of the 70: * z-value from the dataset. 71: * 72: * @since 1.0.4 73: */ 74: public class XYBlockRenderer extends AbstractXYItemRenderer 75: implements XYItemRenderer, Cloneable, Serializable { 76: 77: /** 78: * The block width (defaults to 1.0). 79: */ 80: private double blockWidth = 1.0; 81: 82: /** 83: * The block height (defaults to 1.0). 84: */ 85: private double blockHeight = 1.0; 86: 87: /** 88: * The anchor point used to align each block to its (x, y) location. The 89: * default value is <code>RectangleAnchor.CENTER</code>. 90: */ 91: private RectangleAnchor blockAnchor = RectangleAnchor.CENTER; 92: 93: /** Temporary storage for the x-offset used to align the block anchor. */ 94: private double xOffset; 95: 96: /** Temporary storage for the y-offset used to align the block anchor. */ 97: private double yOffset; 98: 99: /** 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: }