001 /* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2009, 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-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 057 package org.jfree.data.general; 058 059 import java.util.List; 060 061 import org.jfree.data.xy.AbstractIntervalXYDataset; 062 import org.jfree.data.xy.IntervalXYDataset; 063 import org.jfree.data.xy.OHLCDataset; 064 import 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 */ 075 public 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 public int getSeriesCount() { 159 return this.datasetInfo.size(); 160 } 161 162 /** 163 * Returns the key for a series. 164 * 165 * @param series the series (zero-based index). 166 * 167 * @return The key for a series. 168 */ 169 public Comparable getSeriesKey(int series) { 170 DatasetInfo di = getDatasetInfo(series); 171 return di.data.getSeriesKey(di.series); 172 } 173 174 /////////////////////////////////////////////////////////////////////////// 175 // From XYDataset 176 /////////////////////////////////////////////////////////////////////////// 177 178 /** 179 * Returns the X-value for the specified series and item. 180 * <P> 181 * Note: throws <code>ClassCastException</code> if the series is not from 182 * a {@link XYDataset}. 183 * 184 * @param series the index of the series of interest (zero-based). 185 * @param item the index of the item of interest (zero-based). 186 * 187 * @return The X-value for the specified series and item. 188 */ 189 public Number getX(int series, int item) { 190 DatasetInfo di = getDatasetInfo(series); 191 return ((XYDataset) di.data).getX(di.series, item); 192 } 193 194 /** 195 * Returns the Y-value for the specified series and item. 196 * <P> 197 * Note: throws <code>ClassCastException</code> if the series is not from 198 * a {@link XYDataset}. 199 * 200 * @param series the index of the series of interest (zero-based). 201 * @param item the index of the item of interest (zero-based). 202 * 203 * @return The Y-value for the specified series and item. 204 */ 205 public Number getY(int series, int item) { 206 DatasetInfo di = getDatasetInfo(series); 207 return ((XYDataset) di.data).getY(di.series, item); 208 } 209 210 /** 211 * Returns the number of items in a series. 212 * <P> 213 * Note: throws <code>ClassCastException</code> if the series is not from 214 * a {@link XYDataset}. 215 * 216 * @param series the index of the series of interest (zero-based). 217 * 218 * @return The number of items in a series. 219 */ 220 public int getItemCount(int series) { 221 DatasetInfo di = getDatasetInfo(series); 222 return ((XYDataset) di.data).getItemCount(di.series); 223 } 224 225 /////////////////////////////////////////////////////////////////////////// 226 // From HighLowDataset 227 /////////////////////////////////////////////////////////////////////////// 228 229 /** 230 * Returns the high-value for the specified series and item. 231 * <P> 232 * Note: throws <code>ClassCastException</code> if the series is not from a 233 * {@link OHLCDataset}. 234 * 235 * @param series the index of the series of interest (zero-based). 236 * @param item the index of the item of interest (zero-based). 237 * 238 * @return The high-value for the specified series and item. 239 */ 240 public Number getHigh(int series, int item) { 241 DatasetInfo di = getDatasetInfo(series); 242 return ((OHLCDataset) di.data).getHigh(di.series, item); 243 } 244 245 /** 246 * Returns the high-value (as a double primitive) for an item within a 247 * series. 248 * 249 * @param series the series (zero-based index). 250 * @param item the item (zero-based index). 251 * 252 * @return The high-value. 253 */ 254 public double getHighValue(int series, int item) { 255 double result = Double.NaN; 256 Number high = getHigh(series, item); 257 if (high != null) { 258 result = high.doubleValue(); 259 } 260 return result; 261 } 262 263 /** 264 * Returns the low-value for the specified series and item. 265 * <P> 266 * Note: throws <code>ClassCastException</code> if the series is not from a 267 * {@link OHLCDataset}. 268 * 269 * @param series the index of the series of interest (zero-based). 270 * @param item the index of the item of interest (zero-based). 271 * 272 * @return The low-value for the specified series and item. 273 */ 274 public Number getLow(int series, int item) { 275 DatasetInfo di = getDatasetInfo(series); 276 return ((OHLCDataset) di.data).getLow(di.series, item); 277 } 278 279 /** 280 * Returns the low-value (as a double primitive) for an item within a 281 * series. 282 * 283 * @param series the series (zero-based index). 284 * @param item the item (zero-based index). 285 * 286 * @return The low-value. 287 */ 288 public double getLowValue(int series, int item) { 289 double result = Double.NaN; 290 Number low = getLow(series, item); 291 if (low != null) { 292 result = low.doubleValue(); 293 } 294 return result; 295 } 296 297 /** 298 * Returns the open-value for the specified series and item. 299 * <P> 300 * Note: throws <code>ClassCastException</code> if the series is not from a 301 * {@link OHLCDataset}. 302 * 303 * @param series the index of the series of interest (zero-based). 304 * @param item the index of the item of interest (zero-based). 305 * 306 * @return The open-value for the specified series and item. 307 */ 308 public Number getOpen(int series, int item) { 309 DatasetInfo di = getDatasetInfo(series); 310 return ((OHLCDataset) di.data).getOpen(di.series, item); 311 } 312 313 /** 314 * Returns the open-value (as a double primitive) for an item within a 315 * series. 316 * 317 * @param series the series (zero-based index). 318 * @param item the item (zero-based index). 319 * 320 * @return The open-value. 321 */ 322 public double getOpenValue(int series, int item) { 323 double result = Double.NaN; 324 Number open = getOpen(series, item); 325 if (open != null) { 326 result = open.doubleValue(); 327 } 328 return result; 329 } 330 331 /** 332 * Returns the close-value for the specified series and item. 333 * <P> 334 * Note: throws <code>ClassCastException</code> if the series is not from a 335 * {@link OHLCDataset}. 336 * 337 * @param series the index of the series of interest (zero-based). 338 * @param item the index of the item of interest (zero-based). 339 * 340 * @return The close-value for the specified series and item. 341 */ 342 public Number getClose(int series, int item) { 343 DatasetInfo di = getDatasetInfo(series); 344 return ((OHLCDataset) di.data).getClose(di.series, item); 345 } 346 347 /** 348 * Returns the close-value (as a double primitive) for an item within a 349 * series. 350 * 351 * @param series the series (zero-based index). 352 * @param item the item (zero-based index). 353 * 354 * @return The close-value. 355 */ 356 public double getCloseValue(int series, int item) { 357 double result = Double.NaN; 358 Number close = getClose(series, item); 359 if (close != null) { 360 result = close.doubleValue(); 361 } 362 return result; 363 } 364 365 /** 366 * Returns the volume value for the specified series and item. 367 * <P> 368 * Note: throws <code>ClassCastException</code> if the series is not from a 369 * {@link OHLCDataset}. 370 * 371 * @param series the index of the series of interest (zero-based). 372 * @param item the index of the item of interest (zero-based). 373 * 374 * @return The volume value for the specified series and item. 375 */ 376 public Number getVolume(int series, int item) { 377 DatasetInfo di = getDatasetInfo(series); 378 return ((OHLCDataset) di.data).getVolume(di.series, item); 379 } 380 381 /** 382 * Returns the volume-value (as a double primitive) for an item within a 383 * series. 384 * 385 * @param series the series (zero-based index). 386 * @param item the item (zero-based index). 387 * 388 * @return The volume-value. 389 */ 390 public double getVolumeValue(int series, int item) { 391 double result = Double.NaN; 392 Number volume = getVolume(series, item); 393 if (volume != null) { 394 result = volume.doubleValue(); 395 } 396 return result; 397 } 398 399 /////////////////////////////////////////////////////////////////////////// 400 // From IntervalXYDataset 401 /////////////////////////////////////////////////////////////////////////// 402 403 /** 404 * Returns the starting X value for the specified series and item. 405 * 406 * @param series the index of the series of interest (zero-based). 407 * @param item the index of the item of interest (zero-based). 408 * 409 * @return The value. 410 */ 411 public Number getStartX(int series, int item) { 412 DatasetInfo di = getDatasetInfo(series); 413 if (di.data instanceof IntervalXYDataset) { 414 return ((IntervalXYDataset) di.data).getStartX(di.series, item); 415 } 416 else { 417 return getX(series, item); 418 } 419 } 420 421 /** 422 * Returns the ending X value for the specified series and item. 423 * 424 * @param series the index of the series of interest (zero-based). 425 * @param item the index of the item of interest (zero-based). 426 * 427 * @return The value. 428 */ 429 public Number getEndX(int series, int item) { 430 DatasetInfo di = getDatasetInfo(series); 431 if (di.data instanceof IntervalXYDataset) { 432 return ((IntervalXYDataset) di.data).getEndX(di.series, item); 433 } 434 else { 435 return getX(series, item); 436 } 437 } 438 439 /** 440 * Returns the starting Y value for the specified series and item. 441 * 442 * @param series the index of the series of interest (zero-based). 443 * @param item the index of the item of interest (zero-based). 444 * 445 * @return The starting Y value for the specified series and item. 446 */ 447 public Number getStartY(int series, int item) { 448 DatasetInfo di = getDatasetInfo(series); 449 if (di.data instanceof IntervalXYDataset) { 450 return ((IntervalXYDataset) di.data).getStartY(di.series, item); 451 } 452 else { 453 return getY(series, item); 454 } 455 } 456 457 /** 458 * Returns the ending Y value for the specified series and item. 459 * 460 * @param series the index of the series of interest (zero-based). 461 * @param item the index of the item of interest (zero-based). 462 * 463 * @return The ending Y value for the specified series and item. 464 */ 465 public Number getEndY(int series, int item) { 466 DatasetInfo di = getDatasetInfo(series); 467 if (di.data instanceof IntervalXYDataset) { 468 return ((IntervalXYDataset) di.data).getEndY(di.series, item); 469 } 470 else { 471 return getY(series, item); 472 } 473 } 474 475 /////////////////////////////////////////////////////////////////////////// 476 // New methods from CombinationDataset 477 /////////////////////////////////////////////////////////////////////////// 478 479 /** 480 * Returns the parent Dataset of this combination. If there is more than 481 * one parent, or a child is found that is not a CombinationDataset, then 482 * returns <code>null</code>. 483 * 484 * @return The parent Dataset of this combination or <code>null</code>. 485 */ 486 public SeriesDataset getParent() { 487 488 SeriesDataset parent = null; 489 for (int i = 0; i < this.datasetInfo.size(); i++) { 490 SeriesDataset child = getDatasetInfo(i).data; 491 if (child instanceof CombinationDataset) { 492 SeriesDataset childParent 493 = ((CombinationDataset) child).getParent(); 494 if (parent == null) { 495 parent = childParent; 496 } 497 else if (parent != childParent) { 498 return null; 499 } 500 } 501 else { 502 return null; 503 } 504 } 505 return parent; 506 507 } 508 509 /** 510 * Returns a map or indirect indexing form our series into parent's series. 511 * Prior to calling this method, the client should check getParent() to make 512 * sure the CombinationDataset uses the same parent. If not, the map 513 * returned by this method will be invalid or null. 514 * 515 * @return A map or indirect indexing form our series into parent's series. 516 * 517 * @see #getParent() 518 */ 519 public int[] getMap() { 520 521 int[] map = null; 522 for (int i = 0; i < this.datasetInfo.size(); i++) { 523 SeriesDataset child = getDatasetInfo(i).data; 524 if (child instanceof CombinationDataset) { 525 int[] childMap = ((CombinationDataset) child).getMap(); 526 if (childMap == null) { 527 return null; 528 } 529 map = joinMap(map, childMap); 530 } 531 else { 532 return null; 533 } 534 } 535 return map; 536 } 537 538 /////////////////////////////////////////////////////////////////////////// 539 // New Methods 540 /////////////////////////////////////////////////////////////////////////// 541 542 /** 543 * Returns the child position. 544 * 545 * @param child the child dataset. 546 * 547 * @return The position. 548 */ 549 public int getChildPosition(Dataset child) { 550 551 int n = 0; 552 for (int i = 0; i < this.datasetInfo.size(); i++) { 553 SeriesDataset childDataset = getDatasetInfo(i).data; 554 if (childDataset instanceof CombinedDataset) { 555 int m = ((CombinedDataset) childDataset) 556 .getChildPosition(child); 557 if (m >= 0) { 558 return n + m; 559 } 560 n++; 561 } 562 else { 563 if (child == childDataset) { 564 return n; 565 } 566 n++; 567 } 568 } 569 return -1; 570 } 571 572 /////////////////////////////////////////////////////////////////////////// 573 // Private 574 /////////////////////////////////////////////////////////////////////////// 575 576 /** 577 * Returns the DatasetInfo object associated with the series. 578 * 579 * @param series the index of the series. 580 * 581 * @return The DatasetInfo object associated with the series. 582 */ 583 private DatasetInfo getDatasetInfo(int series) { 584 return (DatasetInfo) this.datasetInfo.get(series); 585 } 586 587 /** 588 * Joins two map arrays (int[]) together. 589 * 590 * @param a the first array. 591 * @param b the second array. 592 * 593 * @return A copy of { a[], b[] }. 594 */ 595 private int[] joinMap(int[] a, int[] b) { 596 if (a == null) { 597 return b; 598 } 599 if (b == null) { 600 return a; 601 } 602 int[] result = new int[a.length + b.length]; 603 System.arraycopy(a, 0, result, 0, a.length); 604 System.arraycopy(b, 0, result, a.length, b.length); 605 return result; 606 } 607 608 /** 609 * Private class to store as pairs (SeriesDataset, series) for all combined 610 * series. 611 */ 612 private class DatasetInfo { 613 614 /** The dataset. */ 615 private SeriesDataset data; 616 617 /** The series. */ 618 private int series; 619 620 /** 621 * Creates a new dataset info record. 622 * 623 * @param data the dataset. 624 * @param series the series. 625 */ 626 DatasetInfo(SeriesDataset data, int series) { 627 this.data = data; 628 this.series = series; 629 } 630 } 631 632 }