001/* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2022, 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-2022, 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.internal.Args; 043import org.jfree.chart.api.PublicCloneable; 044 045import org.jfree.data.general.DatasetChangeEvent; 046 047/** 048 * A dataset that defines a range (interval) for both the x-values and the 049 * y-values. This implementation uses six arrays to store the x, start-x, 050 * end-x, y, start-y and end-y values. 051 * <br><br> 052 * An alternative implementation of the {@link IntervalXYDataset} interface 053 * is provided by the {@link XYIntervalSeriesCollection} class. 054 * 055 * @since 1.0.3 056 */ 057public class DefaultIntervalXYDataset<S extends Comparable<S>> 058 extends AbstractIntervalXYDataset<S> 059 implements PublicCloneable { 060 061 /** 062 * Storage for the series keys. This list must be kept in sync with the 063 * seriesList. 064 */ 065 private List<S> seriesKeys; 066 067 /** 068 * Storage for the series in the dataset. We use a list because the 069 * order of the series is significant. This list must be kept in sync 070 * with the seriesKeys list. 071 */ 072 private List<double[][]> seriesList; 073 074 /** 075 * Creates a new {@code DefaultIntervalXYDataset} instance, initially 076 * containing no data. 077 */ 078 public DefaultIntervalXYDataset() { 079 this.seriesKeys = new ArrayList<>(); 080 this.seriesList = new ArrayList<>(); 081 } 082 083 /** 084 * Returns the number of series in the dataset. 085 * 086 * @return The series count. 087 */ 088 @Override 089 public int getSeriesCount() { 090 return this.seriesList.size(); 091 } 092 093 /** 094 * Returns the key for a series. 095 * 096 * @param series the series index (in the range {@code 0} to 097 * {@code getSeriesCount() - 1}). 098 * 099 * @return The key for the series. 100 * 101 * @throws IllegalArgumentException if {@code series} is not in the 102 * specified range. 103 */ 104 @Override 105 public S getSeriesKey(int series) { 106 Args.requireInRange(series, "series", 0, this.seriesKeys.size() - 1); 107 return this.seriesKeys.get(series); 108 } 109 110 /** 111 * Returns the number of items in the specified series. 112 * 113 * @param series the series index (in the range {@code 0} to 114 * {@code getSeriesCount() - 1}). 115 * 116 * @return The item count. 117 * 118 * @throws IllegalArgumentException if {@code series} is not in the 119 * specified range. 120 */ 121 @Override 122 public int getItemCount(int series) { 123 Args.requireInRange(series, "series", 0, this.seriesList.size() - 1); 124 double[][] seriesArray = 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 = 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 = 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 = 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 = 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 = 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 = 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(S seriesKey, double[][] data) { 410 Args.nullNotPermitted(seriesKey, "seriesKey"); 411 Args.nullNotPermitted(data, "data"); 412 if (data.length != 6) { 413 throw new IllegalArgumentException( 414 "The 'data' array must have length == 6."); 415 } 416 int length = data[0].length; 417 if (length != data[1].length || length != data[2].length 418 || length != data[3].length || length != data[4].length 419 || length != data[5].length) { 420 throw new IllegalArgumentException( 421 "The 'data' array must contain six arrays with equal length."); 422 } 423 int seriesIndex = indexOf(seriesKey); 424 if (seriesIndex == -1) { // add a new series 425 this.seriesKeys.add(seriesKey); 426 this.seriesList.add(data); 427 } 428 else { // replace an existing series 429 this.seriesList.remove(seriesIndex); 430 this.seriesList.add(seriesIndex, data); 431 } 432 notifyListeners(new DatasetChangeEvent(this, this)); 433 } 434 435 /** 436 * Tests this {@code DefaultIntervalXYDataset} instance for equality 437 * with an arbitrary object. This method returns {@code true} if and 438 * only if: 439 * <ul> 440 * <li>{@code obj} is not {@code null};</li> 441 * <li>{@code obj} is an instance of {@code DefaultIntervalXYDataset};</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} permitted). 447 * 448 * @return A boolean. 449 */ 450 @Override 451 public boolean equals(Object obj) { 452 if (obj == this) { 453 return true; 454 } 455 if (!(obj instanceof DefaultIntervalXYDataset)) { 456 return false; 457 } 458 DefaultIntervalXYDataset<String> that = (DefaultIntervalXYDataset) obj; 459 if (!this.seriesKeys.equals(that.seriesKeys)) { 460 return false; 461 } 462 for (int i = 0; i < this.seriesList.size(); i++) { 463 double[][] d1 = (double[][]) this.seriesList.get(i); 464 double[][] d2 = (double[][]) that.seriesList.get(i); 465 double[] d1x = d1[0]; 466 double[] d2x = d2[0]; 467 if (!Arrays.equals(d1x, d2x)) { 468 return false; 469 } 470 double[] d1xs = d1[1]; 471 double[] d2xs = d2[1]; 472 if (!Arrays.equals(d1xs, d2xs)) { 473 return false; 474 } 475 double[] d1xe = d1[2]; 476 double[] d2xe = d2[2]; 477 if (!Arrays.equals(d1xe, d2xe)) { 478 return false; 479 } 480 double[] d1y = d1[3]; 481 double[] d2y = d2[3]; 482 if (!Arrays.equals(d1y, d2y)) { 483 return false; 484 } 485 double[] d1ys = d1[4]; 486 double[] d2ys = d2[4]; 487 if (!Arrays.equals(d1ys, d2ys)) { 488 return false; 489 } 490 double[] d1ye = d1[5]; 491 double[] d2ye = d2[5]; 492 if (!Arrays.equals(d1ye, d2ye)) { 493 return false; 494 } 495 } 496 return true; 497 } 498 499 /** 500 * Returns a hash code for this instance. 501 * 502 * @return A hash code. 503 */ 504 @Override 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 @Override 521 public Object clone() throws CloneNotSupportedException { 522 DefaultIntervalXYDataset clone 523 = (DefaultIntervalXYDataset) super.clone(); 524 clone.seriesKeys = new ArrayList<>(this.seriesKeys); 525 clone.seriesList = new ArrayList<>(this.seriesList.size()); 526 for (int i = 0; i < this.seriesList.size(); i++) { 527 double[][] data = (double[][]) this.seriesList.get(i); 528 double[] x = data[0]; 529 double[] xStart = data[1]; 530 double[] xEnd = data[2]; 531 double[] y = data[3]; 532 double[] yStart = data[4]; 533 double[] yEnd = data[5]; 534 double[] xx = new double[x.length]; 535 double[] xxStart = new double[xStart.length]; 536 double[] xxEnd = new double[xEnd.length]; 537 double[] yy = new double[y.length]; 538 double[] yyStart = new double[yStart.length]; 539 double[] yyEnd = new double[yEnd.length]; 540 System.arraycopy(x, 0, xx, 0, x.length); 541 System.arraycopy(xStart, 0, xxStart, 0, xStart.length); 542 System.arraycopy(xEnd, 0, xxEnd, 0, xEnd.length); 543 System.arraycopy(y, 0, yy, 0, y.length); 544 System.arraycopy(yStart, 0, yyStart, 0, yStart.length); 545 System.arraycopy(yEnd, 0, yyEnd, 0, yEnd.length); 546 clone.seriesList.add(i, new double[][] {xx, xxStart, xxEnd, yy, 547 yyStart, yyEnd}); 548 } 549 return clone; 550 } 551 552}