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