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-2007, 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, 2007, 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:  *
  42:  */
  43: 
  44: package org.jfree.data.xy;
  45: 
  46: import java.util.ArrayList;
  47: import java.util.Arrays;
  48: import java.util.List;
  49: 
  50: import org.jfree.data.general.DatasetChangeEvent;
  51: 
  52: /**
  53:  * A dataset that defines a range (interval) for both the x-values and the
  54:  * y-values.  This implementation uses six arrays to store the x, start-x,
  55:  * end-x, y, start-y and end-y values.
  56:  * <br><br>
  57:  * An alternative implementation of the {@link IntervalXYDataset} interface
  58:  * is provided by the {@link XYIntervalSeriesCollection} class.
  59:  * 
  60:  * @since 1.0.3
  61:  */
  62: public class DefaultIntervalXYDataset extends AbstractIntervalXYDataset {
  63: 
  64:     /**
  65:      * Storage for the series keys.  This list must be kept in sync with the
  66:      * seriesList.
  67:      */
  68:     private List seriesKeys;
  69:     
  70:     /** 
  71:      * Storage for the series in the dataset.  We use a list because the
  72:      * order of the series is significant.  This list must be kept in sync 
  73:      * with the seriesKeys list.
  74:      */ 
  75:     private List seriesList;
  76:     
  77:     /**
  78:      * Creates a new <code>DefaultIntervalXYDataset</code> instance, initially 
  79:      * containing no data.
  80:      */
  81:     public DefaultIntervalXYDataset() {
  82:         this.seriesKeys = new java.util.ArrayList();
  83:         this.seriesList = new java.util.ArrayList();    
  84:     }
  85: 
  86:     /**
  87:      * Returns the number of series in the dataset.
  88:      *
  89:      * @return The series count.
  90:      */
  91:     public int getSeriesCount() {
  92:         return this.seriesList.size();
  93:     }
  94: 
  95:     /**
  96:      * Returns the key for a series.  
  97:      *
  98:      * @param series  the series index (in the range <code>0</code> to 
  99:      *     <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: }