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 }