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