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
  10. * under the terms of the GNU Lesser General Public License as published by
  11. * the Free Software Foundation; either version 2.1 of the License, or
  12. * (at your option) any later version.
  13. *
  14. * This library is distributed in the hope that it will be useful, but
  15. * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
  16. * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
  17. * License for more details.
  18. *
  19. * You should have received a copy of the GNU Lesser General Public License
  20. * along with this library; if not, write to the Free Software Foundation,
  21. * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
  22. *
  23. * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
  24. * in the United States and other countries.]
  25. *
  26. * -----------------------------------
  27. * DefaultIntervalCategoryDataset.java
  28. * -----------------------------------
  29. * (C) Copyright 2002-2005, by Jeremy Bowman and Contributors.
  30. *
  31. * Original Author: Jeremy Bowman;
  32. * Contributor(s): David Gilbert (for Object Refinery Limited);
  33. *
  34. * $Id: DefaultIntervalCategoryDataset.java,v 1.4 2005/03/04 11:51:19 mungady Exp $
  35. *
  36. * Changes
  37. * -------
  38. * 29-Apr-2002 : Version 1, contributed by Jeremy Bowman (DG);
  39. * 24-Oct-2002 : Amendments for changes made to the dataset interface (DG);
  40. *
  41. */
  42. package org.jfree.data.category;
  43. import java.util.ArrayList;
  44. import java.util.Arrays;
  45. import java.util.Collections;
  46. import java.util.List;
  47. import java.util.ResourceBundle;
  48. import org.jfree.data.general.AbstractSeriesDataset;
  49. import org.jfree.data.general.DataUtilities;
  50. /**
  51. * A convenience class that provides a default implementation of the
  52. * {@link IntervalCategoryDataset} interface.
  53. * <p>
  54. * The standard constructor accepts data in a two dimensional array where the
  55. * first dimension is the series, and the second dimension is the category.
  56. *
  57. * @author Jeremy Bowman
  58. */
  59. public class DefaultIntervalCategoryDataset extends AbstractSeriesDataset
  60. implements IntervalCategoryDataset {
  61. /** The series keys. */
  62. private Comparable[] seriesKeys;
  63. /** The category keys. */
  64. private Comparable[] categoryKeys;
  65. /** Storage for the start value data. */
  66. private Number[][] startData;
  67. /** Storage for the end value data. */
  68. private Number[][] endData;
  69. /**
  70. * Creates a new dataset.
  71. *
  72. * @param starts the starting values for the intervals.
  73. * @param ends the ending values for the intervals.
  74. */
  75. public DefaultIntervalCategoryDataset(double[][] starts, double[][] ends) {
  76. this(
  77. DataUtilities.createNumberArray2D(starts),
  78. DataUtilities.createNumberArray2D(ends)
  79. );
  80. }
  81. /**
  82. * Constructs a dataset and populates it with data from the array.
  83. * <p>
  84. * The arrays are indexed as data[series][category]. Series and category
  85. * names are automatically generated - you can change them using the
  86. * {@link #setSeriesKeys(Comparable[])} and
  87. * {@link #setCategoryKeys(Comparable[])} methods.
  88. *
  89. * @param starts the start values data.
  90. * @param ends the end values data.
  91. */
  92. public DefaultIntervalCategoryDataset(Number[][] starts, Number[][] ends) {
  93. this(null, null, starts, ends);
  94. }
  95. /**
  96. * Constructs a DefaultIntervalCategoryDataset, populates it with data
  97. * from the arrays, and uses the supplied names for the series.
  98. * <p>
  99. * Category names are generated automatically ("Category 1", "Category 2",
  100. * etc).
  101. *
  102. * @param seriesNames the series names.
  103. * @param starts the start values data, indexed as data[series][category].
  104. * @param ends the end values data, indexed as data[series][category].
  105. */
  106. public DefaultIntervalCategoryDataset(String[] seriesNames,
  107. Number[][] starts,
  108. Number[][] ends) {
  109. this(seriesNames, null, starts, ends);
  110. }
  111. /**
  112. * Constructs a DefaultIntervalCategoryDataset, populates it with data
  113. * from the arrays, and uses the supplied names for the series and the
  114. * supplied objects for the categories.
  115. *
  116. * @param seriesKeys the series keys.
  117. * @param categoryKeys the categories.
  118. * @param starts the start values data, indexed as data[series][category].
  119. * @param ends the end values data, indexed as data[series][category].
  120. */
  121. public DefaultIntervalCategoryDataset(Comparable[] seriesKeys,
  122. Comparable[] categoryKeys,
  123. Number[][] starts,
  124. Number[][] ends) {
  125. this.startData = starts;
  126. this.endData = ends;
  127. if (starts != null && ends != null) {
  128. String baseName = "org.jfree.data.resources.DataPackageResources";
  129. ResourceBundle resources = ResourceBundle.getBundle(baseName);
  130. int seriesCount = starts.length;
  131. if (seriesCount != ends.length) {
  132. String errMsg = "DefaultIntervalCategoryDataset: the number "
  133. + "of series in the start value dataset does "
  134. + "not match the number of series in the end "
  135. + "value dataset.";
  136. throw new IllegalArgumentException(errMsg);
  137. }
  138. if (seriesCount > 0) {
  139. // set up the series names...
  140. if (seriesKeys != null) {
  141. if (seriesKeys.length != seriesCount) {
  142. throw new IllegalArgumentException(
  143. "The number of series keys does "
  144. + "not match the number of series in the data."
  145. );
  146. }
  147. this.seriesKeys = seriesKeys;
  148. }
  149. else {
  150. String prefix
  151. = resources.getString("series.default-prefix") + " ";
  152. this.seriesKeys = generateKeys(seriesCount, prefix);
  153. }
  154. // set up the category names...
  155. int categoryCount = starts[0].length;
  156. if (categoryCount != ends[0].length) {
  157. String errMsg = "DefaultIntervalCategoryDataset: the "
  158. + "number of categories in the start value "
  159. + "dataset does not match the number of "
  160. + "categories in the end value dataset.";
  161. throw new IllegalArgumentException(errMsg);
  162. }
  163. if (categoryKeys != null) {
  164. if (categoryKeys.length != categoryCount) {
  165. throw new IllegalArgumentException(
  166. "The number of category keys does "
  167. + "not match the number of categories in the data."
  168. );
  169. }
  170. this.categoryKeys = categoryKeys;
  171. }
  172. else {
  173. String prefix = resources.getString(
  174. "categories.default-prefix"
  175. ) + " ";
  176. this.categoryKeys = generateKeys(categoryCount, prefix);
  177. }
  178. }
  179. else {
  180. this.seriesKeys = null;
  181. this.categoryKeys = null;
  182. }
  183. }
  184. }
  185. /**
  186. * Returns the number of series in the dataset (possibly zero).
  187. *
  188. * @return The number of series in the dataset.
  189. */
  190. public int getSeriesCount() {
  191. int result = 0;
  192. if (this.startData != null) {
  193. result = this.startData.length;
  194. }
  195. return result;
  196. }
  197. /**
  198. * Returns the item count.
  199. *
  200. * @return The item count.
  201. */
  202. public int getItemCount() {
  203. return this.categoryKeys.length;
  204. }
  205. /**
  206. * Returns a category key.
  207. *
  208. * @param item the category index.
  209. *
  210. * @return The category key.
  211. */
  212. public Comparable getCategory(int item) {
  213. return this.categoryKeys[item];
  214. }
  215. /**
  216. * Returns an item.
  217. *
  218. * @param category the category key.
  219. *
  220. * @return The item index.
  221. */
  222. public int getItem(Object category) {
  223. List categories = getCategories();
  224. return categories.indexOf(category);
  225. }
  226. /**
  227. * Returns a series index.
  228. *
  229. * @param series the series.
  230. *
  231. * @return The series index.
  232. */
  233. public int getSeriesIndex(Object series) {
  234. List seriesKeys = getSeries();
  235. return seriesKeys.indexOf(series);
  236. }
  237. /**
  238. * Returns the name of the specified series.
  239. *
  240. * @param series the index of the required series (zero-based).
  241. *
  242. * @return The name of the specified series.
  243. */
  244. public Comparable getSeries(int series) {
  245. // check argument...
  246. if ((series >= getSeriesCount()) || (series < 0)) {
  247. throw new IllegalArgumentException(
  248. "DefaultCategoryDataset.getSeriesName(int): no such series.");
  249. }
  250. // return the value...
  251. return this.seriesKeys[series];
  252. }
  253. /**
  254. * Returns the name of the specified series.
  255. *
  256. * @param series the index of the required series (zero-based).
  257. *
  258. * @return The name of the specified series.
  259. */
  260. public String getSeriesName(int series) {
  261. if ((series >= getSeriesCount()) || (series < 0)) {
  262. throw new IllegalArgumentException("No such series : " + series);
  263. }
  264. return this.seriesKeys[series].toString();
  265. }
  266. /**
  267. * Sets the names of the series in the dataset.
  268. *
  269. * @param seriesKeys the keys of the series in the dataset.
  270. */
  271. public void setSeriesKeys(Comparable[] seriesKeys) {
  272. // check argument...
  273. if (seriesKeys == null) {
  274. throw new IllegalArgumentException("Null 'seriesKeys' argument.");
  275. }
  276. if (seriesKeys.length != getSeriesCount()) {
  277. throw new IllegalArgumentException(
  278. "DefaultIntervalCategoryDataset.setSeriesKeys(): "
  279. + "the number of series keys does not match the data."
  280. );
  281. }
  282. // make the change...
  283. this.seriesKeys = seriesKeys;
  284. fireDatasetChanged();
  285. }
  286. /**
  287. * Returns the number of categories in the dataset.
  288. * <P>
  289. * This method is part of the CategoryDataset interface.
  290. *
  291. * @return The number of categories in the dataset.
  292. */
  293. public int getCategoryCount() {
  294. int result = 0;
  295. if (this.startData != null) {
  296. if (getSeriesCount() > 0) {
  297. result = this.startData[0].length;
  298. }
  299. }
  300. return result;
  301. }
  302. /**
  303. * Returns a list of the series in the dataset.
  304. * <P>
  305. * Supports the CategoryDataset interface.
  306. *
  307. * @return A list of the series in the dataset.
  308. */
  309. public List getSeries() {
  310. // the CategoryDataset interface expects a list of series, but
  311. // we've stored them in an array...
  312. if (this.seriesKeys == null) {
  313. return new java.util.ArrayList();
  314. }
  315. else {
  316. return Collections.unmodifiableList(Arrays.asList(this.seriesKeys));
  317. }
  318. }
  319. /**
  320. * Returns a list of the categories in the dataset.
  321. * <P>
  322. * Supports the CategoryDataset interface.
  323. *
  324. * @return A list of the categories in the dataset.
  325. */
  326. public List getCategories() {
  327. return getColumnKeys();
  328. }
  329. /**
  330. * Returns a list of the categories in the dataset.
  331. * <P>
  332. * Supports the CategoryDataset interface.
  333. *
  334. * @return A list of the categories in the dataset.
  335. */
  336. public List getColumnKeys() {
  337. // the CategoryDataset interface expects a list of categories, but
  338. // we've stored them in an array...
  339. if (this.categoryKeys == null) {
  340. return new ArrayList();
  341. }
  342. else {
  343. return Collections.unmodifiableList(
  344. Arrays.asList(this.categoryKeys)
  345. );
  346. }
  347. }
  348. /**
  349. * Sets the categories for the dataset.
  350. *
  351. * @param categoryKeys an array of objects representing the categories in
  352. * the dataset.
  353. */
  354. public void setCategoryKeys(Comparable[] categoryKeys) {
  355. // check arguments...
  356. if (categoryKeys == null) {
  357. throw new IllegalArgumentException("Null 'categoryKeys' argument.");
  358. }
  359. if (categoryKeys.length != this.startData[0].length) {
  360. throw new IllegalArgumentException(
  361. "The number of categories does not match the data."
  362. );
  363. }
  364. for (int i = 0; i < categoryKeys.length; i++) {
  365. if (categoryKeys[i] == null) {
  366. throw new IllegalArgumentException(
  367. "DefaultIntervalCategoryDataset.setCategoryKeys(): "
  368. + "null category not permitted.");
  369. }
  370. }
  371. // make the change...
  372. this.categoryKeys = categoryKeys;
  373. fireDatasetChanged();
  374. }
  375. /**
  376. * Returns the data value for one category in a series.
  377. * <P>
  378. * This method is part of the CategoryDataset interface. Not particularly
  379. * meaningful for this class...returns the end value.
  380. * @param series The required series (zero based index).
  381. * @param category The required category.
  382. * @return The data value for one category in a series (null possible).
  383. */
  384. public Number getValue(Comparable series, Comparable category) {
  385. int seriesIndex = getSeriesIndex(series);
  386. int itemIndex = getItem(category);
  387. return getValue(seriesIndex, itemIndex);
  388. }
  389. /**
  390. * Returns the data value for one category in a series.
  391. * <P>
  392. * This method is part of the CategoryDataset interface. Not particularly
  393. * meaningful for this class...returns the end value.
  394. *
  395. * @param series the required series (zero based index).
  396. * @param category the required category.
  397. *
  398. * @return The data value for one category in a series (null possible).
  399. */
  400. public Number getValue(int series, int category) {
  401. return getEndValue(series, category);
  402. }
  403. /**
  404. * Returns the start data value for one category in a series.
  405. *
  406. * @param series the required series.
  407. * @param category the required category.
  408. *
  409. * @return The start data value for one category in a series (null possible).
  410. */
  411. public Number getStartValue(Comparable series, Comparable category) {
  412. int seriesIndex = getSeriesIndex(series);
  413. int itemIndex = getItem(category);
  414. return getStartValue(seriesIndex, itemIndex);
  415. }
  416. /**
  417. * Returns the start data value for one category in a series.
  418. *
  419. * @param series the required series (zero based index).
  420. * @param category the required category.
  421. *
  422. * @return The start data value for one category in a series (null possible).
  423. */
  424. public Number getStartValue(int series, int category) {
  425. // check arguments...
  426. if ((series < 0) || (series >= getSeriesCount())) {
  427. throw new IllegalArgumentException(
  428. "DefaultIntervalCategoryDataset.getValue(): "
  429. + "series index out of range.");
  430. }
  431. if ((category < 0) || (category >= getCategoryCount())) {
  432. throw new IllegalArgumentException(
  433. "DefaultIntervalCategoryDataset.getValue(): "
  434. + "category index out of range.");
  435. }
  436. // fetch the value...
  437. return this.startData[series][category];
  438. }
  439. /**
  440. * Returns the end data value for one category in a series.
  441. *
  442. * @param series the required series.
  443. * @param category the required category.
  444. *
  445. * @return The end data value for one category in a series (null possible).
  446. */
  447. public Number getEndValue(Comparable series, Comparable category) {
  448. int seriesIndex = getSeriesIndex(series);
  449. int itemIndex = getItem(category);
  450. return getEndValue(seriesIndex, itemIndex);
  451. }
  452. /**
  453. * Returns the end data value for one category in a series.
  454. *
  455. * @param series the required series (zero based index).
  456. * @param category the required category.
  457. *
  458. * @return The end data value for one category in a series (null possible).
  459. */
  460. public Number getEndValue(int series, int category) {
  461. // check arguments...
  462. if ((series < 0) || (series >= getSeriesCount())) {
  463. throw new IllegalArgumentException(
  464. "DefaultIntervalCategoryDataset.getValue(): "
  465. + "series index out of range.");
  466. }
  467. if ((category < 0) || (category >= getCategoryCount())) {
  468. throw new IllegalArgumentException(
  469. "DefaultIntervalCategoryDataset.getValue(): "
  470. + "category index out of range.");
  471. }
  472. // fetch the value...
  473. return this.endData[series][category];
  474. }
  475. /**
  476. * Sets the start data value for one category in a series.
  477. *
  478. * @param series the series (zero-based index).
  479. * @param category the category.
  480. *
  481. * @param value The value.
  482. */
  483. public void setStartValue(int series, Object category, Number value) {
  484. // does the series exist?
  485. if ((series < 0) || (series > getSeriesCount())) {
  486. throw new IllegalArgumentException(
  487. "DefaultIntervalCategoryDataset.setValue: "
  488. + "series outside valid range.");
  489. }
  490. // is the category valid?
  491. int categoryIndex = getCategoryIndex(category);
  492. if (categoryIndex < 0) {
  493. throw new IllegalArgumentException(
  494. "DefaultIntervalCategoryDataset.setValue: "
  495. + "unrecognised category.");
  496. }
  497. // update the data...
  498. this.startData[series][categoryIndex] = value;
  499. fireDatasetChanged();
  500. }
  501. /**
  502. * Sets the end data value for one category in a series.
  503. *
  504. * @param series the series (zero-based index).
  505. * @param category the category.
  506. *
  507. * @param value the value.
  508. */
  509. public void setEndValue(int series, Object category, Number value) {
  510. // does the series exist?
  511. if ((series < 0) || (series > getSeriesCount())) {
  512. throw new IllegalArgumentException(
  513. "DefaultIntervalCategoryDataset.setValue: "
  514. + "series outside valid range.");
  515. }
  516. // is the category valid?
  517. int categoryIndex = getCategoryIndex(category);
  518. if (categoryIndex < 0) {
  519. throw new IllegalArgumentException(
  520. "DefaultIntervalCategoryDataset.setValue: "
  521. + "unrecognised category.");
  522. }
  523. // update the data...
  524. this.endData[series][categoryIndex] = value;
  525. fireDatasetChanged();
  526. }
  527. /**
  528. * Returns the index for the given category.
  529. *
  530. * @param category the category.
  531. *
  532. * @return The index.
  533. */
  534. private int getCategoryIndex(Object category) {
  535. int result = -1;
  536. for (int i = 0; i < this.categoryKeys.length; i++) {
  537. if (category.equals(this.categoryKeys[i])) {
  538. result = i;
  539. break;
  540. }
  541. }
  542. return result;
  543. }
  544. /**
  545. * Generates an array of keys, by appending a space plus an integer
  546. * (starting with 1) to the supplied prefix string.
  547. *
  548. * @param count the number of keys required.
  549. * @param prefix the name prefix.
  550. *
  551. * @return an array of <i>prefixN</i> with N = { 1 .. count}.
  552. */
  553. private Comparable[] generateKeys(int count, String prefix) {
  554. Comparable[] result = new Comparable[count];
  555. String name;
  556. for (int i = 0; i < count; i++) {
  557. name = prefix + (i + 1);
  558. result[i] = name;
  559. }
  560. return result;
  561. }
  562. /**
  563. * Returns a column key.
  564. *
  565. * @param item the column index.
  566. *
  567. * @return The column key.
  568. */
  569. public Comparable getColumnKey(int item) {
  570. return this.categoryKeys[item];
  571. }
  572. /**
  573. * Returns a column index.
  574. *
  575. * @param columnKey the column key.
  576. *
  577. * @return The column index.
  578. */
  579. public int getColumnIndex(Comparable columnKey) {
  580. List categories = getCategories();
  581. return categories.indexOf(columnKey);
  582. }
  583. /**
  584. * Returns a row index.
  585. *
  586. * @param rowKey the row key.
  587. *
  588. * @return The row index.
  589. */
  590. public int getRowIndex(Comparable rowKey) {
  591. List seriesKeys = getSeries();
  592. return seriesKeys.indexOf(rowKey);
  593. }
  594. /**
  595. * Returns a list of the series in the dataset.
  596. * <P>
  597. * Supports the CategoryDataset interface.
  598. *
  599. * @return A list of the series in the dataset.
  600. */
  601. public List getRowKeys() {
  602. // the CategoryDataset interface expects a list of series, but
  603. // we've stored them in an array...
  604. if (this.seriesKeys == null) {
  605. return new java.util.ArrayList();
  606. }
  607. else {
  608. return Collections.unmodifiableList(Arrays.asList(this.seriesKeys));
  609. }
  610. }
  611. /**
  612. * Returns the name of the specified series.
  613. *
  614. * @param series the index of the required series (zero-based).
  615. *
  616. * @return the name of the specified series.
  617. */
  618. public Comparable getRowKey(int series) {
  619. // check argument...
  620. if ((series >= getSeriesCount()) || (series < 0)) {
  621. throw new IllegalArgumentException(
  622. "DefaultCategoryDataset.getSeriesName(int): no such series.");
  623. }
  624. // return the value...
  625. return this.seriesKeys[series];
  626. }
  627. /**
  628. * Returns the number of categories in the dataset.
  629. * <P>
  630. * This method is part of the CategoryDataset interface.
  631. *
  632. * @return The number of categories in the dataset.
  633. */
  634. public int getColumnCount() {
  635. int result = 0;
  636. if (this.startData != null) {
  637. if (getSeriesCount() > 0) {
  638. result = this.startData[0].length;
  639. }
  640. }
  641. return result;
  642. }
  643. /**
  644. * Returns the number of series in the dataset (possibly zero).
  645. *
  646. * @return The number of series in the dataset.
  647. */
  648. public int getRowCount() {
  649. int result = 0;
  650. if (this.startData != null) {
  651. result = this.startData.length;
  652. }
  653. return result;
  654. }
  655. }