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 * 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 *
042 */
043
044 package org.jfree.chart.renderer.xy;
045
046 import java.awt.BasicStroke;
047 import java.awt.Graphics2D;
048 import java.awt.Paint;
049 import java.awt.geom.Rectangle2D;
050 import java.io.Serializable;
051
052 import org.jfree.chart.axis.ValueAxis;
053 import org.jfree.chart.event.RendererChangeEvent;
054 import org.jfree.chart.plot.CrosshairState;
055 import org.jfree.chart.plot.PlotOrientation;
056 import org.jfree.chart.plot.PlotRenderingInfo;
057 import org.jfree.chart.plot.XYPlot;
058 import org.jfree.chart.renderer.LookupPaintScale;
059 import org.jfree.chart.renderer.PaintScale;
060 import org.jfree.data.Range;
061 import org.jfree.data.general.DatasetUtilities;
062 import org.jfree.data.xy.XYDataset;
063 import org.jfree.data.xy.XYZDataset;
064 import org.jfree.ui.RectangleAnchor;
065 import org.jfree.util.PublicCloneable;
066
067 /**
068 * A renderer that represents data from an {@link XYZDataset} by drawing a
069 * color block at each (x, y) point, where the color is a function of the
070 * z-value from the dataset.
071 *
072 * @since 1.0.4
073 */
074 public class XYBlockRenderer extends AbstractXYItemRenderer
075 implements XYItemRenderer, Cloneable, Serializable {
076
077 /**
078 * The block width (defaults to 1.0).
079 */
080 private double blockWidth = 1.0;
081
082 /**
083 * The block height (defaults to 1.0).
084 */
085 private double blockHeight = 1.0;
086
087 /**
088 * The anchor point used to align each block to its (x, y) location. The
089 * default value is <code>RectangleAnchor.CENTER</code>.
090 */
091 private RectangleAnchor blockAnchor = RectangleAnchor.CENTER;
092
093 /** Temporary storage for the x-offset used to align the block anchor. */
094 private double xOffset;
095
096 /** Temporary storage for the y-offset used to align the block anchor. */
097 private double yOffset;
098
099 /** 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 }