001/* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2020, 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; 051import org.jfree.chart.util.PublicCloneable; 052 053import org.jfree.data.general.DatasetChangeEvent; 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} 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} to 104 * {@code getSeriesCount() - 1}). 105 * 106 * @return The key for the series. 107 * 108 * @throws IllegalArgumentException if {@code series} 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} to 123 * {@code getSeriesCount() - 1}). 124 * 125 * @return The item count. 126 * 127 * @throws IllegalArgumentException if {@code series} 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} to 143 * {@code getSeriesCount() - 1}). 144 * @param item the item index (in the range {@code 0} to 145 * {@code getItemCount(series)}). 146 * 147 * @return The x-value. 148 * 149 * @throws ArrayIndexOutOfBoundsException if {@code series} is not 150 * within the specified range. 151 * @throws ArrayIndexOutOfBoundsException if {@code item} 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} to 166 * {@code getSeriesCount() - 1}). 167 * @param item the item index (in the range {@code 0} to 168 * {@code getItemCount(series)}). 169 * 170 * @return The y-value. 171 * 172 * @throws ArrayIndexOutOfBoundsException if {@code series} is not 173 * within the specified range. 174 * @throws ArrayIndexOutOfBoundsException if {@code item} 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} to 189 * {@code getSeriesCount() - 1}). 190 * @param item the item index (in the range {@code 0} to 191 * {@code getItemCount(series)}). 192 * 193 * @return The starting x-value. 194 * 195 * @throws ArrayIndexOutOfBoundsException if {@code series} is not 196 * within the specified range. 197 * @throws ArrayIndexOutOfBoundsException if {@code item} 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} to 212 * {@code getSeriesCount() - 1}). 213 * @param item the item index (in the range {@code 0} to 214 * {@code getItemCount(series)}). 215 * 216 * @return The ending x-value. 217 * 218 * @throws ArrayIndexOutOfBoundsException if {@code series} is not 219 * within the specified range. 220 * @throws ArrayIndexOutOfBoundsException if {@code item} 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} to 235 * {@code getSeriesCount() - 1}). 236 * @param item the item index (in the range {@code 0} to 237 * {@code getItemCount(series)}). 238 * 239 * @return The starting y-value. 240 * 241 * @throws ArrayIndexOutOfBoundsException if {@code series} is not 242 * within the specified range. 243 * @throws ArrayIndexOutOfBoundsException if {@code item} 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} to 258 * {@code getSeriesCount() - 1}). 259 * @param item the item index (in the range {@code 0} to 260 * {@code getItemCount(series)}). 261 * 262 * @return The ending y-value. 263 * 264 * @throws ArrayIndexOutOfBoundsException if {@code series} is not 265 * within the specified range. 266 * @throws ArrayIndexOutOfBoundsException if {@code item} 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} to 281 * {@code getSeriesCount() - 1}). 282 * @param item the item index (in the range {@code 0} to 283 * {@code getItemCount(series)}). 284 * 285 * @return The ending x-value. 286 * 287 * @throws ArrayIndexOutOfBoundsException if {@code series} is not 288 * within the specified range. 289 * @throws ArrayIndexOutOfBoundsException if {@code item} 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 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} to 303 * {@code getSeriesCount() - 1}). 304 * @param item the item index (in the range {@code 0} to 305 * {@code getItemCount(series)}). 306 * 307 * @return The ending y-value. 308 * 309 * @throws ArrayIndexOutOfBoundsException if {@code series} is not 310 * within the specified range. 311 * @throws ArrayIndexOutOfBoundsException if {@code item} 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 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} to 325 * {@code getSeriesCount() - 1}). 326 * @param item the item index (in the range {@code 0} to 327 * {@code getItemCount(series)}). 328 * 329 * @return The starting x-value. 330 * 331 * @throws ArrayIndexOutOfBoundsException if {@code series} is not 332 * within the specified range. 333 * @throws ArrayIndexOutOfBoundsException if {@code item} 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 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} to 347 * {@code getSeriesCount() - 1}). 348 * @param item the item index (in the range {@code 0} to 349 * {@code getItemCount(series)}). 350 * 351 * @return The starting y-value. 352 * 353 * @throws ArrayIndexOutOfBoundsException if {@code series} is not 354 * within the specified range. 355 * @throws ArrayIndexOutOfBoundsException if {@code item} 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 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} to 369 * {@code getSeriesCount() - 1}). 370 * @param item the item index (in the range {@code 0} to 371 * {@code getItemCount(series)}). 372 * 373 * @return The x-value. 374 * 375 * @throws ArrayIndexOutOfBoundsException if {@code series} is not 376 * within the specified range. 377 * @throws ArrayIndexOutOfBoundsException if {@code item} 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 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} to 391 * {@code getSeriesCount() - 1}). 392 * @param item the item index (in the range {@code 0} to 393 * {@code getItemCount(series)}). 394 * 395 * @return The y-value. 396 * 397 * @throws ArrayIndexOutOfBoundsException if {@code series} is not 398 * within the specified range. 399 * @throws ArrayIndexOutOfBoundsException if {@code item} 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 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} 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} instance for equality 453 * with an arbitrary object. This method returns {@code true} if and 454 * only if: 455 * <ul> 456 * <li>{@code obj} is not {@code null};</li> 457 * <li>{@code obj} is an instance of {@code DefaultIntervalXYDataset};</li> 458 * <li>both datasets have the same number of series, each containing 459 * exactly the same values.</li> 460 * </ul> 461 * 462 * @param obj the object ({@code null} permitted). 463 * 464 * @return A boolean. 465 */ 466 @Override 467 public boolean equals(Object obj) { 468 if (obj == this) { 469 return true; 470 } 471 if (!(obj instanceof DefaultIntervalXYDataset)) { 472 return false; 473 } 474 DefaultIntervalXYDataset that = (DefaultIntervalXYDataset) obj; 475 if (!this.seriesKeys.equals(that.seriesKeys)) { 476 return false; 477 } 478 for (int i = 0; i < this.seriesList.size(); i++) { 479 double[][] d1 = (double[][]) this.seriesList.get(i); 480 double[][] d2 = (double[][]) that.seriesList.get(i); 481 double[] d1x = d1[0]; 482 double[] d2x = d2[0]; 483 if (!Arrays.equals(d1x, d2x)) { 484 return false; 485 } 486 double[] d1xs = d1[1]; 487 double[] d2xs = d2[1]; 488 if (!Arrays.equals(d1xs, d2xs)) { 489 return false; 490 } 491 double[] d1xe = d1[2]; 492 double[] d2xe = d2[2]; 493 if (!Arrays.equals(d1xe, d2xe)) { 494 return false; 495 } 496 double[] d1y = d1[3]; 497 double[] d2y = d2[3]; 498 if (!Arrays.equals(d1y, d2y)) { 499 return false; 500 } 501 double[] d1ys = d1[4]; 502 double[] d2ys = d2[4]; 503 if (!Arrays.equals(d1ys, d2ys)) { 504 return false; 505 } 506 double[] d1ye = d1[5]; 507 double[] d2ye = d2[5]; 508 if (!Arrays.equals(d1ye, d2ye)) { 509 return false; 510 } 511 } 512 return true; 513 } 514 515 /** 516 * Returns a hash code for this instance. 517 * 518 * @return A hash code. 519 */ 520 @Override 521 public int hashCode() { 522 int result; 523 result = this.seriesKeys.hashCode(); 524 result = 29 * result + this.seriesList.hashCode(); 525 return result; 526 } 527 528 /** 529 * Returns a clone of this dataset. 530 * 531 * @return A clone. 532 * 533 * @throws CloneNotSupportedException if the dataset contains a series with 534 * a key that cannot be cloned. 535 */ 536 @Override 537 public Object clone() throws CloneNotSupportedException { 538 DefaultIntervalXYDataset clone 539 = (DefaultIntervalXYDataset) super.clone(); 540 clone.seriesKeys = new java.util.ArrayList(this.seriesKeys); 541 clone.seriesList = new ArrayList(this.seriesList.size()); 542 for (int i = 0; i < this.seriesList.size(); i++) { 543 double[][] data = (double[][]) this.seriesList.get(i); 544 double[] x = data[0]; 545 double[] xStart = data[1]; 546 double[] xEnd = data[2]; 547 double[] y = data[3]; 548 double[] yStart = data[4]; 549 double[] yEnd = data[5]; 550 double[] xx = new double[x.length]; 551 double[] xxStart = new double[xStart.length]; 552 double[] xxEnd = new double[xEnd.length]; 553 double[] yy = new double[y.length]; 554 double[] yyStart = new double[yStart.length]; 555 double[] yyEnd = new double[yEnd.length]; 556 System.arraycopy(x, 0, xx, 0, x.length); 557 System.arraycopy(xStart, 0, xxStart, 0, xStart.length); 558 System.arraycopy(xEnd, 0, xxEnd, 0, xEnd.length); 559 System.arraycopy(y, 0, yy, 0, y.length); 560 System.arraycopy(yStart, 0, yyStart, 0, yStart.length); 561 System.arraycopy(yEnd, 0, yyEnd, 0, yEnd.length); 562 clone.seriesList.add(i, new double[][] {xx, xxStart, xxEnd, yy, 563 yyStart, yyEnd}); 564 } 565 return clone; 566 } 567 568}