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