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 }