1. /* ===========================================================
  2. * JFreeChart : a free chart library for the Java(tm) platform
  3. * ===========================================================
  4. *
  5. * (C) Copyright 2000-2004, 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. * StackedXYAreaRenderer2.java
  26. * ---------------------------
  27. * (C) Copyright 2004, by Object Refinery Limited and Contributors.
  28. *
  29. * Original Author: David Gilbert (for Object Refinery Limited), based on
  30. * the StackedXYAreaRenderer class by Richard Atkinson;
  31. * Contributor(s): -;
  32. *
  33. * $Id: StackedXYAreaRenderer2.java,v 1.3 2005/01/06 04:25:46 mungady Exp $
  34. *
  35. * Changes:
  36. * --------
  37. * 30-Apr-2004 : Version 1 (DG);
  38. * 15-Jul-2004 : Switched getX() with getXValue() and getY() with getYValue() (DG);
  39. * 10-Sep-2004 : Removed getRangeType() method (DG);
  40. * 06-Jan-2004 : Renamed getRangeExtent() --> findRangeBounds (DG);
  41. *
  42. */
  43. package org.jfree.chart.renderer.xy;
  44. import java.awt.Graphics2D;
  45. import java.awt.Paint;
  46. import java.awt.geom.GeneralPath;
  47. import java.awt.geom.Rectangle2D;
  48. import java.io.Serializable;
  49. import org.jfree.chart.axis.ValueAxis;
  50. import org.jfree.chart.labels.XYToolTipGenerator;
  51. import org.jfree.chart.plot.CrosshairState;
  52. import org.jfree.chart.plot.PlotRenderingInfo;
  53. import org.jfree.chart.plot.XYPlot;
  54. import org.jfree.chart.renderer.AbstractRenderer;
  55. import org.jfree.chart.urls.XYURLGenerator;
  56. import org.jfree.data.Range;
  57. import org.jfree.data.xy.TableXYDataset;
  58. import org.jfree.data.xy.XYDataset;
  59. import org.jfree.ui.RectangleEdge;
  60. import org.jfree.util.PublicCloneable;
  61. /**
  62. * A stacked area renderer for the {@link XYPlot} class.
  63. */
  64. public class StackedXYAreaRenderer2 extends XYAreaRenderer2
  65. implements Cloneable,
  66. PublicCloneable,
  67. Serializable {
  68. /**
  69. * Creates a new renderer.
  70. */
  71. public StackedXYAreaRenderer2() {
  72. this(null, null);
  73. }
  74. /**
  75. * Constructs a new renderer.
  76. *
  77. * @param labelGenerator the tool tip generator to use. <code>null</code> is none.
  78. * @param urlGenerator the URL generator (null permitted).
  79. */
  80. public StackedXYAreaRenderer2(XYToolTipGenerator labelGenerator,
  81. XYURLGenerator urlGenerator) {
  82. super(labelGenerator, urlGenerator);
  83. }
  84. /**
  85. * Returns the range of values the renderer requires to display all the items from the
  86. * specified dataset.
  87. *
  88. * @param dataset the dataset (<code>null</code> permitted).
  89. *
  90. * @return The range (or <code>null</code> if the dataset is <code>null</code> or empty).
  91. */
  92. public Range findRangeBounds(XYDataset dataset) {
  93. double min = Double.POSITIVE_INFINITY;
  94. double max = Double.NEGATIVE_INFINITY;
  95. TableXYDataset d = (TableXYDataset) dataset;
  96. int itemCount = d.getItemCount();
  97. for (int i = 0; i < itemCount; i++) {
  98. double[] stackValues = getStackValues(dataset, d.getSeriesCount(), i);
  99. min = Math.min(min, stackValues[0]);
  100. max = Math.max(max, stackValues[1]);
  101. }
  102. return new Range(min, max);
  103. }
  104. /**
  105. * Returns the number of passes required by the renderer.
  106. *
  107. * @return 1.
  108. */
  109. public int getPassCount() {
  110. return 1;
  111. }
  112. /**
  113. * Draws the visual representation of a single data item.
  114. *
  115. * @param g2 the graphics device.
  116. * @param state the renderer state.
  117. * @param dataArea the area within which the data is being drawn.
  118. * @param info collects information about the drawing.
  119. * @param plot the plot (can be used to obtain standard color information etc).
  120. * @param domainAxis the domain axis.
  121. * @param rangeAxis the range axis.
  122. * @param dataset the dataset.
  123. * @param series the series index (zero-based).
  124. * @param item the item index (zero-based).
  125. * @param crosshairState information about crosshairs on a plot.
  126. * @param pass the pass index.
  127. */
  128. public void drawItem(Graphics2D g2,
  129. XYItemRendererState state,
  130. Rectangle2D dataArea,
  131. PlotRenderingInfo info,
  132. XYPlot plot,
  133. ValueAxis domainAxis,
  134. ValueAxis rangeAxis,
  135. XYDataset dataset,
  136. int series,
  137. int item,
  138. CrosshairState crosshairState,
  139. int pass) {
  140. // get the data point...
  141. Number x1n = dataset.getX(series, item);
  142. Number y1n = dataset.getY(series, item);
  143. if (y1n == null) {
  144. y1n = AbstractRenderer.ZERO;
  145. }
  146. double x1 = x1n.doubleValue();
  147. double y1 = y1n.doubleValue();
  148. double[] stack1 = getStackValues(dataset, series, item);
  149. // get the previous point and the next point so we can calculate a "hot spot"
  150. // for the area (used by the chart entity)...
  151. Number x0n = dataset.getX(series, Math.max(item - 1, 0));
  152. Number y0n = dataset.getY(series, Math.max(item - 1, 0));
  153. if (y0n == null) {
  154. y0n = AbstractRenderer.ZERO;
  155. }
  156. double x0 = x0n.doubleValue();
  157. double y0 = y0n.doubleValue();
  158. double[] stack0 = getStackValues(dataset, series, Math.max(item - 1, 0));
  159. int itemCount = dataset.getItemCount(series);
  160. Number x2n = dataset.getX(series, Math.min(item + 1, itemCount - 1));
  161. Number y2n = dataset.getY(series, Math.min(item + 1, itemCount - 1));
  162. if (y2n == null) {
  163. y2n = AbstractRenderer.ZERO;
  164. }
  165. double x2 = x2n.doubleValue();
  166. double y2 = y2n.doubleValue();
  167. double[] stack2 = getStackValues(dataset, series, Math.min(item + 1, itemCount - 1));
  168. double xleft = (x0 + x1) / 2.0;
  169. double xright = (x1 + x2) / 2.0;
  170. double[] stackLeft = averageStackValues(stack0, stack1);
  171. double[] stackRight = averageStackValues(stack1, stack2);
  172. double[] adjStackLeft = adjustedStackValues(stack0, stack1);
  173. double[] adjStackRight = adjustedStackValues(stack1, stack2);
  174. RectangleEdge edge0 = plot.getDomainAxisEdge();
  175. float transX1 = (float) domainAxis.valueToJava2D(x1, dataArea, edge0);
  176. float transXLeft = (float) domainAxis.valueToJava2D(xleft, dataArea, edge0);
  177. float transXRight = (float) domainAxis.valueToJava2D(xright, dataArea, edge0);
  178. RectangleEdge edge1 = plot.getRangeAxisEdge();
  179. GeneralPath left = new GeneralPath();
  180. GeneralPath right = new GeneralPath();
  181. if (y1 >= 0.0) { // handle positive value
  182. float transY1 = (float) rangeAxis.valueToJava2D(y1 + stack1[1], dataArea, edge1);
  183. float transStack1 = (float) rangeAxis.valueToJava2D(stack1[1], dataArea, edge1);
  184. //float transStackLeft = (float) rangeAxis.valueToJava2D(stackLeft[1], dataArea, edge1);
  185. float transStackLeft = (float) rangeAxis.valueToJava2D(
  186. adjStackLeft[1], dataArea, edge1
  187. );
  188. // LEFT POLYGON
  189. if (y0 >= 0.0) {
  190. double yleft = (y0 + y1) / 2.0 + stackLeft[1];
  191. float transYLeft = (float) rangeAxis.valueToJava2D(yleft, dataArea, edge1);
  192. left.moveTo(transX1, transY1);
  193. left.lineTo(transX1, transStack1);
  194. left.lineTo(transXLeft, transStackLeft);
  195. left.lineTo(transXLeft, transYLeft);
  196. left.closePath();
  197. }
  198. else {
  199. left.moveTo(transX1, transStack1);
  200. left.lineTo(transX1, transY1);
  201. left.lineTo(transXLeft, transStackLeft);
  202. left.closePath();
  203. }
  204. float transStackRight = (float) rangeAxis.valueToJava2D(
  205. adjStackRight[1], dataArea, edge1
  206. );
  207. // RIGHT POLYGON
  208. if (y2 >= 0.0) {
  209. double yright = (y1 + y2) / 2.0 + stackRight[1];
  210. float transYRight = (float) rangeAxis.valueToJava2D(yright, dataArea, edge1);
  211. right.moveTo(transX1, transStack1);
  212. right.lineTo(transX1, transY1);
  213. right.lineTo(transXRight, transYRight);
  214. right.lineTo(transXRight, transStackRight);
  215. right.closePath();
  216. }
  217. else {
  218. right.moveTo(transX1, transStack1);
  219. right.lineTo(transX1, transY1);
  220. right.lineTo(transXRight, transStackRight);
  221. right.closePath();
  222. }
  223. }
  224. else { // handle negative value
  225. float transY1 = (float) rangeAxis.valueToJava2D(y1 + stack1[0], dataArea, edge1);
  226. float transStack1 = (float) rangeAxis.valueToJava2D(stack1[0], dataArea, edge1);
  227. float transStackLeft = (float) rangeAxis.valueToJava2D(
  228. adjStackLeft[0], dataArea, edge1
  229. );
  230. // LEFT POLYGON
  231. if (y0 >= 0.0) {
  232. left.moveTo(transX1, transStack1);
  233. left.lineTo(transX1, transY1);
  234. left.lineTo(transXLeft, transStackLeft);
  235. left.clone();
  236. }
  237. else {
  238. double yleft = (y0 + y1) / 2.0 + stackLeft[0];
  239. float transYLeft = (float) rangeAxis.valueToJava2D(yleft, dataArea, edge1);
  240. left.moveTo(transX1, transY1);
  241. left.lineTo(transX1, transStack1);
  242. left.lineTo(transXLeft, transStackLeft);
  243. left.lineTo(transXLeft, transYLeft);
  244. left.closePath();
  245. }
  246. float transStackRight = (float) rangeAxis.valueToJava2D(
  247. adjStackRight[0], dataArea, edge1
  248. );
  249. // RIGHT POLYGON
  250. if (y2 >= 0.0) {
  251. right.moveTo(transX1, transStack1);
  252. right.lineTo(transX1, transY1);
  253. right.lineTo(transXRight, transStackRight);
  254. right.closePath();
  255. }
  256. else {
  257. double yright = (y1 + y2) / 2.0 + stackRight[0];
  258. float transYRight = (float) rangeAxis.valueToJava2D(yright, dataArea, edge1);
  259. right.moveTo(transX1, transStack1);
  260. right.lineTo(transX1, transY1);
  261. right.lineTo(transXRight, transYRight);
  262. right.lineTo(transXRight, transStackRight);
  263. right.closePath();
  264. }
  265. }
  266. // Get series Paint and Stroke
  267. Paint itemPaint = getItemPaint(series, item);
  268. if (pass == 0) {
  269. g2.setPaint(itemPaint);
  270. g2.fill(left);
  271. g2.fill(right);
  272. }
  273. }
  274. /**
  275. * Calculates the stacked value of the all series up to, but not including <code>series</code>
  276. * for the specified category, <code>category</code>. It returns 0.0 if <code>series</code>
  277. * is the first series, i.e. 0.
  278. *
  279. * @param dataset the data.
  280. * @param series the series.
  281. * @param index the index.
  282. *
  283. * @return double returns a cumulative value for all series' values up to
  284. * but excluding <code>series</code> for <code>index</code>.
  285. */
  286. private double[] getStackValues(XYDataset dataset, int series, int index) {
  287. double[] result = new double[2];
  288. for (int i = 0; i < series; i++) {
  289. Number n = dataset.getY(i, index);
  290. if (n != null) {
  291. double v = n.doubleValue();
  292. if (v >= 0.0) {
  293. result[1] += v;
  294. }
  295. else {
  296. result[0] += v;
  297. }
  298. }
  299. }
  300. return result;
  301. }
  302. /**
  303. * Returns a pair of "stack" values calculated from the two specified pairs.
  304. *
  305. * @param stack1 the first stack pair.
  306. * @param stack2 the second stack pair.
  307. *
  308. * @return A pair of average stack values.
  309. */
  310. private double[] averageStackValues(double[] stack1, double[] stack2) {
  311. double[] result = new double[2];
  312. result[0] = (stack1[0] + stack2[0]) / 2.0;
  313. result[1] = (stack1[1] + stack2[1]) / 2.0;
  314. return result;
  315. }
  316. /**
  317. * Returns a pair of "stack" values calculated from the two specified pairs.
  318. *
  319. * @param stack1 the first stack pair.
  320. * @param stack2 the second stack pair.
  321. *
  322. * @return A pair of average stack values.
  323. */
  324. private double[] adjustedStackValues(double[] stack1, double[] stack2) {
  325. double[] result = new double[2];
  326. if (stack1[0] == 0.0 || stack2[0] == 0.0) {
  327. result[0] = 0.0;
  328. }
  329. else {
  330. result[0] = (stack1[0] + stack2[0]) / 2.0;
  331. }
  332. if (stack1[1] == 0.0 || stack2[1] == 0.0) {
  333. result[1] = 0.0;
  334. }
  335. else {
  336. result[1] = (stack1[1] + stack2[1]) / 2.0;
  337. }
  338. return result;
  339. }
  340. /**
  341. * Returns a clone of the renderer.
  342. *
  343. * @return A clone.
  344. *
  345. * @throws CloneNotSupportedException if the renderer cannot be cloned.
  346. */
  347. public Object clone() throws CloneNotSupportedException {
  348. return super.clone();
  349. }
  350. }