001/* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2014, 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 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. 
025 * Other names may be trademarks of their respective owners.]
026 *
027 * --------------------
028 * XYBlockRenderer.java
029 * --------------------
030 * (C) Copyright 2006-2014, 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 * 03-Jul-2013 : Use ParamChecks (DG);
044 *
045 */
046
047package org.jfree.chart.renderer.xy;
048
049import java.awt.BasicStroke;
050import java.awt.Graphics2D;
051import java.awt.Paint;
052import java.awt.geom.Rectangle2D;
053import java.io.Serializable;
054
055import org.jfree.chart.axis.ValueAxis;
056import org.jfree.chart.entity.EntityCollection;
057import org.jfree.chart.event.RendererChangeEvent;
058import org.jfree.chart.plot.CrosshairState;
059import org.jfree.chart.plot.PlotOrientation;
060import org.jfree.chart.plot.PlotRenderingInfo;
061import org.jfree.chart.plot.XYPlot;
062import org.jfree.chart.renderer.LookupPaintScale;
063import org.jfree.chart.renderer.PaintScale;
064import org.jfree.chart.util.ParamChecks;
065import org.jfree.data.Range;
066import org.jfree.data.general.DatasetUtilities;
067import org.jfree.data.xy.XYDataset;
068import org.jfree.data.xy.XYZDataset;
069import org.jfree.ui.RectangleAnchor;
070import org.jfree.util.PublicCloneable;
071
072/**
073 * A renderer that represents data from an {@link XYZDataset} by drawing a
074 * color block at each (x, y) point, where the color is a function of the
075 * z-value from the dataset.  The example shown here is generated by the
076 * <code>XYBlockChartDemo1.java</code> program included in the JFreeChart
077 * demo collection:
078 * <br><br>
079 * <img src="../../../../../images/XYBlockRendererSample.png"
080 * alt="XYBlockRendererSample.png">
081 *
082 * @since 1.0.4
083 */
084public class XYBlockRenderer extends AbstractXYItemRenderer
085        implements XYItemRenderer, Cloneable, PublicCloneable, Serializable {
086
087    /**
088     * The block width (defaults to 1.0).
089     */
090    private double blockWidth = 1.0;
091
092    /**
093     * The block height (defaults to 1.0).
094     */
095    private double blockHeight = 1.0;
096
097    /**
098     * The anchor point used to align each block to its (x, y) location.  The
099     * default value is <code>RectangleAnchor.CENTER</code>.
100     */
101    private RectangleAnchor blockAnchor = RectangleAnchor.CENTER;
102
103    /** Temporary storage for the x-offset used to align the block anchor. */
104    private double xOffset;
105
106    /** Temporary storage for the y-offset used to align the block anchor. */
107    private double yOffset;
108
109    /** The paint scale. */
110    private PaintScale paintScale;
111
112    /**
113     * Creates a new <code>XYBlockRenderer</code> instance with default
114     * attributes.
115     */
116    public XYBlockRenderer() {
117        updateOffsets();
118        this.paintScale = new LookupPaintScale();
119    }
120
121    /**
122     * Returns the block width, in data/axis units.
123     *
124     * @return The block width.
125     *
126     * @see #setBlockWidth(double)
127     */
128    public double getBlockWidth() {
129        return this.blockWidth;
130    }
131
132    /**
133     * Sets the width of the blocks used to represent each data item and
134     * sends a {@link RendererChangeEvent} to all registered listeners.
135     *
136     * @param width  the new width, in data/axis units (must be &gt; 0.0).
137     *
138     * @see #getBlockWidth()
139     */
140    public void setBlockWidth(double width) {
141        if (width <= 0.0) {
142            throw new IllegalArgumentException(
143                    "The 'width' argument must be > 0.0");
144        }
145        this.blockWidth = width;
146        updateOffsets();
147        fireChangeEvent();
148    }
149
150    /**
151     * Returns the block height, in data/axis units.
152     *
153     * @return The block height.
154     *
155     * @see #setBlockHeight(double)
156     */
157    public double getBlockHeight() {
158        return this.blockHeight;
159    }
160
161    /**
162     * Sets the height of the blocks used to represent each data item and
163     * sends a {@link RendererChangeEvent} to all registered listeners.
164     *
165     * @param height  the new height, in data/axis units (must be &gt; 0.0).
166     *
167     * @see #getBlockHeight()
168     */
169    public void setBlockHeight(double height) {
170        if (height <= 0.0) {
171            throw new IllegalArgumentException(
172                    "The 'height' argument must be > 0.0");
173        }
174        this.blockHeight = height;
175        updateOffsets();
176        fireChangeEvent();
177    }
178
179    /**
180     * Returns the anchor point used to align a block at its (x, y) location.
181     * The default values is {@link RectangleAnchor#CENTER}.
182     *
183     * @return The anchor point (never <code>null</code>).
184     *
185     * @see #setBlockAnchor(RectangleAnchor)
186     */
187    public RectangleAnchor getBlockAnchor() {
188        return this.blockAnchor;
189    }
190
191    /**
192     * Sets the anchor point used to align a block at its (x, y) location and
193     * sends a {@link RendererChangeEvent} to all registered listeners.
194     *
195     * @param anchor  the anchor.
196     *
197     * @see #getBlockAnchor()
198     */
199    public void setBlockAnchor(RectangleAnchor anchor) {
200        ParamChecks.nullNotPermitted(anchor, "anchor");
201        if (this.blockAnchor.equals(anchor)) {
202            return;  // no change
203        }
204        this.blockAnchor = anchor;
205        updateOffsets();
206        fireChangeEvent();
207    }
208
209    /**
210     * Returns the paint scale used by the renderer.
211     *
212     * @return The paint scale (never <code>null</code>).
213     *
214     * @see #setPaintScale(PaintScale)
215     * @since 1.0.4
216     */
217    public PaintScale getPaintScale() {
218        return this.paintScale;
219    }
220
221    /**
222     * Sets the paint scale used by the renderer and sends a
223     * {@link RendererChangeEvent} to all registered listeners.
224     *
225     * @param scale  the scale (<code>null</code> not permitted).
226     *
227     * @see #getPaintScale()
228     * @since 1.0.4
229     */
230    public void setPaintScale(PaintScale scale) {
231        ParamChecks.nullNotPermitted(scale, "scale");
232        this.paintScale = scale;
233        fireChangeEvent();
234    }
235
236    /**
237     * Updates the offsets to take into account the block width, height and
238     * anchor.
239     */
240    private void updateOffsets() {
241        if (this.blockAnchor.equals(RectangleAnchor.BOTTOM_LEFT)) {
242            this.xOffset = 0.0;
243            this.yOffset = 0.0;
244        }
245        else if (this.blockAnchor.equals(RectangleAnchor.BOTTOM)) {
246            this.xOffset = -this.blockWidth / 2.0;
247            this.yOffset = 0.0;
248        }
249        else if (this.blockAnchor.equals(RectangleAnchor.BOTTOM_RIGHT)) {
250            this.xOffset = -this.blockWidth;
251            this.yOffset = 0.0;
252        }
253        else if (this.blockAnchor.equals(RectangleAnchor.LEFT)) {
254            this.xOffset = 0.0;
255            this.yOffset = -this.blockHeight / 2.0;
256        }
257        else if (this.blockAnchor.equals(RectangleAnchor.CENTER)) {
258            this.xOffset = -this.blockWidth / 2.0;
259            this.yOffset = -this.blockHeight / 2.0;
260        }
261        else if (this.blockAnchor.equals(RectangleAnchor.RIGHT)) {
262            this.xOffset = -this.blockWidth;
263            this.yOffset = -this.blockHeight / 2.0;
264        }
265        else if (this.blockAnchor.equals(RectangleAnchor.TOP_LEFT)) {
266            this.xOffset = 0.0;
267            this.yOffset = -this.blockHeight;
268        }
269        else if (this.blockAnchor.equals(RectangleAnchor.TOP)) {
270            this.xOffset = -this.blockWidth / 2.0;
271            this.yOffset = -this.blockHeight;
272        }
273        else if (this.blockAnchor.equals(RectangleAnchor.TOP_RIGHT)) {
274            this.xOffset = -this.blockWidth;
275            this.yOffset = -this.blockHeight;
276        }
277    }
278
279    /**
280     * Returns the lower and upper bounds (range) of the x-values in the
281     * specified dataset.
282     *
283     * @param dataset  the dataset (<code>null</code> permitted).
284     *
285     * @return The range (<code>null</code> if the dataset is <code>null</code>
286     *         or empty).
287     *
288     * @see #findRangeBounds(XYDataset)
289     */
290    @Override
291    public Range findDomainBounds(XYDataset dataset) {
292        if (dataset == null) {
293            return null;
294        }
295        Range r = DatasetUtilities.findDomainBounds(dataset, false);
296        if (r == null) {
297            return null;
298        }
299        return new Range(r.getLowerBound() + this.xOffset,
300                         r.getUpperBound() + this.blockWidth + this.xOffset);
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    @Override
315    public Range findRangeBounds(XYDataset dataset) {
316        if (dataset != null) {
317            Range r = DatasetUtilities.findRangeBounds(dataset, false);
318            if (r == null) {
319                return null;
320            }
321            else {
322                return new Range(r.getLowerBound() + this.yOffset,
323                        r.getUpperBound() + this.blockHeight + this.yOffset);
324            }
325        }
326        else {
327            return null;
328        }
329    }
330
331    /**
332     * Draws the block representing the specified item.
333     *
334     * @param g2  the graphics device.
335     * @param state  the state.
336     * @param dataArea  the data area.
337     * @param info  the plot rendering info.
338     * @param plot  the plot.
339     * @param domainAxis  the x-axis.
340     * @param rangeAxis  the y-axis.
341     * @param dataset  the dataset.
342     * @param series  the series index.
343     * @param item  the item index.
344     * @param crosshairState  the crosshair state.
345     * @param pass  the pass index.
346     */
347    @Override
348    public void drawItem(Graphics2D g2, XYItemRendererState state,
349            Rectangle2D dataArea, PlotRenderingInfo info, XYPlot plot,
350            ValueAxis domainAxis, ValueAxis rangeAxis, XYDataset dataset,
351            int series, int item, CrosshairState crosshairState, int pass) {
352
353        double x = dataset.getXValue(series, item);
354        double y = dataset.getYValue(series, item);
355        double z = 0.0;
356        if (dataset instanceof XYZDataset) {
357            z = ((XYZDataset) dataset).getZValue(series, item);
358        }
359        Paint p = this.paintScale.getPaint(z);
360        double xx0 = domainAxis.valueToJava2D(x + this.xOffset, dataArea,
361                plot.getDomainAxisEdge());
362        double yy0 = rangeAxis.valueToJava2D(y + this.yOffset, dataArea,
363                plot.getRangeAxisEdge());
364        double xx1 = domainAxis.valueToJava2D(x + this.blockWidth
365                + this.xOffset, dataArea, plot.getDomainAxisEdge());
366        double yy1 = rangeAxis.valueToJava2D(y + this.blockHeight
367                + this.yOffset, dataArea, plot.getRangeAxisEdge());
368        Rectangle2D block;
369        PlotOrientation orientation = plot.getOrientation();
370        if (orientation.equals(PlotOrientation.HORIZONTAL)) {
371            block = new Rectangle2D.Double(Math.min(yy0, yy1),
372                    Math.min(xx0, xx1), Math.abs(yy1 - yy0),
373                    Math.abs(xx0 - xx1));
374        }
375        else {
376            block = new Rectangle2D.Double(Math.min(xx0, xx1),
377                    Math.min(yy0, yy1), Math.abs(xx1 - xx0),
378                    Math.abs(yy1 - yy0));
379        }
380        g2.setPaint(p);
381        g2.fill(block);
382        g2.setStroke(new BasicStroke(1.0f));
383        g2.draw(block);
384
385        EntityCollection entities = state.getEntityCollection();
386        if (entities != null) {
387            addEntity(entities, block, dataset, series, item, 0.0, 0.0);
388        }
389
390    }
391
392    /**
393     * Tests this <code>XYBlockRenderer</code> for equality with an arbitrary
394     * object.  This method returns <code>true</code> if and only if:
395     * <ul>
396     * <li><code>obj</code> is an instance of <code>XYBlockRenderer</code> (not
397     *     <code>null</code>);</li>
398     * <li><code>obj</code> has the same field values as this
399     *     <code>XYBlockRenderer</code>;</li>
400     * </ul>
401     *
402     * @param obj  the object (<code>null</code> permitted).
403     *
404     * @return A boolean.
405     */
406    @Override
407    public boolean equals(Object obj) {
408        if (obj == this) {
409            return true;
410        }
411        if (!(obj instanceof XYBlockRenderer)) {
412            return false;
413        }
414        XYBlockRenderer that = (XYBlockRenderer) obj;
415        if (this.blockHeight != that.blockHeight) {
416            return false;
417        }
418        if (this.blockWidth != that.blockWidth) {
419            return false;
420        }
421        if (!this.blockAnchor.equals(that.blockAnchor)) {
422            return false;
423        }
424        if (!this.paintScale.equals(that.paintScale)) {
425            return false;
426        }
427        return super.equals(obj);
428    }
429
430    /**
431     * Returns a clone of this renderer.
432     *
433     * @return A clone of this renderer.
434     *
435     * @throws CloneNotSupportedException if there is a problem creating the
436     *     clone.
437     */
438    @Override
439    public Object clone() throws CloneNotSupportedException {
440        XYBlockRenderer clone = (XYBlockRenderer) super.clone();
441        if (this.paintScale instanceof PublicCloneable) {
442            PublicCloneable pc = (PublicCloneable) this.paintScale;
443            clone.paintScale = (PaintScale) pc.clone();
444        }
445        return clone;
446    }
447
448}