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