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