    001/* ===========================================================
    002 * JFreeChart : a free chart library for the Java(tm) platform
    003 * ===========================================================
    004 *
    005 * (C) Copyright 2000-2011, 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, 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}