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    }