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. * IntervalXYDelegate.java
  28. * -----------------------
  29. * (C) Copyright 2004, 2005, by Andreas Schroeder and Contributors.
  30. *
  31. * Original Author: Andreas Schroeder;
  32. * Contributor(s): David Gilbert (for Object Refinery Limited);
  33. *
  34. * $Id: IntervalXYDelegate.java,v 1.8 2005/02/24 10:59:40 mungady Exp $
  35. *
  36. * Changes (from 31-Mar-2004)
  37. * --------------------------
  38. * 31-Mar-2004 : Version 1 (AS);
  39. * 15-Jul-2004 : Switched getX() with getXValue() and getY() with
  40. * getYValue() (DG);
  41. * 18-Aug-2004 : Moved from org.jfree.data --> org.jfree.data.xy (DG);
  42. * 04-Nov-2004 : Added argument check for setIntervalWidth() method (DG);
  43. * 17-Nov-2004 : New methods to reflect changes in DomainInfo (DG);
  44. * 11-Jan-2005 : Removed deprecated methods in preparation for the 1.0.0
  45. * release (DG);
  46. * 21-Feb-2005 : Made public and added equals() method (DG);
  47. *
  48. */
  49. package org.jfree.data.xy;
  50. import java.io.Serializable;
  51. import org.jfree.data.DomainInfo;
  52. import org.jfree.data.Range;
  53. import org.jfree.data.general.DatasetUtilities;
  54. import org.jfree.util.PublicCloneable;
  55. /**
  56. * A class for delegating xy-interval issues to.
  57. * Enhances a XYDataset to an XYIntervalDataset. The decorator pattern
  58. * was not used because of the several possibly implemented interfaces of
  59. * the decorated instance (e.g. TableXYDataset, RangeInfo, DomainInfo etc.).
  60. * <p>
  61. * This class calculates the minimal interval width between two items. This
  62. * width influences the width of bars displayed with this dataset.
  63. * <p>
  64. * The width can be set manually or calculated automatically. The switch
  65. * autoWidth allows to determine which behavior is used. The behavior is
  66. * transparent: The width is always calculated automatically in the background
  67. * without affecting the manually set width. The switch simply determines which
  68. * value is used. <br> As default manually set width, 1.0 is used. <br> If there
  69. * is only one item in the series, the auto width calculation fails and falls
  70. * back on the manually set interval width (which is itself defaulted to 1.0).
  71. *
  72. * @author andreas.schroeder
  73. */
  74. public class IntervalXYDelegate implements DomainInfo, Serializable,
  75. Cloneable, PublicCloneable {
  76. /** For serialization. */
  77. private static final long serialVersionUID = -685166711639592857L;
  78. /**
  79. * The dataset to enhance.
  80. */
  81. private XYDataset dataset;
  82. /**
  83. * A flag to indicate whether the width should be calculated automatically.
  84. */
  85. private boolean autoWidth;
  86. /**
  87. * A factor that determines the position of the gap between two bars - only
  88. * relevant if the data is dispalyed with a bar renderer.
  89. */
  90. private double intervalPositionFactor;
  91. /**
  92. * The manually set interval width.
  93. */
  94. private double intervalWidth;
  95. /**
  96. * The automatically calculated interval width.
  97. */
  98. private double autoIntervalWidth;
  99. /**
  100. * The lower value of the interval. Only used for autoWidth.
  101. */
  102. private double lowerBound;
  103. /**
  104. * The upper value of the interval. Only used for autoWidth.
  105. */
  106. private double upperBound;
  107. /**
  108. * Creates an XYIntervalDelegate.
  109. *
  110. * @param dataset the dataset for which this interval delegate works.
  111. */
  112. public IntervalXYDelegate(XYDataset dataset) {
  113. this(dataset, true);
  114. }
  115. /**
  116. * Creates a new delegate for the specified dataset.
  117. *
  118. * @param dataset the dataset for which this interval delegate works.
  119. * @param autoWidth a flag that controls whether the interval width is
  120. * calculated automatically.
  121. */
  122. public IntervalXYDelegate(XYDataset dataset, boolean autoWidth) {
  123. this.autoWidth = autoWidth;
  124. this.dataset = dataset;
  125. this.intervalPositionFactor = 0.5;
  126. this.autoWidth = autoWidth;
  127. this.autoIntervalWidth = Double.POSITIVE_INFINITY;
  128. this.intervalWidth = 1.0;
  129. }
  130. /**
  131. * Returns whether the interval width is automatically calculated or not.
  132. *
  133. * @return Whether the width is automatically calculated or not.
  134. */
  135. public boolean isAutoWidth() {
  136. return this.autoWidth;
  137. }
  138. /**
  139. * Sets the flag that indicates whether the interval width is automatically
  140. * calculated or not.
  141. *
  142. * @param b a boolean.
  143. */
  144. public void setAutoWidth(boolean b) {
  145. this.autoWidth = b;
  146. }
  147. /**
  148. * Returns the interval position factor.
  149. *
  150. * @return The interval position factor.
  151. */
  152. public double getIntervalPositionFactor() {
  153. return this.intervalPositionFactor;
  154. }
  155. /**
  156. * Sets the interval position factor. Must be between 0.0 and 1.0 inclusive.
  157. * If the factor is 0.5, the gap is in the middle of the x values. If it
  158. * is lesser than 0.5, the gap is farther to the left and if greater than
  159. * 0.5 it gets farther to the right.
  160. *
  161. * @param d the new interval position factor (in the range
  162. * <code>0.0</code> to <code>1.0</code> inclusive).
  163. */
  164. public void setIntervalPositionFactor(double d) {
  165. if (d < 0.0 || 1.0 < d) {
  166. throw new IllegalArgumentException(
  167. "Argument 'd' outside valid range."
  168. );
  169. }
  170. this.intervalPositionFactor = d;
  171. }
  172. /**
  173. * Sets the manual interval width.
  174. *
  175. * @param w the width (negative values not permitted).
  176. */
  177. public void setIntervalWidth(double w) {
  178. if (w < 0.0) {
  179. throw new IllegalArgumentException("Negative 'w' argument.");
  180. }
  181. this.intervalWidth = w;
  182. }
  183. /**
  184. * Returns the full interval width. For behavior of this method, see
  185. * the class comments.
  186. *
  187. * @return the interval width to use.
  188. */
  189. public double getIntervalWidth() {
  190. if (isAutoWidth() && !Double.isInfinite(this.autoIntervalWidth)) {
  191. // everything is fine: autoWidth is on, and an autoIntervalWidth
  192. // was set.
  193. return this.autoIntervalWidth;
  194. }
  195. else {
  196. // either autoWidth is off or autoIntervalWidth was not set.
  197. return this.intervalWidth;
  198. }
  199. }
  200. /**
  201. * Returns the start x value based on the intervalWidth and the
  202. * intervalPositionFactor.
  203. *
  204. * @param series the series index.
  205. * @param item the item index.
  206. *
  207. * @return The start value based on the intervalWidth and the
  208. * intervalPositionFactor.
  209. */
  210. public Number getStartX(int series, int item) {
  211. Number startX = null;
  212. Number x = this.dataset.getX(series, item);
  213. if (x != null) {
  214. startX = new Double(x.doubleValue()
  215. - (getIntervalPositionFactor() * getIntervalWidth()));
  216. }
  217. return startX;
  218. }
  219. /**
  220. * Returns the end x value based on the intervalWidth and the
  221. * intervalPositionFactor.
  222. *
  223. * @param series the series index.
  224. * @param item the item index.
  225. *
  226. * @return The end value based on the intervalWidth and the
  227. * intervalPositionFactor.
  228. */
  229. public Number getEndX(int series, int item) {
  230. Number endX = null;
  231. Number x = this.dataset.getX(series, item);
  232. if (x != null) {
  233. endX = new Double(
  234. x.doubleValue()
  235. + ((1.0 - getIntervalPositionFactor()) * getIntervalWidth())
  236. );
  237. }
  238. return endX;
  239. }
  240. /**
  241. * Returns the minimum x-value in the dataset.
  242. *
  243. * @param includeInterval a flag that determines whether or not the
  244. * x-interval is taken into account.
  245. *
  246. * @return The minimum value.
  247. */
  248. public double getDomainLowerBound(boolean includeInterval) {
  249. double result = Double.NaN;
  250. Range r = this.getDomainBounds(includeInterval);
  251. if (r != null) {
  252. result = r.getLowerBound();
  253. }
  254. return result;
  255. }
  256. /**
  257. * Returns the maximum x-value in the dataset.
  258. *
  259. * @param includeInterval a flag that determines whether or not the
  260. * x-interval is taken into account.
  261. *
  262. * @return The maximum value.
  263. */
  264. public double getDomainUpperBound(boolean includeInterval) {
  265. double result = Double.NaN;
  266. Range r = this.getDomainBounds(includeInterval);
  267. if (r != null) {
  268. result = r.getUpperBound();
  269. }
  270. return result;
  271. }
  272. /**
  273. * Returns the range of the values in this dataset's domain.
  274. *
  275. * @param includeInterval a flag that determines whether or not the
  276. * x-interval should be taken into account.
  277. *
  278. * @return The range.
  279. */
  280. public Range getDomainBounds(boolean includeInterval) {
  281. Range range = DatasetUtilities.iterateDomainBounds(
  282. this.dataset, includeInterval
  283. );
  284. if (this.dataset.getSeriesCount() == 1
  285. && this.dataset.getItemCount(0) == 1) {
  286. /* if there is only one interval value, so add some space to the
  287. * left and the right - otherwise one bar looks like a background
  288. * coloration.
  289. */
  290. range = new Range(
  291. range.getLowerBound() - getIntervalWidth(),
  292. range.getUpperBound() + getIntervalWidth()
  293. );
  294. }
  295. return range;
  296. }
  297. /**
  298. * Updates the interval width if an item is added. That is, relaxes the
  299. * interval width to the minimum of the actual interval width, the
  300. * distance between the actual x value and the previous x value and the
  301. * distance between the next x value and the actual x value.
  302. *
  303. * @param item the number of the item.
  304. * @param series the number of the series
  305. *
  306. */
  307. public void itemAdded(int series, int item) {
  308. double x = this.dataset.getXValue(series, item);
  309. if (item > 0) {
  310. double before = this.dataset.getXValue(series, item - 1);
  311. double delta = x - before;
  312. if (delta < this.autoIntervalWidth) {
  313. this.autoIntervalWidth = delta;
  314. this.lowerBound = before;
  315. this.upperBound = x;
  316. }
  317. }
  318. if (item + 1 < this.dataset.getItemCount(series)) {
  319. double after = this.dataset.getXValue(series, item + 1);
  320. double delta = after - x;
  321. if (delta < this.autoIntervalWidth) {
  322. this.autoIntervalWidth = delta;
  323. this.lowerBound = x;
  324. this.upperBound = after;
  325. }
  326. }
  327. }
  328. /**
  329. * Updates the interval width if an item is removed. That is, enlarges the
  330. * interval width to the new interval minimum if the removed value was
  331. * part of the minimum. For performance reason this method should be called
  332. * only if the x value was definitely removed from the series.
  333. *
  334. * @param x the x value of the removed item (that doesn't occur twice)
  335. */
  336. public void itemRemoved(double x) {
  337. if (x == this.lowerBound || x == this.upperBound) {
  338. recalculateIntervalWidth();
  339. }
  340. }
  341. /**
  342. * Recalculate the minimum width "from scratch".
  343. */
  344. private void recalculateIntervalWidth() {
  345. this.autoIntervalWidth = Double.POSITIVE_INFINITY;
  346. for (int series = 0, seriesCount = this.dataset.getSeriesCount();
  347. series < seriesCount; series++) {
  348. calculateSeries(series);
  349. }
  350. }
  351. /**
  352. * Calculates the interval width for a given series.
  353. *
  354. * @param series the series index.
  355. */
  356. private void calculateSeries(int series) {
  357. int totalCount = this.dataset.getItemCount(series);
  358. for (int item = 1, itemCount = totalCount; item < itemCount; item++) {
  359. double lower = this.dataset.getXValue(series, item - 1);
  360. double upper = this.dataset.getXValue(series, item);
  361. double delta = upper - lower;
  362. if (delta < this.autoIntervalWidth) {
  363. this.autoIntervalWidth = delta;
  364. this.lowerBound = lower;
  365. this.upperBound = upper;
  366. }
  367. }
  368. }
  369. /**
  370. * Convenience method for XYSeriesCollection.
  371. *
  372. * @param series the series index.
  373. */
  374. public void seriesAdded(int series) {
  375. calculateSeries(series);
  376. }
  377. /**
  378. * A convenience method for {@link XYSeriesCollection} which is called
  379. * whenever a series is removed - the interval width is recalculated.
  380. */
  381. public void seriesRemoved() {
  382. recalculateIntervalWidth();
  383. }
  384. /**
  385. * Tests the delegate for equality with an arbitrary object.
  386. *
  387. * @param obj the object (<code>null</code> permitted).
  388. *
  389. * @return A boolean.
  390. */
  391. public boolean equals(Object obj) {
  392. if (obj == this) {
  393. return true;
  394. }
  395. if (!(obj instanceof IntervalXYDelegate)) {
  396. return false;
  397. }
  398. IntervalXYDelegate that = (IntervalXYDelegate) obj;
  399. if (this.autoWidth != that.autoWidth) {
  400. return false;
  401. }
  402. if (this.intervalPositionFactor != that.intervalPositionFactor) {
  403. return false;
  404. }
  405. if (this.intervalWidth != that.intervalWidth) {
  406. return false;
  407. }
  408. return true;
  409. }
  410. /**
  411. * @return A clone of this delegate.
  412. *
  413. * @throws CloneNotSupportedException if the object cannot be cloned.
  414. */
  415. public Object clone() throws CloneNotSupportedException {
  416. return super.clone();
  417. }
  418. }