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
044package org.jfree.chart.renderer.xy;
045
046import java.awt.BasicStroke;
047import java.awt.Graphics2D;
048import java.awt.Paint;
049import java.awt.geom.Rectangle2D;
050import java.io.Serializable;
051
052import org.jfree.chart.axis.ValueAxis;
053import org.jfree.chart.event.RendererChangeEvent;
054import org.jfree.chart.plot.CrosshairState;
055import org.jfree.chart.plot.PlotOrientation;
056import org.jfree.chart.plot.PlotRenderingInfo;
057import org.jfree.chart.plot.XYPlot;
058import org.jfree.chart.renderer.LookupPaintScale;
059import org.jfree.chart.renderer.PaintScale;
060import org.jfree.data.Range;
061import org.jfree.data.general.DatasetUtilities;
062import org.jfree.data.xy.XYDataset;
063import org.jfree.data.xy.XYZDataset;
064import org.jfree.ui.RectangleAnchor;
065import 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 */
074public 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.
124     * 
125     * @param width  the new width, in data/axis units (must be > 0.0).
126     * 
127     * @see #getBlockWidth()
128     */
129    public void setBlockWidth(double width) {
130        if (width <= 0.0) {
131            throw new IllegalArgumentException(
132                    "The 'width' argument must be > 0.0");
133        }
134        this.blockWidth = width;
135        updateOffsets();
136        this.notifyListeners(new RendererChangeEvent(this));
137    }
138    
139    /**
140     * Returns the block height, in data/axis units.
141     * 
142     * @return The block height.
143     * 
144     * @see #setBlockHeight(double)
145     */
146    public double getBlockHeight() {
147        return this.blockHeight;
148    }
149    
150    /**
151     * Sets the height of the blocks used to represent each data item.
152     * 
153     * @param height  the new height, in data/axis units (must be > 0.0).
154     * 
155     * @see #getBlockHeight()
156     */
157    public void setBlockHeight(double height) {
158        if (height <= 0.0) {
159            throw new IllegalArgumentException(
160                    "The 'height' argument must be > 0.0");
161        }
162        this.blockHeight = height;
163        updateOffsets();
164        this.notifyListeners(new RendererChangeEvent(this));
165    }
166    
167    /**
168     * Returns the anchor point used to align a block at its (x, y) location.
169     * The default values is {@link RectangleAnchor#CENTER}.
170     * 
171     * @return The anchor point (never <code>null</code>).
172     * 
173     * @see #setBlockAnchor(RectangleAnchor)
174     */
175    public RectangleAnchor getBlockAnchor() {
176        return this.blockAnchor;
177    }
178    
179    /**
180     * Sets the anchor point used to align a block at its (x, y) location and
181     * sends a {@link RendererChangeEvent} to all registered listeners.
182     * 
183     * @param anchor  the anchor.
184     * 
185     * @see #getBlockAnchor()
186     */
187    public void setBlockAnchor(RectangleAnchor anchor) {
188        if (anchor == null) { 
189            throw new IllegalArgumentException("Null 'anchor' argument.");
190        }
191        if (this.blockAnchor.equals(anchor)) {
192            return;  // no change
193        }
194        this.blockAnchor = anchor;
195        updateOffsets();
196        notifyListeners(new RendererChangeEvent(this));
197    }
198    
199    /**
200     * Returns the paint scale used by the renderer.
201     * 
202     * @return The paint scale (never <code>null</code>).
203     * 
204     * @see #setPaintScale(PaintScale)
205     * @since 1.0.4
206     */
207    public PaintScale getPaintScale() {
208        return this.paintScale;
209    }
210    
211    /**
212     * Sets the paint scale used by the renderer.
213     * 
214     * @param scale  the scale (<code>null</code> not permitted).
215     * 
216     * @see #getPaintScale()
217     * @since 1.0.4
218     */
219    public void setPaintScale(PaintScale scale) {
220        if (scale == null) {
221            throw new IllegalArgumentException("Null 'scale' argument.");
222        }
223        this.paintScale = scale;
224        notifyListeners(new RendererChangeEvent(this));
225    }
226    
227    /**
228     * Updates the offsets to take into account the block width, height and
229     * anchor.
230     */
231    private void updateOffsets() {
232        if (this.blockAnchor.equals(RectangleAnchor.BOTTOM_LEFT)) {
233            this.xOffset = 0.0;
234            this.yOffset = 0.0;
235        }
236        else if (this.blockAnchor.equals(RectangleAnchor.BOTTOM)) {
237            this.xOffset = -this.blockWidth / 2.0;
238            this.yOffset = 0.0;
239        }
240        else if (this.blockAnchor.equals(RectangleAnchor.BOTTOM_RIGHT)) {
241            this.xOffset = -this.blockWidth;
242            this.yOffset = 0.0;
243        }
244        else if (this.blockAnchor.equals(RectangleAnchor.LEFT)) {
245            this.xOffset = 0.0;
246            this.yOffset = -this.blockHeight / 2.0;
247        }
248        else if (this.blockAnchor.equals(RectangleAnchor.CENTER)) {
249            this.xOffset = -this.blockWidth / 2.0;
250            this.yOffset = -this.blockHeight / 2.0;
251        }
252        else if (this.blockAnchor.equals(RectangleAnchor.RIGHT)) {
253            this.xOffset = -this.blockWidth;
254            this.yOffset = -this.blockHeight / 2.0;
255        }
256        else if (this.blockAnchor.equals(RectangleAnchor.TOP_LEFT)) {
257            this.xOffset = 0.0;
258            this.yOffset = -this.blockHeight;
259        }
260        else if (this.blockAnchor.equals(RectangleAnchor.TOP)) {
261            this.xOffset = -this.blockWidth / 2.0;
262            this.yOffset = -this.blockHeight;
263        }
264        else if (this.blockAnchor.equals(RectangleAnchor.TOP_RIGHT)) {
265            this.xOffset = -this.blockWidth;
266            this.yOffset = -this.blockHeight;
267        }        
268    }
269    
270    /**
271     * Returns the lower and upper bounds (range) of the x-values in the 
272     * specified dataset.
273     * 
274     * @param dataset  the dataset (<code>null</code> permitted).
275     * 
276     * @return The range (<code>null</code> if the dataset is <code>null</code>
277     *         or empty).
278     *         
279     * @see #findRangeBounds(XYDataset)
280     */
281    public Range findDomainBounds(XYDataset dataset) {
282        if (dataset != null) {
283            Range r = DatasetUtilities.findDomainBounds(dataset, false);
284            if (r == null) {
285                return null; 
286            }
287            else {
288                return new Range(r.getLowerBound() + this.xOffset, 
289                        r.getUpperBound() + this.blockWidth + this.xOffset);
290            }
291        }
292        else {
293            return null;
294        }
295    }
296
297    /**
298     * Returns the range of values the renderer requires to display all the 
299     * items from the specified dataset.
300     * 
301     * @param dataset  the dataset (<code>null</code> permitted).
302     * 
303     * @return The range (<code>null</code> if the dataset is <code>null</code> 
304     *         or empty).
305     *         
306     * @see #findDomainBounds(XYDataset)
307     */
308    public Range findRangeBounds(XYDataset dataset) {
309        if (dataset != null) {
310            Range r = DatasetUtilities.findRangeBounds(dataset, false);
311            if (r == null) {
312                return null; 
313            }
314            else {
315                return new Range(r.getLowerBound() + this.yOffset, 
316                        r.getUpperBound() + this.blockHeight + this.yOffset);
317            }
318        }
319        else {
320            return null;
321        }
322    }
323    
324    /**
325     * Draws the block representing the specified item.
326     * 
327     * @param g2  the graphics device.
328     * @param state  the state.
329     * @param dataArea  the data area.
330     * @param info  the plot rendering info.
331     * @param plot  the plot.
332     * @param domainAxis  the x-axis.
333     * @param rangeAxis  the y-axis.
334     * @param dataset  the dataset.
335     * @param series  the series index.
336     * @param item  the item index.
337     * @param crosshairState  the crosshair state.
338     * @param pass  the pass index.
339     */
340    public void drawItem(Graphics2D g2, XYItemRendererState state, 
341            Rectangle2D dataArea, PlotRenderingInfo info, XYPlot plot, 
342            ValueAxis domainAxis, ValueAxis rangeAxis, XYDataset dataset, 
343            int series, int item, CrosshairState crosshairState, int pass) {
344        
345        double x = dataset.getXValue(series, item);
346        double y = dataset.getYValue(series, item);
347        double z = 0.0;
348        if (dataset instanceof XYZDataset) {
349            z = ((XYZDataset) dataset).getZValue(series, item);
350        }
351        Paint p = this.paintScale.getPaint(z);
352        double xx0 = domainAxis.valueToJava2D(x + this.xOffset, dataArea, 
353                plot.getDomainAxisEdge());
354        double yy0 = rangeAxis.valueToJava2D(y + this.yOffset, dataArea, 
355                plot.getRangeAxisEdge());
356        double xx1 = domainAxis.valueToJava2D(x + this.blockWidth 
357                + this.xOffset, dataArea, plot.getDomainAxisEdge());
358        double yy1 = rangeAxis.valueToJava2D(y + this.blockHeight 
359                + this.yOffset, dataArea, plot.getRangeAxisEdge());
360        Rectangle2D block;
361        PlotOrientation orientation = plot.getOrientation();
362        if (orientation.equals(PlotOrientation.HORIZONTAL)) {
363            block = new Rectangle2D.Double(Math.min(yy0, yy1), 
364                    Math.min(xx0, xx1), Math.abs(yy1 - yy0), 
365                    Math.abs(xx0 - xx1));
366        }
367        else {
368            block = new Rectangle2D.Double(Math.min(xx0, xx1), 
369                    Math.min(yy0, yy1), Math.abs(xx1 - xx0), 
370                    Math.abs(yy1 - yy0));            
371        }
372        g2.setPaint(p);
373        g2.fill(block);
374        g2.setStroke(new BasicStroke(1.0f));
375        g2.draw(block);
376    }
377    
378    /**
379     * Tests this <code>XYBlockRenderer</code> for equality with an arbitrary
380     * object.  This method returns <code>true</code> if and only if:
381     * <ul>
382     * <li><code>obj</code> is an instance of <code>XYBlockRenderer</code> (not
383     *     <code>null</code>);</li>
384     * <li><code>obj</code> has the same field values as this 
385     *     <code>XYBlockRenderer</code>;</li>
386     * </ul>
387     * 
388     * @param obj  the object (<code>null</code> permitted).
389     * 
390     * @return A boolean.
391     */
392    public boolean equals(Object obj) {
393        if (obj == this) {
394            return true;
395        }
396        if (!(obj instanceof XYBlockRenderer)) {
397            return false;
398        }
399        XYBlockRenderer that = (XYBlockRenderer) obj;
400        if (this.blockHeight != that.blockHeight) {
401            return false;
402        }
403        if (this.blockWidth != that.blockWidth) {
404            return false;
405        }
406        if (!this.blockAnchor.equals(that.blockAnchor)) {
407            return false;
408        }
409        if (!this.paintScale.equals(that.paintScale)) {
410            return false;
411        }
412        return super.equals(obj);
413    }
414    
415    /**
416     * Returns a clone of this renderer.
417     * 
418     * @return A clone of this renderer.
419     * 
420     * @throws CloneNotSupportedException if there is a problem creating the 
421     *     clone.
422     */
423    public Object clone() throws CloneNotSupportedException {
424        XYBlockRenderer clone = (XYBlockRenderer) super.clone();
425        if (this.paintScale instanceof PublicCloneable) {
426            PublicCloneable pc = (PublicCloneable) this.paintScale;
427            clone.paintScale = (PaintScale) pc.clone();
428        }
429        return clone;
430    }
431
432}