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