Source for org.jfree.data.xy.DefaultIntervalXYDataset

   1: /* ===========================================================
   2:  * JFreeChart : a free chart library for the Java(tm) platform
   3:  * ===========================================================
   4:  *
   5:  * (C) Copyright 2000-2008, by Object Refinery Limited and Contributors.
   6:  *
   7:  * Project Info:  http://www.jfree.org/jfreechart/index.html
   8:  *
   9:  * This library is free software; you can redistribute it and/or modify it
  10:  * under the terms of the GNU Lesser General Public License as published by
  11:  * the Free Software Foundation; either version 2.1 of the License, or
  12:  * (at your option) any later version.
  13:  *
  14:  * This library is distributed in the hope that it will be useful, but
  15:  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
  16:  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
  17:  * License for more details.
  18:  *
  19:  * You should have received a copy of the GNU Lesser General Public
  20:  * License along with this library; if not, write to the Free Software
  21:  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
  22:  * USA.
  23:  *
  24:  * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
  25:  * in the United States and other countries.]
  26:  *
  27:  * -----------------------------
  28:  * DefaultIntervalXYDataset.java
  29:  * -----------------------------
  30:  * (C) Copyright 2006-2008, by Object Refinery Limited and Contributors.
  31:  *
  32:  * Original Author:  David Gilbert (for Object Refinery Limited);
  33:  * Contributor(s):   -;
  34:  *
  35:  * Changes
  36:  * -------
  37:  * 23-Oct-2006 : Version 1 (DG);
  38:  * 02-Nov-2006 : Fixed a problem with adding a new series with the same key
  39:  *               as an existing series (see bug 1589392) (DG);
  40:  * 28-Nov-2006 : New override for clone() (DG);
  41:  * 22-Apr-2008 : Implemented PublicCloneable (DG);
  42:  *
  43:  */
  44: 
  45: package org.jfree.data.xy;
  46: 
  47: import java.util.ArrayList;
  48: import java.util.Arrays;
  49: import java.util.List;
  50: 
  51: import org.jfree.data.general.DatasetChangeEvent;
  52: import org.jfree.util.PublicCloneable;
  53: 
  54: /**
  55:  * A dataset that defines a range (interval) for both the x-values and the
  56:  * y-values.  This implementation uses six arrays to store the x, start-x,
  57:  * end-x, y, start-y and end-y values.
  58:  * <br><br>
  59:  * An alternative implementation of the {@link IntervalXYDataset} interface
  60:  * is provided by the {@link XYIntervalSeriesCollection} class.
  61:  *
  62:  * @since 1.0.3
  63:  */
  64: public class DefaultIntervalXYDataset extends AbstractIntervalXYDataset
  65:         implements PublicCloneable {
  66: 
  67:     /**
  68:      * Storage for the series keys.  This list must be kept in sync with the
  69:      * seriesList.
  70:      */
  71:     private List seriesKeys;
  72: 
  73:     /**
  74:      * Storage for the series in the dataset.  We use a list because the
  75:      * order of the series is significant.  This list must be kept in sync
  76:      * with the seriesKeys list.
  77:      */
  78:     private List seriesList;
  79: 
  80:     /**
  81:      * Creates a new <code>DefaultIntervalXYDataset</code> instance, initially
  82:      * containing no data.
  83:      */
  84:     public DefaultIntervalXYDataset() {
  85:         this.seriesKeys = new java.util.ArrayList();
  86:         this.seriesList = new java.util.ArrayList();
  87:     }
  88: 
  89:     /**
  90:      * Returns the number of series in the dataset.
  91:      *
  92:      * @return The series count.
  93:      */
  94:     public int getSeriesCount() {
  95:         return this.seriesList.size();
  96:     }
  97: 
  98:     /**
  99:      * Returns the key for a series.
 100:      *
 101:      * @param series  the series index (in the range <code>0</code> to
 102:      *     <code>getSeriesCount() - 1</code>).
 103:      *
 104:      * @return The key for the series.
 105:      *
 106:      * @throws IllegalArgumentException if <code>series</code> is not in the
 107:      *     specified range.
 108:      */
 109:     public Comparable getSeriesKey(int series) {
 110:         if ((series < 0) || (series >= getSeriesCount())) {
 111:             throw new IllegalArgumentException("Series index out of bounds");
 112:         }
 113:         return (Comparable) this.seriesKeys.get(series);
 114:     }
 115: 
 116:     /**
 117:      * Returns the number of items in the specified series.
 118:      *
 119:      * @param series  the series index (in the range <code>0</code> to
 120:      *     <code>getSeriesCount() - 1</code>).
 121:      *
 122:      * @return The item count.
 123:      *
 124:      * @throws IllegalArgumentException if <code>series</code> is not in the
 125:      *     specified range.
 126:      */
 127:     public int getItemCount(int series) {
 128:         if ((series < 0) || (series >= getSeriesCount())) {
 129:             throw new IllegalArgumentException("Series index out of bounds");
 130:         }
 131:         double[][] seriesArray = (double[][]) this.seriesList.get(series);
 132:         return seriesArray[0].length;
 133:     }
 134: 
 135:     /**
 136:      * Returns the x-value for an item within a series.
 137:      *
 138:      * @param series  the series index (in the range <code>0</code> to
 139:      *     <code>getSeriesCount() - 1</code>).
 140:      * @param item  the item index (in the range <code>0</code> to
 141:      *     <code>getItemCount(series)</code>).
 142:      *
 143:      * @return The x-value.
 144:      *
 145:      * @throws ArrayIndexOutOfBoundsException if <code>series</code> is not
 146:      *     within the specified range.
 147:      * @throws ArrayIndexOutOfBoundsException if <code>item</code> is not
 148:      *     within the specified range.
 149:      *
 150:      * @see #getX(int, int)
 151:      */
 152:     public double getXValue(int series, int item) {
 153:         double[][] seriesData = (double[][]) this.seriesList.get(series);
 154:         return seriesData[0][item];
 155:     }
 156: 
 157:     /**
 158:      * Returns the y-value for an item within a series.
 159:      *
 160:      * @param series  the series index (in the range <code>0</code> to
 161:      *     <code>getSeriesCount() - 1</code>).
 162:      * @param item  the item index (in the range <code>0</code> to
 163:      *     <code>getItemCount(series)</code>).
 164:      *
 165:      * @return The y-value.
 166:      *
 167:      * @throws ArrayIndexOutOfBoundsException if <code>series</code> is not
 168:      *     within the specified range.
 169:      * @throws ArrayIndexOutOfBoundsException if <code>item</code> is not
 170:      *     within the specified range.
 171:      *
 172:      * @see #getY(int, int)
 173:      */
 174:     public double getYValue(int series, int item) {
 175:         double[][] seriesData = (double[][]) this.seriesList.get(series);
 176:         return seriesData[3][item];
 177:     }
 178: 
 179:     /**
 180:      * Returns the starting x-value for an item within a series.
 181:      *
 182:      * @param series  the series index (in the range <code>0</code> to
 183:      *     <code>getSeriesCount() - 1</code>).
 184:      * @param item  the item index (in the range <code>0</code> to
 185:      *     <code>getItemCount(series)</code>).
 186:      *
 187:      * @return The starting x-value.
 188:      *
 189:      * @throws ArrayIndexOutOfBoundsException if <code>series</code> is not
 190:      *     within the specified range.
 191:      * @throws ArrayIndexOutOfBoundsException if <code>item</code> is not
 192:      *     within the specified range.
 193:      *
 194:      * @see #getStartX(int, int)
 195:      */
 196:     public double getStartXValue(int series, int item) {
 197:         double[][] seriesData = (double[][]) this.seriesList.get(series);
 198:         return seriesData[1][item];
 199:     }
 200: 
 201:     /**
 202:      * Returns the ending x-value for an item within a series.
 203:      *
 204:      * @param series  the series index (in the range <code>0</code> to
 205:      *     <code>getSeriesCount() - 1</code>).
 206:      * @param item  the item index (in the range <code>0</code> to
 207:      *     <code>getItemCount(series)</code>).
 208:      *
 209:      * @return The ending x-value.
 210:      *
 211:      * @throws ArrayIndexOutOfBoundsException if <code>series</code> is not
 212:      *     within the specified range.
 213:      * @throws ArrayIndexOutOfBoundsException if <code>item</code> is not
 214:      *     within the specified range.
 215:      *
 216:      * @see #getEndX(int, int)
 217:      */
 218:     public double getEndXValue(int series, int item) {
 219:         double[][] seriesData = (double[][]) this.seriesList.get(series);
 220:         return seriesData[2][item];
 221:     }
 222: 
 223:     /**
 224:      * Returns the starting y-value for an item within a series.
 225:      *
 226:      * @param series  the series index (in the range <code>0</code> to
 227:      *     <code>getSeriesCount() - 1</code>).
 228:      * @param item  the item index (in the range <code>0</code> to
 229:      *     <code>getItemCount(series)</code>).
 230:      *
 231:      * @return The starting y-value.
 232:      *
 233:      * @throws ArrayIndexOutOfBoundsException if <code>series</code> is not
 234:      *     within the specified range.
 235:      * @throws ArrayIndexOutOfBoundsException if <code>item</code> is not
 236:      *     within the specified range.
 237:      *
 238:      * @see #getStartY(int, int)
 239:      */
 240:     public double getStartYValue(int series, int item) {
 241:         double[][] seriesData = (double[][]) this.seriesList.get(series);
 242:         return seriesData[4][item];
 243:     }
 244: 
 245:     /**
 246:      * Returns the ending y-value for an item within a series.
 247:      *
 248:      * @param series  the series index (in the range <code>0</code> to
 249:      *     <code>getSeriesCount() - 1</code>).
 250:      * @param item  the item index (in the range <code>0</code> to
 251:      *     <code>getItemCount(series)</code>).
 252:      *
 253:      * @return The ending y-value.
 254:      *
 255:      * @throws ArrayIndexOutOfBoundsException if <code>series</code> is not
 256:      *     within the specified range.
 257:      * @throws ArrayIndexOutOfBoundsException if <code>item</code> is not
 258:      *     within the specified range.
 259:      *
 260:      * @see #getEndY(int, int)
 261:      */
 262:     public double getEndYValue(int series, int item) {
 263:         double[][] seriesData = (double[][]) this.seriesList.get(series);
 264:         return seriesData[5][item];
 265:     }
 266: 
 267:     /**
 268:      * Returns the ending x-value for an item within a series.
 269:      *
 270:      * @param series  the series index (in the range <code>0</code> to
 271:      *     <code>getSeriesCount() - 1</code>).
 272:      * @param item  the item index (in the range <code>0</code> to
 273:      *     <code>getItemCount(series)</code>).
 274:      *
 275:      * @return The ending x-value.
 276:      *
 277:      * @throws ArrayIndexOutOfBoundsException if <code>series</code> is not
 278:      *     within the specified range.
 279:      * @throws ArrayIndexOutOfBoundsException if <code>item</code> is not
 280:      *     within the specified range.
 281:      *
 282:      * @see #getEndXValue(int, int)
 283:      */
 284:     public Number getEndX(int series, int item) {
 285:         return new Double(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</code> to
 292:      *     <code>getSeriesCount() - 1</code>).
 293:      * @param item  the item index (in the range <code>0</code> to
 294:      *     <code>getItemCount(series)</code>).
 295:      *
 296:      * @return The ending y-value.
 297:      *
 298:      * @throws ArrayIndexOutOfBoundsException if <code>series</code> is not
 299:      *     within the specified range.
 300:      * @throws ArrayIndexOutOfBoundsException if <code>item</code> is not
 301:      *     within the specified range.
 302:      *
 303:      * @see #getEndYValue(int, int)
 304:      */
 305:     public Number getEndY(int series, int item) {
 306:         return new Double(getEndYValue(series, item));
 307:     }
 308: 
 309:     /**
 310:      * Returns the starting x-value for an item within a series.
 311:      *
 312:      * @param series  the series index (in the range <code>0</code> to
 313:      *     <code>getSeriesCount() - 1</code>).
 314:      * @param item  the item index (in the range <code>0</code> to
 315:      *     <code>getItemCount(series)</code>).
 316:      *
 317:      * @return The starting x-value.
 318:      *
 319:      * @throws ArrayIndexOutOfBoundsException if <code>series</code> is not
 320:      *     within the specified range.
 321:      * @throws ArrayIndexOutOfBoundsException if <code>item</code> is not
 322:      *     within the specified range.
 323:      *
 324:      * @see #getStartXValue(int, int)
 325:      */
 326:     public Number getStartX(int series, int item) {
 327:         return new Double(getStartXValue(series, item));
 328:     }
 329: 
 330:     /**
 331:      * Returns the starting y-value for an item within a series.
 332:      *
 333:      * @param series  the series index (in the range <code>0</code> to
 334:      *     <code>getSeriesCount() - 1</code>).
 335:      * @param item  the item index (in the range <code>0</code> to
 336:      *     <code>getItemCount(series)</code>).
 337:      *
 338:      * @return The starting y-value.
 339:      *
 340:      * @throws ArrayIndexOutOfBoundsException if <code>series</code> is not
 341:      *     within the specified range.
 342:      * @throws ArrayIndexOutOfBoundsException if <code>item</code> is not
 343:      *     within the specified range.
 344:      *
 345:      * @see #getStartYValue(int, int)
 346:      */
 347:     public Number getStartY(int series, int item) {
 348:         return new Double(getStartYValue(series, item));
 349:     }
 350: 
 351:     /**
 352:      * Returns the x-value for an item within a series.
 353:      *
 354:      * @param series  the series index (in the range <code>0</code> to
 355:      *     <code>getSeriesCount() - 1</code>).
 356:      * @param item  the item index (in the range <code>0</code> to
 357:      *     <code>getItemCount(series)</code>).
 358:      *
 359:      * @return The x-value.
 360:      *
 361:      * @throws ArrayIndexOutOfBoundsException if <code>series</code> is not
 362:      *     within the specified range.
 363:      * @throws ArrayIndexOutOfBoundsException if <code>item</code> is not
 364:      *     within the specified range.
 365:      *
 366:      * @see #getXValue(int, int)
 367:      */
 368:     public Number getX(int series, int item) {
 369:         return new Double(getXValue(series, item));
 370:     }
 371: 
 372:     /**
 373:      * Returns the y-value for an item within a series.
 374:      *
 375:      * @param series  the series index (in the range <code>0</code> to
 376:      *     <code>getSeriesCount() - 1</code>).
 377:      * @param item  the item index (in the range <code>0</code> to
 378:      *     <code>getItemCount(series)</code>).
 379:      *
 380:      * @return The y-value.
 381:      *
 382:      * @throws ArrayIndexOutOfBoundsException if <code>series</code> is not
 383:      *     within the specified range.
 384:      * @throws ArrayIndexOutOfBoundsException if <code>item</code> is not
 385:      *     within the specified range.
 386:      *
 387:      * @see #getYValue(int, int)
 388:      */
 389:     public Number getY(int series, int item) {
 390:         return new Double(getYValue(series, item));
 391:     }
 392: 
 393:     /**
 394:      * Adds a series or if a series with the same key already exists replaces
 395:      * the data for that series, then sends a {@link DatasetChangeEvent} to
 396:      * all registered listeners.
 397:      *
 398:      * @param seriesKey  the series key (<code>null</code> not permitted).
 399:      * @param data  the data (must be an array with length 6, containing six
 400:      *     arrays of equal length, the first containing the x-values and the
 401:      *     second containing the y-values).
 402:      */
 403:     public void addSeries(Comparable seriesKey, double[][] data) {
 404:         if (seriesKey == null) {
 405:             throw new IllegalArgumentException(
 406:                     "The 'seriesKey' cannot be null.");
 407:         }
 408:         if (data == null) {
 409:             throw new IllegalArgumentException("The 'data' is null.");
 410:         }
 411:         if (data.length != 6) {
 412:             throw new IllegalArgumentException(
 413:                     "The 'data' array must have length == 6.");
 414:         }
 415:         int length = data[0].length;
 416:         if (length != data[1].length || length != data[2].length
 417:                 || length != data[3].length || length != data[4].length
 418:                 || length != data[5].length) {
 419:             throw new IllegalArgumentException(
 420:                 "The 'data' array must contain two arrays with equal length.");
 421:         }
 422:         int seriesIndex = indexOf(seriesKey);
 423:         if (seriesIndex == -1) {  // add a new series
 424:             this.seriesKeys.add(seriesKey);
 425:             this.seriesList.add(data);
 426:         }
 427:         else {  // replace an existing series
 428:             this.seriesList.remove(seriesIndex);
 429:             this.seriesList.add(seriesIndex, data);
 430:         }
 431:         notifyListeners(new DatasetChangeEvent(this, this));
 432:     }
 433: 
 434:     /**
 435:      * Tests this <code>DefaultIntervalXYDataset</code> instance for equality
 436:      * with an arbitrary object.  This method returns <code>true</code> if and
 437:      * only if:
 438:      * <ul>
 439:      * <li><code>obj</code> is not <code>null</code>;</li>
 440:      * <li><code>obj</code> is an instance of
 441:      *         <code>DefaultIntervalXYDataset</code>;</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</code> permitted).
 447:      *
 448:      * @return A boolean.
 449:      */
 450:     public boolean equals(Object obj) {
 451:         if (obj == this) {
 452:             return true;
 453:         }
 454:         if (!(obj instanceof DefaultIntervalXYDataset)) {
 455:             return false;
 456:         }
 457:         DefaultIntervalXYDataset that = (DefaultIntervalXYDataset) obj;
 458:         if (!this.seriesKeys.equals(that.seriesKeys)) {
 459:             return false;
 460:         }
 461:         for (int i = 0; i < this.seriesList.size(); i++) {
 462:             double[][] d1 = (double[][]) this.seriesList.get(i);
 463:             double[][] d2 = (double[][]) that.seriesList.get(i);
 464:             double[] d1x = d1[0];
 465:             double[] d2x = d2[0];
 466:             if (!Arrays.equals(d1x, d2x)) {
 467:                 return false;
 468:             }
 469:             double[] d1xs = d1[1];
 470:             double[] d2xs = d2[1];
 471:             if (!Arrays.equals(d1xs, d2xs)) {
 472:                 return false;
 473:             }
 474:             double[] d1xe = d1[2];
 475:             double[] d2xe = d2[2];
 476:             if (!Arrays.equals(d1xe, d2xe)) {
 477:                 return false;
 478:             }
 479:             double[] d1y = d1[3];
 480:             double[] d2y = d2[3];
 481:             if (!Arrays.equals(d1y, d2y)) {
 482:                 return false;
 483:             }
 484:             double[] d1ys = d1[4];
 485:             double[] d2ys = d2[4];
 486:             if (!Arrays.equals(d1ys, d2ys)) {
 487:                 return false;
 488:             }
 489:             double[] d1ye = d1[5];
 490:             double[] d2ye = d2[5];
 491:             if (!Arrays.equals(d1ye, d2ye)) {
 492:                 return false;
 493:             }
 494:         }
 495:         return true;
 496:     }
 497: 
 498:     /**
 499:      * Returns a hash code for this instance.
 500:      *
 501:      * @return A hash code.
 502:      */
 503:     public int hashCode() {
 504:         int result;
 505:         result = this.seriesKeys.hashCode();
 506:         result = 29 * result + this.seriesList.hashCode();
 507:         return result;
 508:     }
 509: 
 510:     /**
 511:      * Returns a clone of this dataset.
 512:      *
 513:      * @return A clone.
 514:      *
 515:      * @throws CloneNotSupportedException if the dataset contains a series with
 516:      *         a key that cannot be cloned.
 517:      */
 518:     public Object clone() throws CloneNotSupportedException {
 519:         DefaultIntervalXYDataset clone
 520:                 = (DefaultIntervalXYDataset) super.clone();
 521:         clone.seriesKeys = new java.util.ArrayList(this.seriesKeys);
 522:         clone.seriesList = new ArrayList(this.seriesList.size());
 523:         for (int i = 0; i < this.seriesList.size(); i++) {
 524:             double[][] data = (double[][]) this.seriesList.get(i);
 525:             double[] x = data[0];
 526:             double[] xStart = data[1];
 527:             double[] xEnd = data[2];
 528:             double[] y = data[3];
 529:             double[] yStart = data[4];
 530:             double[] yEnd = data[5];
 531:             double[] xx = new double[x.length];
 532:             double[] xxStart = new double[xStart.length];
 533:             double[] xxEnd = new double[xEnd.length];
 534:             double[] yy = new double[y.length];
 535:             double[] yyStart = new double[yStart.length];
 536:             double[] yyEnd = new double[yEnd.length];
 537:             System.arraycopy(x, 0, xx, 0, x.length);
 538:             System.arraycopy(xStart, 0, xxStart, 0, xStart.length);
 539:             System.arraycopy(xEnd, 0, xxEnd, 0, xEnd.length);
 540:             System.arraycopy(y, 0, yy, 0, y.length);
 541:             System.arraycopy(yStart, 0, yyStart, 0, yStart.length);
 542:             System.arraycopy(yEnd, 0, yyEnd, 0, yEnd.length);
 543:             clone.seriesList.add(i, new double[][] {xx, xxStart, xxEnd, yy,
 544:                     yyStart, yyEnd});
 545:         }
 546:         return clone;
 547:     }
 548: 
 549: }