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. * HistogramDataset.java
  26. * ---------------------
  27. * (C) Copyright 2003-2005, by Jelai Wang and Contributors.
  28. *
  29. * Original Author: Jelai Wang (jelaiw AT mindspring.com);
  30. * Contributor(s): David Gilbert (for Object Refinery Limited);
  31. *
  32. * $Id: HistogramDataset.java,v 1.4 2005/01/14 17:30:46 mungady Exp $
  33. *
  34. * Changes
  35. * -------
  36. * 06-Jul-2003 : Version 1, contributed by Jelai Wang (DG);
  37. * 07-Jul-2003 : Changed package and added Javadocs (DG);
  38. * 15-Oct-2003 : Updated Javadocs and removed array sorting (JW);
  39. * 09-Jan-2004 : Added fix by "Z." posted in the JFreeChart forum (DG);
  40. * 01-Mar-2004 : Added equals() and clone() methods and implemented Serializable. Also added
  41. * new addSeries() method (DG);
  42. * 06-May-2004 : Now extends AbstractIntervalXYDataset (DG);
  43. * 15-Jul-2004 : Switched getX() with getXValue() and getY() with getYValue() (DG);
  44. *
  45. */
  46. package org.jfree.data.statistics;
  47. import java.io.Serializable;
  48. import java.util.ArrayList;
  49. import java.util.HashMap;
  50. import java.util.List;
  51. import java.util.Map;
  52. import org.jfree.data.general.DatasetChangeEvent;
  53. import org.jfree.data.xy.AbstractIntervalXYDataset;
  54. import org.jfree.data.xy.IntervalXYDataset;
  55. import org.jfree.util.ObjectUtilities;
  56. /**
  57. * A dataset that can be used for creating histograms.
  58. * <p>
  59. * See the <code>HistogramDemo.java</code> file in the JFreeChart distribution for an example.
  60. *
  61. * @author Jelai Wang, jelaiw AT mindspring.com
  62. */
  63. public class HistogramDataset extends AbstractIntervalXYDataset
  64. implements IntervalXYDataset, Cloneable, Serializable {
  65. /** A list of maps. */
  66. private List list;
  67. /** The histogram type. */
  68. private HistogramType type;
  69. /**
  70. * Creates a new (empty) dataset with a default type of {@link HistogramType}.FREQUENCY.
  71. */
  72. public HistogramDataset() {
  73. this.list = new ArrayList();
  74. this.type = HistogramType.FREQUENCY;
  75. }
  76. /**
  77. * Returns the histogram type.
  78. *
  79. * @return the type (never <code>null</code>).
  80. */
  81. public HistogramType getType() {
  82. return this.type;
  83. }
  84. /**
  85. * Sets the histogram type and sends a {@link DatasetChangeEvent} to all registered
  86. * listeners.
  87. *
  88. * @param type the type (<code>null</code> not permitted).
  89. */
  90. public void setType(HistogramType type) {
  91. if (type == null) {
  92. throw new IllegalArgumentException("Null 'type' argument");
  93. }
  94. this.type = type;
  95. notifyListeners(new DatasetChangeEvent(this, this));
  96. }
  97. /**
  98. * Adds a series to the dataset, using the specified number of bins.
  99. *
  100. * @param name the series name (<code>null</code> not permitted).
  101. * @param values the values (<code>null</code> not permitted).
  102. * @param bins the number of bins (must be at least 1).
  103. */
  104. public void addSeries(String name, double[] values, int bins) {
  105. // defer argument checking...
  106. double minimum = getMinimum(values);
  107. double maximum = getMaximum(values);
  108. addSeries(name, values, bins, minimum, maximum);
  109. }
  110. /**
  111. * Adds a series to the dataset. Any data value falling on a bin boundary will be assigned to
  112. * the lower value bin, with the exception of the lower bound of the bin range which is always
  113. * assigned to the first bin.
  114. *
  115. * @param name the series name (<code>null</code> not permitted).
  116. * @param values the raw observations.
  117. * @param bins the number of bins.
  118. * @param minimum the lower bound of the bin range.
  119. * @param maximum the upper bound of the bin range.
  120. */
  121. public void addSeries(String name,
  122. double[] values,
  123. int bins,
  124. double minimum,
  125. double maximum) {
  126. if (name == null) {
  127. throw new IllegalArgumentException("Null 'name' argument.");
  128. }
  129. if (values == null) {
  130. throw new IllegalArgumentException("Null 'values' argument.");
  131. }
  132. else if (bins < 1) {
  133. throw new IllegalArgumentException("The 'bins' value must be at least 1.");
  134. }
  135. double binWidth = (maximum - minimum) / bins;
  136. double tmp = minimum;
  137. List binList = new ArrayList(bins);
  138. for (int i = 0; i < bins; i++) {
  139. HistogramBin bin;
  140. // make sure bins[bins.length]'s upper boundary ends at maximum
  141. // to avoid the rounding issue. the bins[0] lower boundary is
  142. // guaranteed start from min
  143. if (i == bins - 1) {
  144. bin = new HistogramBin(tmp, maximum);
  145. }
  146. else {
  147. bin = new HistogramBin(tmp, tmp + binWidth);
  148. }
  149. tmp = tmp + binWidth;
  150. binList.add(bin);
  151. }
  152. // fill the bins
  153. for (int i = 0; i < values.length; i++) {
  154. for (int j = 0; j < bins; j++) {
  155. HistogramBin currentBin = (HistogramBin) binList.get(j);
  156. if (values[i] >= currentBin.getStartBoundary()
  157. && values[i] <= currentBin.getEndBoundary()) {
  158. // note the greedy <=
  159. currentBin.incrementCount();
  160. break; // break out of inner loop
  161. }
  162. }
  163. }
  164. // generic map for each series
  165. Map map = new HashMap();
  166. map.put("name", name);
  167. map.put("bins", binList);
  168. map.put("values.length", new Integer(values.length));
  169. map.put("bin width", new Double(binWidth));
  170. this.list.add(map);
  171. }
  172. /**
  173. * Returns the minimum value in an array of values.
  174. *
  175. * @param values the values (<code>null</code> not permitted and zero-length array
  176. * not permitted).
  177. *
  178. * @return the minimum value.
  179. */
  180. private double getMinimum(double[] values) {
  181. if (values == null || values.length < 1) {
  182. throw new IllegalArgumentException("Null or zero length 'values' argument.");
  183. }
  184. double min = Double.MAX_VALUE;
  185. for (int i = 0; i < values.length; i++) {
  186. if (values[i] < min) {
  187. min = values[i];
  188. }
  189. }
  190. return min;
  191. }
  192. /**
  193. * Returns the maximum value in an array of values.
  194. *
  195. * @param values the values (<code>null</code> not permitted and zero-length array
  196. * not permitted).
  197. *
  198. * @return the maximum value.
  199. */
  200. private double getMaximum(double[] values) {
  201. if (values == null || values.length < 1) {
  202. throw new IllegalArgumentException("Null or zero length 'values' argument.");
  203. }
  204. double max = -Double.MAX_VALUE;
  205. for (int i = 0; i < values.length; i++) {
  206. if (values[i] > max) {
  207. max = values[i];
  208. }
  209. }
  210. return max;
  211. }
  212. /**
  213. * Returns the bins for a series.
  214. *
  215. * @param series the series index.
  216. *
  217. * @return An array of bins.
  218. */
  219. List getBins(int series) {
  220. Map map = (Map) this.list.get(series);
  221. return (List) map.get("bins");
  222. }
  223. /**
  224. * Returns the total number of observations for a series.
  225. *
  226. * @param series the series index.
  227. *
  228. * @return the total.
  229. */
  230. private int getTotal(int series) {
  231. Map map = (Map) this.list.get(series);
  232. return ((Integer) map.get("values.length")).intValue();
  233. }
  234. /**
  235. * Returns the bin width for a series.
  236. *
  237. * @param series the series index (zero based).
  238. *
  239. * @return the bin width.
  240. */
  241. private double getBinWidth(int series) {
  242. Map map = (Map) this.list.get(series);
  243. return ((Double) map.get("bin width")).doubleValue();
  244. }
  245. /**
  246. * Returns the number of series in the dataset.
  247. *
  248. * @return The series count.
  249. */
  250. public int getSeriesCount() {
  251. return this.list.size();
  252. }
  253. /**
  254. * Returns the name for a series.
  255. *
  256. * @param series the series index (zero based).
  257. *
  258. * @return The series name.
  259. */
  260. public String getSeriesName(int series) {
  261. Map map = (Map) this.list.get(series);
  262. return (String) map.get("name");
  263. }
  264. /**
  265. * Returns the number of data items for a series.
  266. *
  267. * @param series the series index (zero based).
  268. *
  269. * @return the item count.
  270. */
  271. public int getItemCount(int series) {
  272. return getBins(series).size();
  273. }
  274. /**
  275. * Returns the X value for a bin.
  276. * <p>
  277. * This value won't be used for plotting histograms, since the renderer will ignore it.
  278. * But other renderers can use it (for example, you could use the dataset to create a line
  279. * chart).
  280. *
  281. * @param series the series index (zero based).
  282. * @param item the item index (zero based).
  283. *
  284. * @return The start value.
  285. */
  286. public Number getX(int series, int item) {
  287. List bins = getBins(series);
  288. HistogramBin bin = (HistogramBin) bins.get(item);
  289. double x = (bin.getStartBoundary() + bin.getEndBoundary()) / 2.;
  290. return new Double(x);
  291. }
  292. /**
  293. * Returns the y-value for a bin (calculated to take into account the histogram type).
  294. *
  295. * @param series the series index (zero based).
  296. * @param item the item index (zero based).
  297. *
  298. * @return The y-value.
  299. */
  300. public Number getY(int series, int item) {
  301. List bins = getBins(series);
  302. HistogramBin bin = (HistogramBin) bins.get(item);
  303. double total = getTotal(series);
  304. double binWidth = getBinWidth(series);
  305. if (this.type == HistogramType.FREQUENCY) {
  306. return new Double(bin.getCount());
  307. }
  308. else if (this.type == HistogramType.RELATIVE_FREQUENCY) {
  309. return new Double(bin.getCount() / total);
  310. }
  311. else if (this.type == HistogramType.SCALE_AREA_TO_1) {
  312. return new Double(bin.getCount() / (binWidth * total));
  313. }
  314. else { // pretty sure this shouldn't ever happen
  315. throw new IllegalStateException();
  316. }
  317. }
  318. /**
  319. * Returns the start value for a bin.
  320. *
  321. * @param series the series index (zero based).
  322. * @param item the item index (zero based).
  323. *
  324. * @return The start value.
  325. */
  326. public Number getStartX(int series, int item) {
  327. List bins = getBins(series);
  328. HistogramBin bin = (HistogramBin) bins.get(item);
  329. return new Double(bin.getStartBoundary());
  330. }
  331. /**
  332. * Returns the end value for a bin.
  333. *
  334. * @param series the series index (zero based).
  335. * @param item the item index (zero based).
  336. *
  337. * @return The end value.
  338. */
  339. public Number getEndX(int series, int item) {
  340. List bins = getBins(series);
  341. HistogramBin bin = (HistogramBin) bins.get(item);
  342. return new Double(bin.getEndBoundary());
  343. }
  344. /**
  345. * Returns the start y-value for a bin (which is the same as the y-value, this method
  346. * exists only to support the general form of the {@link IntervalXYDataset} interface).
  347. *
  348. * @param series the series index (zero based).
  349. * @param item the item index (zero based).
  350. *
  351. * @return The y-value.
  352. */
  353. public Number getStartY(int series, int item) {
  354. return getY(series, item);
  355. }
  356. /**
  357. * Returns the end y-value for a bin (which is the same as the y-value, this method
  358. * exists only to support the general form of the {@link IntervalXYDataset} interface).
  359. *
  360. * @param series the series index (zero based).
  361. * @param item the item index (zero based).
  362. *
  363. * @return The Y value.
  364. */
  365. public Number getEndY(int series, int item) {
  366. return getY(series, item);
  367. }
  368. /**
  369. * Tests this dataset for equality with an arbitrary object.
  370. *
  371. * @param obj the object to test against (<code>null</code> permitted).
  372. *
  373. * @return A boolean.
  374. */
  375. public boolean equals(Object obj) {
  376. if (obj == this) {
  377. return true;
  378. }
  379. if (!(obj instanceof HistogramDataset)) {
  380. return false;
  381. }
  382. HistogramDataset that = (HistogramDataset) obj;
  383. if (!ObjectUtilities.equal(this.type, that.type)) {
  384. return false;
  385. }
  386. if (!ObjectUtilities.equal(this.list, that.list)) {
  387. return false;
  388. }
  389. return true;
  390. }
  391. /**
  392. * Returns a clone of the dataset.
  393. *
  394. * @return A clone of the dataset.
  395. *
  396. * @throws CloneNotSupportedException if the object cannot be cloned.
  397. */
  398. public Object clone() throws CloneNotSupportedException {
  399. return super.clone();
  400. }
  401. }