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    
041    package org.jfree.data.general;
042    
043    import java.io.Serializable;
044    import org.jfree.data.DataUtilities;
045    import org.jfree.util.PublicCloneable;
046    
047    /**
048     * A default implementation of the {@link HeatMapDataset} interface.
049     *
050     * @since 1.0.13
051     */
052    public 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    }