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