Frames | No Frames |
1: /* =========================================================== 2: * JFreeChart : a free chart library for the Java(tm) platform 3: * =========================================================== 4: * 5: * (C) Copyright 2000-2008, 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-2008, 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: * 07-Apr-2008 : Added entity collection code (DG); 42: * 22-Apr-2008 : Implemented PublicCloneable (DG); 43: * 44: */ 45: 46: package org.jfree.chart.renderer.xy; 47: 48: import java.awt.BasicStroke; 49: import java.awt.Graphics2D; 50: import java.awt.Paint; 51: import java.awt.geom.Rectangle2D; 52: import java.io.Serializable; 53: 54: import org.jfree.chart.axis.ValueAxis; 55: import org.jfree.chart.entity.EntityCollection; 56: import org.jfree.chart.event.RendererChangeEvent; 57: import org.jfree.chart.plot.CrosshairState; 58: import org.jfree.chart.plot.PlotOrientation; 59: import org.jfree.chart.plot.PlotRenderingInfo; 60: import org.jfree.chart.plot.XYPlot; 61: import org.jfree.chart.renderer.LookupPaintScale; 62: import org.jfree.chart.renderer.PaintScale; 63: import org.jfree.data.Range; 64: import org.jfree.data.general.DatasetUtilities; 65: import org.jfree.data.xy.XYDataset; 66: import org.jfree.data.xy.XYZDataset; 67: import org.jfree.ui.RectangleAnchor; 68: import org.jfree.util.PublicCloneable; 69: 70: /** 71: * A renderer that represents data from an {@link XYZDataset} by drawing a 72: * color block at each (x, y) point, where the color is a function of the 73: * z-value from the dataset. 74: * 75: * @since 1.0.4 76: */ 77: public class XYBlockRenderer extends AbstractXYItemRenderer 78: implements XYItemRenderer, Cloneable, PublicCloneable, Serializable { 79: 80: /** 81: * The block width (defaults to 1.0). 82: */ 83: private double blockWidth = 1.0; 84: 85: /** 86: * The block height (defaults to 1.0). 87: */ 88: private double blockHeight = 1.0; 89: 90: /** 91: * The anchor point used to align each block to its (x, y) location. The 92: * default value is <code>RectangleAnchor.CENTER</code>. 93: */ 94: private RectangleAnchor blockAnchor = RectangleAnchor.CENTER; 95: 96: /** Temporary storage for the x-offset used to align the block anchor. */ 97: private double xOffset; 98: 99: /** 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: }