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