001 /* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2008, 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 * DefaultIntervalXYDataset.java 029 * ----------------------------- 030 * (C) Copyright 2006-2008, by Object Refinery Limited and Contributors. 031 * 032 * Original Author: David Gilbert (for Object Refinery Limited); 033 * Contributor(s): -; 034 * 035 * Changes 036 * ------- 037 * 23-Oct-2006 : Version 1 (DG); 038 * 02-Nov-2006 : Fixed a problem with adding a new series with the same key 039 * as an existing series (see bug 1589392) (DG); 040 * 28-Nov-2006 : New override for clone() (DG); 041 * 22-Apr-2008 : Implemented PublicCloneable (DG); 042 * 043 */ 044 045 package org.jfree.data.xy; 046 047 import java.util.ArrayList; 048 import java.util.Arrays; 049 import java.util.List; 050 051 import org.jfree.data.general.DatasetChangeEvent; 052 import org.jfree.util.PublicCloneable; 053 054 /** 055 * A dataset that defines a range (interval) for both the x-values and the 056 * y-values. This implementation uses six arrays to store the x, start-x, 057 * end-x, y, start-y and end-y values. 058 * <br><br> 059 * An alternative implementation of the {@link IntervalXYDataset} interface 060 * is provided by the {@link XYIntervalSeriesCollection} class. 061 * 062 * @since 1.0.3 063 */ 064 public class DefaultIntervalXYDataset extends AbstractIntervalXYDataset 065 implements PublicCloneable { 066 067 /** 068 * Storage for the series keys. This list must be kept in sync with the 069 * seriesList. 070 */ 071 private List seriesKeys; 072 073 /** 074 * Storage for the series in the dataset. We use a list because the 075 * order of the series is significant. This list must be kept in sync 076 * with the seriesKeys list. 077 */ 078 private List seriesList; 079 080 /** 081 * Creates a new <code>DefaultIntervalXYDataset</code> instance, initially 082 * containing no data. 083 */ 084 public DefaultIntervalXYDataset() { 085 this.seriesKeys = new java.util.ArrayList(); 086 this.seriesList = new java.util.ArrayList(); 087 } 088 089 /** 090 * Returns the number of series in the dataset. 091 * 092 * @return The series count. 093 */ 094 public int getSeriesCount() { 095 return this.seriesList.size(); 096 } 097 098 /** 099 * Returns the key for a series. 100 * 101 * @param series the series index (in the range <code>0</code> to 102 * <code>getSeriesCount() - 1</code>). 103 * 104 * @return The key for the series. 105 * 106 * @throws IllegalArgumentException if <code>series</code> is not in the 107 * specified range. 108 */ 109 public Comparable getSeriesKey(int series) { 110 if ((series < 0) || (series >= getSeriesCount())) { 111 throw new IllegalArgumentException("Series index out of bounds"); 112 } 113 return (Comparable) this.seriesKeys.get(series); 114 } 115 116 /** 117 * Returns the number of items in the specified series. 118 * 119 * @param series the series index (in the range <code>0</code> to 120 * <code>getSeriesCount() - 1</code>). 121 * 122 * @return The item count. 123 * 124 * @throws IllegalArgumentException if <code>series</code> is not in the 125 * specified range. 126 */ 127 public int getItemCount(int series) { 128 if ((series < 0) || (series >= getSeriesCount())) { 129 throw new IllegalArgumentException("Series index out of bounds"); 130 } 131 double[][] seriesArray = (double[][]) this.seriesList.get(series); 132 return seriesArray[0].length; 133 } 134 135 /** 136 * Returns the x-value for an item within a series. 137 * 138 * @param series the series index (in the range <code>0</code> to 139 * <code>getSeriesCount() - 1</code>). 140 * @param item the item index (in the range <code>0</code> to 141 * <code>getItemCount(series)</code>). 142 * 143 * @return The x-value. 144 * 145 * @throws ArrayIndexOutOfBoundsException if <code>series</code> is not 146 * within the specified range. 147 * @throws ArrayIndexOutOfBoundsException if <code>item</code> is not 148 * within the specified range. 149 * 150 * @see #getX(int, int) 151 */ 152 public double getXValue(int series, int item) { 153 double[][] seriesData = (double[][]) this.seriesList.get(series); 154 return seriesData[0][item]; 155 } 156 157 /** 158 * Returns the y-value for an item within a series. 159 * 160 * @param series the series index (in the range <code>0</code> to 161 * <code>getSeriesCount() - 1</code>). 162 * @param item the item index (in the range <code>0</code> to 163 * <code>getItemCount(series)</code>). 164 * 165 * @return The y-value. 166 * 167 * @throws ArrayIndexOutOfBoundsException if <code>series</code> is not 168 * within the specified range. 169 * @throws ArrayIndexOutOfBoundsException if <code>item</code> is not 170 * within the specified range. 171 * 172 * @see #getY(int, int) 173 */ 174 public double getYValue(int series, int item) { 175 double[][] seriesData = (double[][]) this.seriesList.get(series); 176 return seriesData[3][item]; 177 } 178 179 /** 180 * Returns the starting x-value for an item within a series. 181 * 182 * @param series the series index (in the range <code>0</code> to 183 * <code>getSeriesCount() - 1</code>). 184 * @param item the item index (in the range <code>0</code> to 185 * <code>getItemCount(series)</code>). 186 * 187 * @return The starting x-value. 188 * 189 * @throws ArrayIndexOutOfBoundsException if <code>series</code> is not 190 * within the specified range. 191 * @throws ArrayIndexOutOfBoundsException if <code>item</code> is not 192 * within the specified range. 193 * 194 * @see #getStartX(int, int) 195 */ 196 public double getStartXValue(int series, int item) { 197 double[][] seriesData = (double[][]) this.seriesList.get(series); 198 return seriesData[1][item]; 199 } 200 201 /** 202 * Returns the ending x-value for an item within a series. 203 * 204 * @param series the series index (in the range <code>0</code> to 205 * <code>getSeriesCount() - 1</code>). 206 * @param item the item index (in the range <code>0</code> to 207 * <code>getItemCount(series)</code>). 208 * 209 * @return The ending x-value. 210 * 211 * @throws ArrayIndexOutOfBoundsException if <code>series</code> is not 212 * within the specified range. 213 * @throws ArrayIndexOutOfBoundsException if <code>item</code> is not 214 * within the specified range. 215 * 216 * @see #getEndX(int, int) 217 */ 218 public double getEndXValue(int series, int item) { 219 double[][] seriesData = (double[][]) this.seriesList.get(series); 220 return seriesData[2][item]; 221 } 222 223 /** 224 * Returns the starting y-value for an item within a series. 225 * 226 * @param series the series index (in the range <code>0</code> to 227 * <code>getSeriesCount() - 1</code>). 228 * @param item the item index (in the range <code>0</code> to 229 * <code>getItemCount(series)</code>). 230 * 231 * @return The starting y-value. 232 * 233 * @throws ArrayIndexOutOfBoundsException if <code>series</code> is not 234 * within the specified range. 235 * @throws ArrayIndexOutOfBoundsException if <code>item</code> is not 236 * within the specified range. 237 * 238 * @see #getStartY(int, int) 239 */ 240 public double getStartYValue(int series, int item) { 241 double[][] seriesData = (double[][]) this.seriesList.get(series); 242 return seriesData[4][item]; 243 } 244 245 /** 246 * Returns the ending y-value for an item within a series. 247 * 248 * @param series the series index (in the range <code>0</code> to 249 * <code>getSeriesCount() - 1</code>). 250 * @param item the item index (in the range <code>0</code> to 251 * <code>getItemCount(series)</code>). 252 * 253 * @return The ending y-value. 254 * 255 * @throws ArrayIndexOutOfBoundsException if <code>series</code> is not 256 * within the specified range. 257 * @throws ArrayIndexOutOfBoundsException if <code>item</code> is not 258 * within the specified range. 259 * 260 * @see #getEndY(int, int) 261 */ 262 public double getEndYValue(int series, int item) { 263 double[][] seriesData = (double[][]) this.seriesList.get(series); 264 return seriesData[5][item]; 265 } 266 267 /** 268 * Returns the ending x-value for an item within a series. 269 * 270 * @param series the series index (in the range <code>0</code> to 271 * <code>getSeriesCount() - 1</code>). 272 * @param item the item index (in the range <code>0</code> to 273 * <code>getItemCount(series)</code>). 274 * 275 * @return The ending x-value. 276 * 277 * @throws ArrayIndexOutOfBoundsException if <code>series</code> is not 278 * within the specified range. 279 * @throws ArrayIndexOutOfBoundsException if <code>item</code> is not 280 * within the specified range. 281 * 282 * @see #getEndXValue(int, int) 283 */ 284 public Number getEndX(int series, int item) { 285 return new Double(getEndXValue(series, item)); 286 } 287 288 /** 289 * Returns the ending y-value for an item within a series. 290 * 291 * @param series the series index (in the range <code>0</code> to 292 * <code>getSeriesCount() - 1</code>). 293 * @param item the item index (in the range <code>0</code> to 294 * <code>getItemCount(series)</code>). 295 * 296 * @return The ending y-value. 297 * 298 * @throws ArrayIndexOutOfBoundsException if <code>series</code> is not 299 * within the specified range. 300 * @throws ArrayIndexOutOfBoundsException if <code>item</code> is not 301 * within the specified range. 302 * 303 * @see #getEndYValue(int, int) 304 */ 305 public Number getEndY(int series, int item) { 306 return new Double(getEndYValue(series, item)); 307 } 308 309 /** 310 * Returns the starting x-value for an item within a series. 311 * 312 * @param series the series index (in the range <code>0</code> to 313 * <code>getSeriesCount() - 1</code>). 314 * @param item the item index (in the range <code>0</code> to 315 * <code>getItemCount(series)</code>). 316 * 317 * @return The starting x-value. 318 * 319 * @throws ArrayIndexOutOfBoundsException if <code>series</code> is not 320 * within the specified range. 321 * @throws ArrayIndexOutOfBoundsException if <code>item</code> is not 322 * within the specified range. 323 * 324 * @see #getStartXValue(int, int) 325 */ 326 public Number getStartX(int series, int item) { 327 return new Double(getStartXValue(series, item)); 328 } 329 330 /** 331 * Returns the starting y-value for an item within a series. 332 * 333 * @param series the series index (in the range <code>0</code> to 334 * <code>getSeriesCount() - 1</code>). 335 * @param item the item index (in the range <code>0</code> to 336 * <code>getItemCount(series)</code>). 337 * 338 * @return The starting y-value. 339 * 340 * @throws ArrayIndexOutOfBoundsException if <code>series</code> is not 341 * within the specified range. 342 * @throws ArrayIndexOutOfBoundsException if <code>item</code> is not 343 * within the specified range. 344 * 345 * @see #getStartYValue(int, int) 346 */ 347 public Number getStartY(int series, int item) { 348 return new Double(getStartYValue(series, item)); 349 } 350 351 /** 352 * Returns the x-value for an item within a series. 353 * 354 * @param series the series index (in the range <code>0</code> to 355 * <code>getSeriesCount() - 1</code>). 356 * @param item the item index (in the range <code>0</code> to 357 * <code>getItemCount(series)</code>). 358 * 359 * @return The x-value. 360 * 361 * @throws ArrayIndexOutOfBoundsException if <code>series</code> is not 362 * within the specified range. 363 * @throws ArrayIndexOutOfBoundsException if <code>item</code> is not 364 * within the specified range. 365 * 366 * @see #getXValue(int, int) 367 */ 368 public Number getX(int series, int item) { 369 return new Double(getXValue(series, item)); 370 } 371 372 /** 373 * Returns the y-value for an item within a series. 374 * 375 * @param series the series index (in the range <code>0</code> to 376 * <code>getSeriesCount() - 1</code>). 377 * @param item the item index (in the range <code>0</code> to 378 * <code>getItemCount(series)</code>). 379 * 380 * @return The y-value. 381 * 382 * @throws ArrayIndexOutOfBoundsException if <code>series</code> is not 383 * within the specified range. 384 * @throws ArrayIndexOutOfBoundsException if <code>item</code> is not 385 * within the specified range. 386 * 387 * @see #getYValue(int, int) 388 */ 389 public Number getY(int series, int item) { 390 return new Double(getYValue(series, item)); 391 } 392 393 /** 394 * Adds a series or if a series with the same key already exists replaces 395 * the data for that series, then sends a {@link DatasetChangeEvent} to 396 * all registered listeners. 397 * 398 * @param seriesKey the series key (<code>null</code> not permitted). 399 * @param data the data (must be an array with length 6, containing six 400 * arrays of equal length, the first containing the x-values and the 401 * second containing the y-values). 402 */ 403 public void addSeries(Comparable seriesKey, double[][] data) { 404 if (seriesKey == null) { 405 throw new IllegalArgumentException( 406 "The 'seriesKey' cannot be null."); 407 } 408 if (data == null) { 409 throw new IllegalArgumentException("The 'data' is null."); 410 } 411 if (data.length != 6) { 412 throw new IllegalArgumentException( 413 "The 'data' array must have length == 6."); 414 } 415 int length = data[0].length; 416 if (length != data[1].length || length != data[2].length 417 || length != data[3].length || length != data[4].length 418 || length != data[5].length) { 419 throw new IllegalArgumentException( 420 "The 'data' array must contain two arrays with equal length."); 421 } 422 int seriesIndex = indexOf(seriesKey); 423 if (seriesIndex == -1) { // add a new series 424 this.seriesKeys.add(seriesKey); 425 this.seriesList.add(data); 426 } 427 else { // replace an existing series 428 this.seriesList.remove(seriesIndex); 429 this.seriesList.add(seriesIndex, data); 430 } 431 notifyListeners(new DatasetChangeEvent(this, this)); 432 } 433 434 /** 435 * Tests this <code>DefaultIntervalXYDataset</code> instance for equality 436 * with an arbitrary object. This method returns <code>true</code> if and 437 * only if: 438 * <ul> 439 * <li><code>obj</code> is not <code>null</code>;</li> 440 * <li><code>obj</code> is an instance of 441 * <code>DefaultIntervalXYDataset</code>;</li> 442 * <li>both datasets have the same number of series, each containing 443 * exactly the same values.</li> 444 * </ul> 445 * 446 * @param obj the object (<code>null</code> permitted). 447 * 448 * @return A boolean. 449 */ 450 public boolean equals(Object obj) { 451 if (obj == this) { 452 return true; 453 } 454 if (!(obj instanceof DefaultIntervalXYDataset)) { 455 return false; 456 } 457 DefaultIntervalXYDataset that = (DefaultIntervalXYDataset) obj; 458 if (!this.seriesKeys.equals(that.seriesKeys)) { 459 return false; 460 } 461 for (int i = 0; i < this.seriesList.size(); i++) { 462 double[][] d1 = (double[][]) this.seriesList.get(i); 463 double[][] d2 = (double[][]) that.seriesList.get(i); 464 double[] d1x = d1[0]; 465 double[] d2x = d2[0]; 466 if (!Arrays.equals(d1x, d2x)) { 467 return false; 468 } 469 double[] d1xs = d1[1]; 470 double[] d2xs = d2[1]; 471 if (!Arrays.equals(d1xs, d2xs)) { 472 return false; 473 } 474 double[] d1xe = d1[2]; 475 double[] d2xe = d2[2]; 476 if (!Arrays.equals(d1xe, d2xe)) { 477 return false; 478 } 479 double[] d1y = d1[3]; 480 double[] d2y = d2[3]; 481 if (!Arrays.equals(d1y, d2y)) { 482 return false; 483 } 484 double[] d1ys = d1[4]; 485 double[] d2ys = d2[4]; 486 if (!Arrays.equals(d1ys, d2ys)) { 487 return false; 488 } 489 double[] d1ye = d1[5]; 490 double[] d2ye = d2[5]; 491 if (!Arrays.equals(d1ye, d2ye)) { 492 return false; 493 } 494 } 495 return true; 496 } 497 498 /** 499 * Returns a hash code for this instance. 500 * 501 * @return A hash code. 502 */ 503 public int hashCode() { 504 int result; 505 result = this.seriesKeys.hashCode(); 506 result = 29 * result + this.seriesList.hashCode(); 507 return result; 508 } 509 510 /** 511 * Returns a clone of this dataset. 512 * 513 * @return A clone. 514 * 515 * @throws CloneNotSupportedException if the dataset contains a series with 516 * a key that cannot be cloned. 517 */ 518 public Object clone() throws CloneNotSupportedException { 519 DefaultIntervalXYDataset clone 520 = (DefaultIntervalXYDataset) super.clone(); 521 clone.seriesKeys = new java.util.ArrayList(this.seriesKeys); 522 clone.seriesList = new ArrayList(this.seriesList.size()); 523 for (int i = 0; i < this.seriesList.size(); i++) { 524 double[][] data = (double[][]) this.seriesList.get(i); 525 double[] x = data[0]; 526 double[] xStart = data[1]; 527 double[] xEnd = data[2]; 528 double[] y = data[3]; 529 double[] yStart = data[4]; 530 double[] yEnd = data[5]; 531 double[] xx = new double[x.length]; 532 double[] xxStart = new double[xStart.length]; 533 double[] xxEnd = new double[xEnd.length]; 534 double[] yy = new double[y.length]; 535 double[] yyStart = new double[yStart.length]; 536 double[] yyEnd = new double[yEnd.length]; 537 System.arraycopy(x, 0, xx, 0, x.length); 538 System.arraycopy(xStart, 0, xxStart, 0, xStart.length); 539 System.arraycopy(xEnd, 0, xxEnd, 0, xEnd.length); 540 System.arraycopy(y, 0, yy, 0, y.length); 541 System.arraycopy(yStart, 0, yyStart, 0, yStart.length); 542 System.arraycopy(yEnd, 0, yyEnd, 0, yEnd.length); 543 clone.seriesList.add(i, new double[][] {xx, xxStart, xxEnd, yy, 544 yyStart, yyEnd}); 545 } 546 return clone; 547 } 548 549 }