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