    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    }