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    }