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 * CombinedDataset.java 029 * -------------------- 030 * (C) Copyright 2001-2007, by Bill Kelemen and Contributors. 031 * 032 * Original Author: Bill Kelemen; 033 * Contributor(s): David Gilbert (for Object Refinery Limited); 034 * 035 * Changes 036 * ------- 037 * 06-Dec-2001 : Version 1 (BK); 038 * 27-Dec-2001 : Fixed bug in getChildPosition method (BK); 039 * 29-Dec-2001 : Fixed bug in getChildPosition method with complex 040 * CombinePlot (BK); 041 * 05-Feb-2002 : Small addition to the interface HighLowDataset, as requested 042 * by Sylvain Vieujot (DG); 043 * 14-Feb-2002 : Added bug fix for IntervalXYDataset methods, submitted by 044 * Gyula Kun-Szabo (DG); 045 * 11-Jun-2002 : Updated for change in event constructor (DG); 046 * 04-Oct-2002 : Fixed errors reported by Checkstyle (DG); 047 * 06-May-2004 : Now extends AbstractIntervalXYDataset and added other methods 048 * that return double primitives (DG); 049 * 15-Jul-2004 : Switched getX() with getXValue() and getY() with 050 * getYValue() (DG); 051 * ------------- JFREECHART 1.0.x --------------------------------------------- 052 * 02-Feb-2007 : Removed author tags from all over JFreeChart sources (DG); 053 * 054 */ 055 056 package org.jfree.data.general; 057 058 import java.util.List; 059 060 import org.jfree.data.xy.AbstractIntervalXYDataset; 061 import org.jfree.data.xy.IntervalXYDataset; 062 import org.jfree.data.xy.OHLCDataset; 063 import org.jfree.data.xy.XYDataset; 064 065 /** 066 * This class can combine instances of {@link XYDataset}, {@link OHLCDataset} 067 * and {@link IntervalXYDataset} together exposing the union of all the series 068 * under one dataset. 069 */ 070 public class CombinedDataset extends AbstractIntervalXYDataset 071 implements XYDataset, 072 OHLCDataset, 073 IntervalXYDataset, 074 CombinationDataset { 075 076 /** Storage for the datasets we combine. */ 077 private List datasetInfo = new java.util.ArrayList(); 078 079 /** 080 * Default constructor for an empty combination. 081 */ 082 public CombinedDataset() { 083 super(); 084 } 085 086 /** 087 * Creates a CombinedDataset initialized with an array of SeriesDatasets. 088 * 089 * @param data array of SeriesDataset that contains the SeriesDatasets to 090 * combine. 091 */ 092 public CombinedDataset(SeriesDataset[] data) { 093 add(data); 094 } 095 096 /** 097 * Adds one SeriesDataset to the combination. Listeners are notified of the 098 * change. 099 * 100 * @param data the SeriesDataset to add. 101 */ 102 public void add(SeriesDataset data) { 103 fastAdd(data); 104 DatasetChangeEvent event = new DatasetChangeEvent(this, this); 105 notifyListeners(event); 106 } 107 108 /** 109 * Adds an array of SeriesDataset's to the combination. Listeners are 110 * notified of the change. 111 * 112 * @param data array of SeriesDataset to add 113 */ 114 public void add(SeriesDataset[] data) { 115 116 for (int i = 0; i < data.length; i++) { 117 fastAdd(data[i]); 118 } 119 DatasetChangeEvent event = new DatasetChangeEvent(this, this); 120 notifyListeners(event); 121 122 } 123 124 /** 125 * Adds one series from a SeriesDataset to the combination. Listeners are 126 * notified of the change. 127 * 128 * @param data the SeriesDataset where series is contained 129 * @param series series to add 130 */ 131 public void add(SeriesDataset data, int series) { 132 add(new SubSeriesDataset(data, series)); 133 } 134 135 /** 136 * Fast add of a SeriesDataset. Does not notify listeners of the change. 137 * 138 * @param data SeriesDataset to add 139 */ 140 private void fastAdd(SeriesDataset data) { 141 for (int i = 0; i < data.getSeriesCount(); i++) { 142 this.datasetInfo.add(new DatasetInfo(data, i)); 143 } 144 } 145 146 /////////////////////////////////////////////////////////////////////////// 147 // From SeriesDataset 148 /////////////////////////////////////////////////////////////////////////// 149 150 /** 151 * Returns the number of series in the dataset. 152 * 153 * @return The number of series in the dataset. 154 */ 155 public int getSeriesCount() { 156 return this.datasetInfo.size(); 157 } 158 159 /** 160 * Returns the key for a series. 161 * 162 * @param series the series (zero-based index). 163 * 164 * @return The key for a series. 165 */ 166 public Comparable getSeriesKey(int series) { 167 DatasetInfo di = getDatasetInfo(series); 168 return di.data.getSeriesKey(di.series); 169 } 170 171 /////////////////////////////////////////////////////////////////////////// 172 // From XYDataset 173 /////////////////////////////////////////////////////////////////////////// 174 175 /** 176 * Returns the X-value for the specified series and item. 177 * <P> 178 * Note: throws <code>ClassCastException</code> if the series is not from 179 * a {@link XYDataset}. 180 * 181 * @param series the index of the series of interest (zero-based). 182 * @param item the index of the item of interest (zero-based). 183 * 184 * @return The X-value for the specified series and item. 185 */ 186 public Number getX(int series, int item) { 187 DatasetInfo di = getDatasetInfo(series); 188 return ((XYDataset) di.data).getX(di.series, item); 189 } 190 191 /** 192 * Returns the Y-value for the specified series and item. 193 * <P> 194 * Note: throws <code>ClassCastException</code> if the series is not from 195 * a {@link XYDataset}. 196 * 197 * @param series the index of the series of interest (zero-based). 198 * @param item the index of the item of interest (zero-based). 199 * 200 * @return The Y-value for the specified series and item. 201 */ 202 public Number getY(int series, int item) { 203 DatasetInfo di = getDatasetInfo(series); 204 return ((XYDataset) di.data).getY(di.series, item); 205 } 206 207 /** 208 * Returns the number of items in a series. 209 * <P> 210 * Note: throws <code>ClassCastException</code> if the series is not from 211 * a {@link XYDataset}. 212 * 213 * @param series the index of the series of interest (zero-based). 214 * 215 * @return The number of items in a series. 216 */ 217 public int getItemCount(int series) { 218 DatasetInfo di = getDatasetInfo(series); 219 return ((XYDataset) di.data).getItemCount(di.series); 220 } 221 222 /////////////////////////////////////////////////////////////////////////// 223 // From HighLowDataset 224 /////////////////////////////////////////////////////////////////////////// 225 226 /** 227 * Returns the high-value for the specified series and item. 228 * <P> 229 * Note: throws <code>ClassCastException</code> if the series is not from a 230 * {@link OHLCDataset}. 231 * 232 * @param series the index of the series of interest (zero-based). 233 * @param item the index of the item of interest (zero-based). 234 * 235 * @return The high-value for the specified series and item. 236 */ 237 public Number getHigh(int series, int item) { 238 DatasetInfo di = getDatasetInfo(series); 239 return ((OHLCDataset) di.data).getHigh(di.series, item); 240 } 241 242 /** 243 * Returns the high-value (as a double primitive) for an item within a 244 * series. 245 * 246 * @param series the series (zero-based index). 247 * @param item the item (zero-based index). 248 * 249 * @return The high-value. 250 */ 251 public double getHighValue(int series, int item) { 252 double result = Double.NaN; 253 Number high = getHigh(series, item); 254 if (high != null) { 255 result = high.doubleValue(); 256 } 257 return result; 258 } 259 260 /** 261 * Returns the low-value for the specified series and item. 262 * <P> 263 * Note: throws <code>ClassCastException</code> if the series is not from a 264 * {@link OHLCDataset}. 265 * 266 * @param series the index of the series of interest (zero-based). 267 * @param item the index of the item of interest (zero-based). 268 * 269 * @return The low-value for the specified series and item. 270 */ 271 public Number getLow(int series, int item) { 272 DatasetInfo di = getDatasetInfo(series); 273 return ((OHLCDataset) di.data).getLow(di.series, item); 274 } 275 276 /** 277 * Returns the low-value (as a double primitive) for an item within a 278 * series. 279 * 280 * @param series the series (zero-based index). 281 * @param item the item (zero-based index). 282 * 283 * @return The low-value. 284 */ 285 public double getLowValue(int series, int item) { 286 double result = Double.NaN; 287 Number low = getLow(series, item); 288 if (low != null) { 289 result = low.doubleValue(); 290 } 291 return result; 292 } 293 294 /** 295 * Returns the open-value for the specified series and item. 296 * <P> 297 * Note: throws <code>ClassCastException</code> if the series is not from a 298 * {@link OHLCDataset}. 299 * 300 * @param series the index of the series of interest (zero-based). 301 * @param item the index of the item of interest (zero-based). 302 * 303 * @return The open-value for the specified series and item. 304 */ 305 public Number getOpen(int series, int item) { 306 DatasetInfo di = getDatasetInfo(series); 307 return ((OHLCDataset) di.data).getOpen(di.series, item); 308 } 309 310 /** 311 * Returns the open-value (as a double primitive) for an item within a 312 * series. 313 * 314 * @param series the series (zero-based index). 315 * @param item the item (zero-based index). 316 * 317 * @return The open-value. 318 */ 319 public double getOpenValue(int series, int item) { 320 double result = Double.NaN; 321 Number open = getOpen(series, item); 322 if (open != null) { 323 result = open.doubleValue(); 324 } 325 return result; 326 } 327 328 /** 329 * Returns the close-value for the specified series and item. 330 * <P> 331 * Note: throws <code>ClassCastException</code> if the series is not from a 332 * {@link OHLCDataset}. 333 * 334 * @param series the index of the series of interest (zero-based). 335 * @param item the index of the item of interest (zero-based). 336 * 337 * @return The close-value for the specified series and item. 338 */ 339 public Number getClose(int series, int item) { 340 DatasetInfo di = getDatasetInfo(series); 341 return ((OHLCDataset) di.data).getClose(di.series, item); 342 } 343 344 /** 345 * Returns the close-value (as a double primitive) for an item within a 346 * series. 347 * 348 * @param series the series (zero-based index). 349 * @param item the item (zero-based index). 350 * 351 * @return The close-value. 352 */ 353 public double getCloseValue(int series, int item) { 354 double result = Double.NaN; 355 Number close = getClose(series, item); 356 if (close != null) { 357 result = close.doubleValue(); 358 } 359 return result; 360 } 361 362 /** 363 * Returns the volume value for the specified series and item. 364 * <P> 365 * Note: throws <code>ClassCastException</code> if the series is not from a 366 * {@link OHLCDataset}. 367 * 368 * @param series the index of the series of interest (zero-based). 369 * @param item the index of the item of interest (zero-based). 370 * 371 * @return The volume value for the specified series and item. 372 */ 373 public Number getVolume(int series, int item) { 374 DatasetInfo di = getDatasetInfo(series); 375 return ((OHLCDataset) di.data).getVolume(di.series, item); 376 } 377 378 /** 379 * Returns the volume-value (as a double primitive) for an item within a 380 * series. 381 * 382 * @param series the series (zero-based index). 383 * @param item the item (zero-based index). 384 * 385 * @return The volume-value. 386 */ 387 public double getVolumeValue(int series, int item) { 388 double result = Double.NaN; 389 Number volume = getVolume(series, item); 390 if (volume != null) { 391 result = volume.doubleValue(); 392 } 393 return result; 394 } 395 396 /////////////////////////////////////////////////////////////////////////// 397 // From IntervalXYDataset 398 /////////////////////////////////////////////////////////////////////////// 399 400 /** 401 * Returns the starting X value for the specified series and item. 402 * 403 * @param series the index of the series of interest (zero-based). 404 * @param item the index of the item of interest (zero-based). 405 * 406 * @return The value. 407 */ 408 public Number getStartX(int series, int item) { 409 DatasetInfo di = getDatasetInfo(series); 410 if (di.data instanceof IntervalXYDataset) { 411 return ((IntervalXYDataset) di.data).getStartX(di.series, item); 412 } 413 else { 414 return getX(series, item); 415 } 416 } 417 418 /** 419 * Returns the ending X value for the specified series and item. 420 * 421 * @param series the index of the series of interest (zero-based). 422 * @param item the index of the item of interest (zero-based). 423 * 424 * @return The value. 425 */ 426 public Number getEndX(int series, int item) { 427 DatasetInfo di = getDatasetInfo(series); 428 if (di.data instanceof IntervalXYDataset) { 429 return ((IntervalXYDataset) di.data).getEndX(di.series, item); 430 } 431 else { 432 return getX(series, item); 433 } 434 } 435 436 /** 437 * Returns the starting Y value for the specified series and item. 438 * 439 * @param series the index of the series of interest (zero-based). 440 * @param item the index of the item of interest (zero-based). 441 * 442 * @return The starting Y value for the specified series and item. 443 */ 444 public Number getStartY(int series, int item) { 445 DatasetInfo di = getDatasetInfo(series); 446 if (di.data instanceof IntervalXYDataset) { 447 return ((IntervalXYDataset) di.data).getStartY(di.series, item); 448 } 449 else { 450 return getY(series, item); 451 } 452 } 453 454 /** 455 * Returns the ending Y value for the specified series and item. 456 * 457 * @param series the index of the series of interest (zero-based). 458 * @param item the index of the item of interest (zero-based). 459 * 460 * @return The ending Y value for the specified series and item. 461 */ 462 public Number getEndY(int series, int item) { 463 DatasetInfo di = getDatasetInfo(series); 464 if (di.data instanceof IntervalXYDataset) { 465 return ((IntervalXYDataset) di.data).getEndY(di.series, item); 466 } 467 else { 468 return getY(series, item); 469 } 470 } 471 472 /////////////////////////////////////////////////////////////////////////// 473 // New methods from CombinationDataset 474 /////////////////////////////////////////////////////////////////////////// 475 476 /** 477 * Returns the parent Dataset of this combination. If there is more than 478 * one parent, or a child is found that is not a CombinationDataset, then 479 * returns <code>null</code>. 480 * 481 * @return The parent Dataset of this combination or <code>null</code>. 482 */ 483 public SeriesDataset getParent() { 484 485 SeriesDataset parent = null; 486 for (int i = 0; i < this.datasetInfo.size(); i++) { 487 SeriesDataset child = getDatasetInfo(i).data; 488 if (child instanceof CombinationDataset) { 489 SeriesDataset childParent 490 = ((CombinationDataset) child).getParent(); 491 if (parent == null) { 492 parent = childParent; 493 } 494 else if (parent != childParent) { 495 return null; 496 } 497 } 498 else { 499 return null; 500 } 501 } 502 return parent; 503 504 } 505 506 /** 507 * Returns a map or indirect indexing form our series into parent's series. 508 * Prior to calling this method, the client should check getParent() to make 509 * sure the CombinationDataset uses the same parent. If not, the map 510 * returned by this method will be invalid or null. 511 * 512 * @return A map or indirect indexing form our series into parent's series. 513 * 514 * @see #getParent() 515 */ 516 public int[] getMap() { 517 518 int[] map = null; 519 for (int i = 0; i < this.datasetInfo.size(); i++) { 520 SeriesDataset child = getDatasetInfo(i).data; 521 if (child instanceof CombinationDataset) { 522 int[] childMap = ((CombinationDataset) child).getMap(); 523 if (childMap == null) { 524 return null; 525 } 526 map = joinMap(map, childMap); 527 } 528 else { 529 return null; 530 } 531 } 532 return map; 533 } 534 535 /////////////////////////////////////////////////////////////////////////// 536 // New Methods 537 /////////////////////////////////////////////////////////////////////////// 538 539 /** 540 * Returns the child position. 541 * 542 * @param child the child dataset. 543 * 544 * @return The position. 545 */ 546 public int getChildPosition(Dataset child) { 547 548 int n = 0; 549 for (int i = 0; i < this.datasetInfo.size(); i++) { 550 SeriesDataset childDataset = getDatasetInfo(i).data; 551 if (childDataset instanceof CombinedDataset) { 552 int m = ((CombinedDataset) childDataset) 553 .getChildPosition(child); 554 if (m >= 0) { 555 return n + m; 556 } 557 n++; 558 } 559 else { 560 if (child == childDataset) { 561 return n; 562 } 563 n++; 564 } 565 } 566 return -1; 567 } 568 569 /////////////////////////////////////////////////////////////////////////// 570 // Private 571 /////////////////////////////////////////////////////////////////////////// 572 573 /** 574 * Returns the DatasetInfo object associated with the series. 575 * 576 * @param series the index of the series. 577 * 578 * @return The DatasetInfo object associated with the series. 579 */ 580 private DatasetInfo getDatasetInfo(int series) { 581 return (DatasetInfo) this.datasetInfo.get(series); 582 } 583 584 /** 585 * Joins two map arrays (int[]) together. 586 * 587 * @param a the first array. 588 * @param b the second array. 589 * 590 * @return A copy of { a[], b[] }. 591 */ 592 private int[] joinMap(int[] a, int[] b) { 593 if (a == null) { 594 return b; 595 } 596 if (b == null) { 597 return a; 598 } 599 int[] result = new int[a.length + b.length]; 600 System.arraycopy(a, 0, result, 0, a.length); 601 System.arraycopy(b, 0, result, a.length, b.length); 602 return result; 603 } 604 605 /** 606 * Private class to store as pairs (SeriesDataset, series) for all combined 607 * series. 608 */ 609 private class DatasetInfo { 610 611 /** The dataset. */ 612 private SeriesDataset data; 613 614 /** The series. */ 615 private int series; 616 617 /** 618 * Creates a new dataset info record. 619 * 620 * @param data the dataset. 621 * @param series the series. 622 */ 623 DatasetInfo(SeriesDataset data, int series) { 624 this.data = data; 625 this.series = series; 626 } 627 } 628 629 }