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