1. /* ===========================================================
  2. * JFreeChart : a free chart library for the Java(tm) platform
  3. * ===========================================================
  4. *
  5. * (C) Copyright 2000-2005, by Object Refinery Limited and Contributors.
  6. *
  7. * Project Info: http://www.jfree.org/jfreechart/index.html
  8. *
  9. * This library is free software; you can redistribute it and/or modify it under the terms
  10. * of the GNU Lesser General Public License as published by the Free Software Foundation;
  11. * either version 2.1 of the License, or (at your option) any later version.
  12. *
  13. * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
  14. * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  15. * See the GNU Lesser General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU Lesser General Public License along with this
  18. * library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
  19. * Boston, MA 02111-1307, USA.
  20. *
  21. * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
  22. * in the United States and other countries.]
  23. *
  24. * --------------------
  25. * CombinedDataset.java
  26. * --------------------
  27. * (C) Copyright 2001-2005, by Bill Kelemen and Contributors.
  28. *
  29. * Original Author: Bill Kelemen;
  30. * Contributor(s): David Gilbert (for Object Refinery Limited);
  31. *
  32. * $Id: CombinedDataset.java,v 1.3 2005/01/14 17:31:44 mungady Exp $
  33. *
  34. * Changes
  35. * -------
  36. * 06-Dec-2001 : Version 1 (BK);
  37. * 27-Dec-2001 : Fixed bug in getChildPosition method (BK);
  38. * 29-Dec-2001 : Fixed bug in getChildPosition method with complex CombinePlot (BK);
  39. * 05-Feb-2002 : Small addition to the interface HighLowDataset, as requested by Sylvain
  40. * Vieujot (DG);
  41. * 14-Feb-2002 : Added bug fix for IntervalXYDataset methods, submitted by Gyula Kun-Szabo (DG);
  42. * 11-Jun-2002 : Updated for change in event constructor (DG);
  43. * 04-Oct-2002 : Fixed errors reported by Checkstyle (DG);
  44. * 06-May-2004 : Now extends AbstractIntervalXYDataset and added other methods that return
  45. * double primitives (DG);
  46. * 15-Jul-2004 : Switched getX() with getXValue() and getY() with getYValue() (DG);
  47. *
  48. */
  49. package org.jfree.data.general;
  50. import java.util.List;
  51. import org.jfree.data.xy.AbstractIntervalXYDataset;
  52. import org.jfree.data.xy.OHLCDataset;
  53. import org.jfree.data.xy.IntervalXYDataset;
  54. import org.jfree.data.xy.XYDataset;
  55. /**
  56. * This class can combine instances of {@link XYDataset}, {@link OHLCDataset} and
  57. * {@link IntervalXYDataset} together exposing the union of all the series under one dataset.
  58. *
  59. * @author Bill Kelemen (bill@kelemen-usa.com)
  60. */
  61. public class CombinedDataset extends AbstractIntervalXYDataset
  62. implements XYDataset,
  63. OHLCDataset,
  64. IntervalXYDataset,
  65. CombinationDataset {
  66. /** Storage for the datasets we combine. */
  67. private List datasetInfo = new java.util.ArrayList();
  68. /**
  69. * Default constructor for an empty combination.
  70. */
  71. public CombinedDataset() {
  72. super();
  73. }
  74. /**
  75. * Creates a CombinedDataset initialized with an array of SeriesDatasets.
  76. *
  77. * @param data array of SeriesDataset that contains the SeriesDatasets to combine.
  78. */
  79. public CombinedDataset(SeriesDataset[] data) {
  80. add(data);
  81. }
  82. /**
  83. * Adds one SeriesDataset to the combination. Listeners are notified of the change.
  84. *
  85. * @param data the SeriesDataset to add.
  86. */
  87. public void add(SeriesDataset data) {
  88. fastAdd(data);
  89. DatasetChangeEvent event = new DatasetChangeEvent(this, this);
  90. notifyListeners(event);
  91. }
  92. /**
  93. * Adds an array of SeriesDataset's to the combination. Listeners are
  94. * notified of the change.
  95. *
  96. * @param data array of SeriesDataset to add
  97. */
  98. public void add(SeriesDataset[] data) {
  99. for (int i = 0; i < data.length; i++) {
  100. fastAdd(data[i]);
  101. }
  102. DatasetChangeEvent event = new DatasetChangeEvent(this, this);
  103. notifyListeners(event);
  104. }
  105. /**
  106. * Adds one series from a SeriesDataset to the combination. Listeners are
  107. * notified of the change.
  108. *
  109. * @param data the SeriesDataset where series is contained
  110. * @param series series to add
  111. */
  112. public void add(SeriesDataset data, int series) {
  113. add(new SubSeriesDataset(data, series));
  114. }
  115. /**
  116. * Fast add of a SeriesDataset. Does not notify listeners of the change.
  117. *
  118. * @param data SeriesDataset to add
  119. */
  120. private void fastAdd(SeriesDataset data) {
  121. for (int i = 0; i < data.getSeriesCount(); i++) {
  122. this.datasetInfo.add(new DatasetInfo(data, i));
  123. }
  124. }
  125. ///////////////////////////////////////////////////////////////////////////
  126. // From SeriesDataset
  127. ///////////////////////////////////////////////////////////////////////////
  128. /**
  129. * Returns the number of series in the dataset.
  130. *
  131. * @return The number of series in the dataset.
  132. */
  133. public int getSeriesCount() {
  134. return this.datasetInfo.size();
  135. }
  136. /**
  137. * Returns the name of a series.
  138. *
  139. * @param series the series (zero-based index).
  140. *
  141. * @return the name of a series.
  142. */
  143. public String getSeriesName(int series) {
  144. DatasetInfo di = getDatasetInfo(series);
  145. return di.data.getSeriesName(di.series);
  146. }
  147. ///////////////////////////////////////////////////////////////////////////
  148. // From XYDataset
  149. ///////////////////////////////////////////////////////////////////////////
  150. /**
  151. * Returns the X-value for the specified series and item.
  152. * <P>
  153. * Note: throws <code>ClassCastException</code> if the series is not from a {@link XYDataset}.
  154. *
  155. * @param series the index of the series of interest (zero-based).
  156. * @param item the index of the item of interest (zero-based).
  157. *
  158. * @return the X-value for the specified series and item.
  159. */
  160. public Number getX(int series, int item) {
  161. DatasetInfo di = getDatasetInfo(series);
  162. return ((XYDataset) di.data).getX(di.series, item);
  163. }
  164. /**
  165. * Returns the Y-value for the specified series and item.
  166. * <P>
  167. * Note: throws <code>ClassCastException</code> if the series is not from a {@link XYDataset}.
  168. *
  169. * @param series the index of the series of interest (zero-based).
  170. * @param item the index of the item of interest (zero-based).
  171. *
  172. * @return The Y-value for the specified series and item.
  173. */
  174. public Number getY(int series, int item) {
  175. DatasetInfo di = getDatasetInfo(series);
  176. return ((XYDataset) di.data).getY(di.series, item);
  177. }
  178. /**
  179. * Returns the number of items in a series.
  180. * <P>
  181. * Note: throws <code>ClassCastException</code> if the series is not from a {@link XYDataset}.
  182. *
  183. * @param series the index of the series of interest (zero-based).
  184. *
  185. * @return The number of items in a series.
  186. */
  187. public int getItemCount(int series) {
  188. DatasetInfo di = getDatasetInfo(series);
  189. return ((XYDataset) di.data).getItemCount(di.series);
  190. }
  191. ///////////////////////////////////////////////////////////////////////////
  192. // From HighLowDataset
  193. ///////////////////////////////////////////////////////////////////////////
  194. /**
  195. * Returns the high-value for the specified series and item.
  196. * <P>
  197. * Note: throws <code>ClassCastException</code> if the series is not from a
  198. * {@link OHLCDataset}.
  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 high-value for the specified series and item.
  204. */
  205. public Number getHigh(int series, int item) {
  206. DatasetInfo di = getDatasetInfo(series);
  207. return ((OHLCDataset) di.data).getHigh(di.series, item);
  208. }
  209. /**
  210. * Returns the high-value (as a double primitive) for an item within a series.
  211. *
  212. * @param series the series (zero-based index).
  213. * @param item the item (zero-based index).
  214. *
  215. * @return The high-value.
  216. */
  217. public double getHighValue(int series, int item) {
  218. double result = Double.NaN;
  219. Number high = getHigh(series, item);
  220. if (high != null) {
  221. result = high.doubleValue();
  222. }
  223. return result;
  224. }
  225. /**
  226. * Returns the low-value for the specified series and item.
  227. * <P>
  228. * Note: throws <code>ClassCastException</code> if the series is not from a
  229. * {@link OHLCDataset}.
  230. *
  231. * @param series the index of the series of interest (zero-based).
  232. * @param item the index of the item of interest (zero-based).
  233. *
  234. * @return The low-value for the specified series and item.
  235. */
  236. public Number getLow(int series, int item) {
  237. DatasetInfo di = getDatasetInfo(series);
  238. return ((OHLCDataset) di.data).getLow(di.series, item);
  239. }
  240. /**
  241. * Returns the low-value (as a double primitive) for an item within a series.
  242. *
  243. * @param series the series (zero-based index).
  244. * @param item the item (zero-based index).
  245. *
  246. * @return The low-value.
  247. */
  248. public double getLowValue(int series, int item) {
  249. double result = Double.NaN;
  250. Number low = getLow(series, item);
  251. if (low != null) {
  252. result = low.doubleValue();
  253. }
  254. return result;
  255. }
  256. /**
  257. * Returns the open-value for the specified series and item.
  258. * <P>
  259. * Note: throws <code>ClassCastException</code> if the series is not from a
  260. * {@link OHLCDataset}.
  261. *
  262. * @param series the index of the series of interest (zero-based).
  263. * @param item the index of the item of interest (zero-based).
  264. *
  265. * @return The open-value for the specified series and item.
  266. */
  267. public Number getOpen(int series, int item) {
  268. DatasetInfo di = getDatasetInfo(series);
  269. return ((OHLCDataset) di.data).getOpen(di.series, item);
  270. }
  271. /**
  272. * Returns the open-value (as a double primitive) for an item within a series.
  273. *
  274. * @param series the series (zero-based index).
  275. * @param item the item (zero-based index).
  276. *
  277. * @return The open-value.
  278. */
  279. public double getOpenValue(int series, int item) {
  280. double result = Double.NaN;
  281. Number open = getOpen(series, item);
  282. if (open != null) {
  283. result = open.doubleValue();
  284. }
  285. return result;
  286. }
  287. /**
  288. * Returns the close-value for the specified series and item.
  289. * <P>
  290. * Note: throws <code>ClassCastException</code> if the series is not from a
  291. * {@link OHLCDataset}.
  292. *
  293. * @param series the index of the series of interest (zero-based).
  294. * @param item the index of the item of interest (zero-based).
  295. *
  296. * @return The close-value for the specified series and item.
  297. */
  298. public Number getClose(int series, int item) {
  299. DatasetInfo di = getDatasetInfo(series);
  300. return ((OHLCDataset) di.data).getClose(di.series, item);
  301. }
  302. /**
  303. * Returns the close-value (as a double primitive) for an item within a series.
  304. *
  305. * @param series the series (zero-based index).
  306. * @param item the item (zero-based index).
  307. *
  308. * @return The close-value.
  309. */
  310. public double getCloseValue(int series, int item) {
  311. double result = Double.NaN;
  312. Number close = getClose(series, item);
  313. if (close != null) {
  314. result = close.doubleValue();
  315. }
  316. return result;
  317. }
  318. /**
  319. * Returns the volume value for the specified series and item.
  320. * <P>
  321. * Note: throws <code>ClassCastException</code> if the series is not from a
  322. * {@link OHLCDataset}.
  323. *
  324. * @param series the index of the series of interest (zero-based).
  325. * @param item the index of the item of interest (zero-based).
  326. *
  327. * @return The volume value for the specified series and item.
  328. */
  329. public Number getVolume(int series, int item) {
  330. DatasetInfo di = getDatasetInfo(series);
  331. return ((OHLCDataset) di.data).getVolume(di.series, item);
  332. }
  333. /**
  334. * Returns the volume-value (as a double primitive) for an item within a series.
  335. *
  336. * @param series the series (zero-based index).
  337. * @param item the item (zero-based index).
  338. *
  339. * @return The volume-value.
  340. */
  341. public double getVolumeValue(int series, int item) {
  342. double result = Double.NaN;
  343. Number volume = getVolume(series, item);
  344. if (volume != null) {
  345. result = volume.doubleValue();
  346. }
  347. return result;
  348. }
  349. ///////////////////////////////////////////////////////////////////////////
  350. // From IntervalXYDataset
  351. ///////////////////////////////////////////////////////////////////////////
  352. /**
  353. * Returns the starting X value for the specified series and item.
  354. *
  355. * @param series the index of the series of interest (zero-based).
  356. * @param item the index of the item of interest (zero-based).
  357. *
  358. * @return The value.
  359. */
  360. public Number getStartX(int series, int item) {
  361. DatasetInfo di = getDatasetInfo(series);
  362. if (di.data instanceof IntervalXYDataset) {
  363. return ((IntervalXYDataset) di.data).getStartX(di.series, item);
  364. }
  365. else {
  366. return getX(series, item);
  367. }
  368. }
  369. /**
  370. * Returns the ending X value for the specified series and item.
  371. *
  372. * @param series the index of the series of interest (zero-based).
  373. * @param item the index of the item of interest (zero-based).
  374. *
  375. * @return The value.
  376. */
  377. public Number getEndX(int series, int item) {
  378. DatasetInfo di = getDatasetInfo(series);
  379. if (di.data instanceof IntervalXYDataset) {
  380. return ((IntervalXYDataset) di.data).getEndX(di.series, item);
  381. }
  382. else {
  383. return getX(series, item);
  384. }
  385. }
  386. /**
  387. * Returns the starting Y value for the specified series and item.
  388. *
  389. * @param series the index of the series of interest (zero-based).
  390. * @param item the index of the item of interest (zero-based).
  391. *
  392. * @return the starting Y value for the specified series and item.
  393. */
  394. public Number getStartY(int series, int item) {
  395. DatasetInfo di = getDatasetInfo(series);
  396. if (di.data instanceof IntervalXYDataset) {
  397. return ((IntervalXYDataset) di.data).getStartY(di.series, item);
  398. }
  399. else {
  400. return getY(series, item);
  401. }
  402. }
  403. /**
  404. * Returns the ending Y 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 ending Y value for the specified series and item.
  410. */
  411. public Number getEndY(int series, int item) {
  412. DatasetInfo di = getDatasetInfo(series);
  413. if (di.data instanceof IntervalXYDataset) {
  414. return ((IntervalXYDataset) di.data).getEndY(di.series, item);
  415. }
  416. else {
  417. return getY(series, item);
  418. }
  419. }
  420. ///////////////////////////////////////////////////////////////////////////
  421. // New methods from CombinationDataset
  422. ///////////////////////////////////////////////////////////////////////////
  423. /**
  424. * Returns the parent Dataset of this combination. If there is more than
  425. * one parent, or a child is found that is not a CombinationDataset, then
  426. * returns <code>null</code>.
  427. *
  428. * @return The parent Dataset of this combination or <code>null</code>.
  429. */
  430. public SeriesDataset getParent() {
  431. SeriesDataset parent = null;
  432. for (int i = 0; i < this.datasetInfo.size(); i++) {
  433. SeriesDataset child = getDatasetInfo(i).data;
  434. if (child instanceof CombinationDataset) {
  435. SeriesDataset childParent = ((CombinationDataset) child).getParent();
  436. if (parent == null) {
  437. parent = childParent;
  438. }
  439. else if (parent != childParent) {
  440. return null;
  441. }
  442. }
  443. else {
  444. return null;
  445. }
  446. }
  447. return parent;
  448. }
  449. /**
  450. * Returns a map or indirect indexing form our series into parent's series.
  451. * Prior to calling this method, the client should check getParent() to make
  452. * sure the CombinationDataset uses the same parent. If not, the map
  453. * returned by this method will be invalid or null.
  454. *
  455. * @return A map or indirect indexing form our series into parent's series.
  456. *
  457. * @see #getParent()
  458. */
  459. public int[] getMap() {
  460. int[] map = null;
  461. for (int i = 0; i < this.datasetInfo.size(); i++) {
  462. SeriesDataset child = getDatasetInfo(i).data;
  463. if (child instanceof CombinationDataset) {
  464. int[] childMap = ((CombinationDataset) child).getMap();
  465. if (childMap == null) {
  466. return null;
  467. }
  468. map = joinMap(map, childMap);
  469. }
  470. else {
  471. return null;
  472. }
  473. }
  474. return map;
  475. }
  476. ///////////////////////////////////////////////////////////////////////////
  477. // New Methods
  478. ///////////////////////////////////////////////////////////////////////////
  479. /**
  480. * Returns the child position.
  481. *
  482. * @param child the child dataset.
  483. *
  484. * @return the position.
  485. */
  486. public int getChildPosition(Dataset child) {
  487. int n = 0;
  488. for (int i = 0; i < this.datasetInfo.size(); i++) {
  489. SeriesDataset childDataset = getDatasetInfo(i).data;
  490. if (childDataset instanceof CombinedDataset) {
  491. int m = ((CombinedDataset) childDataset).getChildPosition(child);
  492. if (m >= 0) {
  493. return n + m;
  494. }
  495. n++;
  496. }
  497. else {
  498. if (child == childDataset) {
  499. return n;
  500. }
  501. n++;
  502. }
  503. }
  504. return -1;
  505. }
  506. ///////////////////////////////////////////////////////////////////////////
  507. // Private
  508. ///////////////////////////////////////////////////////////////////////////
  509. /**
  510. * Returns the DatasetInfo object associated with the series.
  511. *
  512. * @param series the index of the series.
  513. *
  514. * @return The DatasetInfo object associated with the series.
  515. */
  516. private DatasetInfo getDatasetInfo(int series) {
  517. return (DatasetInfo) this.datasetInfo.get(series);
  518. }
  519. /**
  520. * Joins two map arrays (int[]) together.
  521. *
  522. * @param a the first array.
  523. * @param b the second array.
  524. *
  525. * @return a copy of { a[], b[] }.
  526. */
  527. private int[] joinMap(int[] a, int[] b) {
  528. if (a == null) {
  529. return b;
  530. }
  531. if (b == null) {
  532. return a;
  533. }
  534. int[] result = new int[a.length + b.length];
  535. System.arraycopy(a, 0, result, 0, a.length);
  536. System.arraycopy(b, 0, result, a.length, b.length);
  537. return result;
  538. }
  539. /**
  540. * Private class to store as pairs (SeriesDataset, series) for all combined series.
  541. */
  542. private class DatasetInfo {
  543. /** The dataset. */
  544. private SeriesDataset data;
  545. /** The series. */
  546. private int series;
  547. /**
  548. * Creates a new dataset info record.
  549. *
  550. * @param data the dataset.
  551. * @param series the series.
  552. */
  553. DatasetInfo(SeriesDataset data, int series) {
  554. this.data = data;
  555. this.series = series;
  556. }
  557. }
  558. }