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