001/* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2009, 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 * DefaultHeatMapDataset.java
029 * --------------------------
030 * (C) Copyright 2009, by Object Refinery Limited.
031 *
032 * Original Author:  David Gilbert (for Object Refinery Limited);
033 * Contributor(s):   -;
034 *
035 * Changes:
036 * --------
037 * 28-Jan-2009 : Version 1 (DG);
038 *
039 */
040
041package org.jfree.data.general;
042
043import java.io.Serializable;
044import org.jfree.data.DataUtilities;
045import org.jfree.util.PublicCloneable;
046
047/**
048 * A default implementation of the {@link HeatMapDataset} interface.
049 *
050 * @since 1.0.13
051 */
052public class DefaultHeatMapDataset extends AbstractDataset
053        implements HeatMapDataset, Cloneable, PublicCloneable, Serializable {
054
055    /** The number of samples in this dataset for the x-dimension. */
056    private int xSamples;
057
058    /** The number of samples in this dataset for the y-dimension. */
059    private int ySamples;
060
061    /** The minimum x-value in the dataset. */
062    private double minX;
063
064    /** The maximum x-value in the dataset. */
065    private double maxX;
066
067    /** The minimum y-value in the dataset. */
068    private double minY;
069
070    /** The maximum y-value in the dataset. */
071    private double maxY;
072
073    /** Storage for the z-values. */
074    private double[][] zValues;
075
076    /**
077     * Creates a new dataset where all the z-values are initially 0.  This is
078     * a fixed size array of z-values.
079     *
080     * @param xSamples  the number of x-values.
081     * @param ySamples  the number of y-values
082     * @param minX  the minimum x-value in the dataset.
083     * @param maxX  the maximum x-value in the dataset.
084     * @param minY  the minimum y-value in the dataset.
085     * @param maxY  the maximum y-value in the dataset.
086     */
087    public DefaultHeatMapDataset(int xSamples, int ySamples, double minX,
088            double maxX, double minY, double maxY) {
089
090        if (xSamples < 1) {
091            throw new IllegalArgumentException("Requires 'xSamples' > 0");
092        }
093        if (ySamples < 1) {
094            throw new IllegalArgumentException("Requires 'ySamples' > 0");
095        }
096        if (Double.isInfinite(minX) || Double.isNaN(minX)) {
097            throw new IllegalArgumentException("'minX' cannot be INF or NaN.");
098        }
099        if (Double.isInfinite(maxX) || Double.isNaN(maxX)) {
100            throw new IllegalArgumentException("'maxX' cannot be INF or NaN.");
101        }
102        if (Double.isInfinite(minY) || Double.isNaN(minY)) {
103            throw new IllegalArgumentException("'minY' cannot be INF or NaN.");
104        }
105        if (Double.isInfinite(maxY) || Double.isNaN(maxY)) {
106            throw new IllegalArgumentException("'maxY' cannot be INF or NaN.");
107        }
108
109        this.xSamples = xSamples;
110        this.ySamples = ySamples;
111        this.minX = minX;
112        this.maxX = maxX;
113        this.minY = minY;
114        this.maxY = maxY;
115        this.zValues = new double[xSamples][];
116        for (int x = 0; x < xSamples; x++) {
117            this.zValues[x] = new double[ySamples];
118        }
119    }
120
121    /**
122     * Returns the number of x values across the width of the dataset.  The
123     * values are evenly spaced between {@link #getMinimumXValue()} and
124     * {@link #getMaximumXValue()}.
125     *
126     * @return The number of x-values (always > 0).
127     */
128    public int getXSampleCount() {
129        return this.xSamples;
130    }
131
132    /**
133     * Returns the number of y values (or samples) for the dataset.  The
134     * values are evenly spaced between {@link #getMinimumYValue()} and
135     * {@link #getMaximumYValue()}.
136     *
137     * @return The number of y-values (always > 0).
138     */
139    public int getYSampleCount() {
140        return this.ySamples;
141    }
142
143    /**
144     * Returns the lowest x-value represented in this dataset.  A requirement
145     * of this interface is that this method must never return infinite or
146     * Double.NAN values.
147     *
148     * @return The lowest x-value represented in this dataset.
149     */
150    public double getMinimumXValue() {
151        return this.minX;
152    }
153
154    /**
155     * Returns the highest x-value represented in this dataset.  A requirement
156     * of this interface is that this method must never return infinite or
157     * Double.NAN values.
158     *
159     * @return The highest x-value represented in this dataset.
160     */
161    public double getMaximumXValue() {
162        return this.maxX;
163    }
164
165    /**
166     * Returns the lowest y-value represented in this dataset.  A requirement
167     * of this interface is that this method must never return infinite or
168     * Double.NAN values.
169     *
170     * @return The lowest y-value represented in this dataset.
171     */
172    public double getMinimumYValue() {
173        return this.minY;
174    }
175
176    /**
177     * Returns the highest y-value represented in this dataset.  A requirement
178     * of this interface is that this method must never return infinite or
179     * Double.NAN values.
180     *
181     * @return The highest y-value represented in this dataset.
182     */
183    public double getMaximumYValue() {
184        return this.maxY;
185    }
186
187    /**
188     * A convenience method that returns the x-value for the given index.
189     *
190     * @param xIndex  the xIndex.
191     *
192     * @return The x-value.
193     */
194    public double getXValue(int xIndex) {
195        double x = this.minX
196                + (this.maxX - this.minX) * (xIndex / (double) this.xSamples);
197        return x;
198    }
199
200    /**
201     * A convenience method that returns the y-value for the given index.
202     *
203     * @param yIndex  the yIndex.
204     *
205     * @return The y-value.
206     */
207    public double getYValue(int yIndex) {
208        double y = this.minY
209                + (this.maxY - this.minY) * (yIndex / (double) this.ySamples);
210        return y;
211    }
212
213    /**
214     * Returns the z-value at the specified sample position in the dataset.
215     * For a missing or unknown value, this method should return Double.NAN.
216     *
217     * @param xIndex  the position of the x sample in the dataset.
218     * @param yIndex  the position of the y sample in the dataset.
219     *
220     * @return The z-value.
221     */
222    public double getZValue(int xIndex, int yIndex) {
223        return this.zValues[xIndex][yIndex];
224    }
225
226    /**
227     * Returns the z-value at the specified sample position in the dataset.
228     * In this implementation, where the underlying values are stored in an
229     * array of double primitives, you should avoid using this method and
230     * use {@link #getZValue(int, int)} instead.
231     *
232     * @param xIndex  the position of the x sample in the dataset.
233     * @param yIndex  the position of the y sample in the dataset.
234     *
235     * @return The z-value.
236     */
237    public Number getZ(int xIndex, int yIndex) {
238        return new Double(getZValue(xIndex, yIndex));
239    }
240
241    /**
242     * Updates a z-value in the dataset and sends a {@link DatasetChangeEvent}
243     * to all registered listeners.
244     *
245     * @param xIndex  the x-index.
246     * @param yIndex  the y-index.
247     * @param z  the new z-value.
248     */
249    public void setZValue(int xIndex, int yIndex, double z) {
250        setZValue(xIndex, yIndex, z, true);
251    }
252
253    /**
254     * Updates a z-value in the dataset and, if requested, sends a
255     * {@link DatasetChangeEvent} to all registered listeners.
256     *
257     * @param xIndex  the x-index.
258     * @param yIndex  the y-index.
259     * @param z  the new z-value.
260     * @param notify  notify listeners?
261     */
262    public void setZValue(int xIndex, int yIndex, double z, boolean notify) {
263        this.zValues[xIndex][yIndex] = z;
264        if (notify) {
265            fireDatasetChanged();
266        }
267    }
268
269    /**
270     * Tests this dataset for equality with an arbitrary object.
271     *
272     * @param obj  the object (<code>null</code> permitted).
273     *
274     * @return A boolean.
275     */
276    public boolean equals(Object obj) {
277        if (obj == this) {
278            return true;
279        }
280        if (!(obj instanceof DefaultHeatMapDataset)) {
281            return false;
282        }
283        DefaultHeatMapDataset that = (DefaultHeatMapDataset) obj;
284        if (this.xSamples != that.xSamples) {
285            return false;
286        }
287        if (this.ySamples != that.ySamples) {
288            return false;
289        }
290        if (this.minX != that.minX) {
291            return false;
292        }
293        if (this.maxX != that.maxX) {
294            return false;
295        }
296        if (this.minY != that.minY) {
297            return false;
298        }
299        if (this.maxY != that.maxY) {
300            return false;
301        }
302        if (!DataUtilities.equal(this.zValues, that.zValues)) {
303            return false;
304        }
305        // can't find any differences
306        return true;
307    }
308
309    /**
310     * Returns an independent copy of this dataset.
311     *
312     * @return A clone.
313     *
314     * @throws java.lang.CloneNotSupportedException
315     */
316    public Object clone() throws CloneNotSupportedException {
317        DefaultHeatMapDataset clone = (DefaultHeatMapDataset) super.clone();
318        clone.zValues = DataUtilities.clone(this.zValues);
319        return clone;
320    }
321
322}