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. * StackedXYAreaRenderer.java
  26. * --------------------------
  27. * (C) Copyright 2003-2005, by Richard Atkinson and Contributors.
  28. *
  29. * Original Author: Richard Atkinson;
  30. * Contributor(s): Christian W. Zuckschwerdt;
  31. * David Gilbert (for Object Refinery Limited);
  32. *
  33. * $Id: StackedXYAreaRenderer.java,v 1.6 2005/01/07 17:35:17 mungady Exp $
  34. *
  35. * Changes:
  36. * --------
  37. * 27-Jul-2003 : Initial version (RA);
  38. * 30-Jul-2003 : Modified entity constructor (CZ);
  39. * 18-Aug-2003 : Now handles null values (RA);
  40. * 20-Aug-2003 : Implemented Cloneable, PublicCloneable and Serializable (DG);
  41. * 22-Sep-2003 : Changed to be a two pass renderer with optional shape Paint and Stroke (RA);
  42. * 07-Oct-2003 : Added renderer state (DG);
  43. * 10-Feb-2004 : Updated state object and changed drawItem() method to make overriding
  44. * easier (DG);
  45. * 25-Feb-2004 : Replaced CrosshairInfo with CrosshairState. Renamed XYToolTipGenerator
  46. * --> XYItemLabelGenerator (DG);
  47. * 15-Jul-2004 : Switched getX() with getXValue() and getY() with getYValue() (DG);
  48. * 10-Sep-2004 : Removed getRangeType() method (DG);
  49. * 11-Nov-2004 : Now uses ShapeUtilities to translate shapes (DG);
  50. * 06-Jan-2005 : Override equals() (DG);
  51. * 07-Jan-2005 : Update for method name changes in DatasetUtilities (DG);
  52. *
  53. */
  54. package org.jfree.chart.renderer.xy;
  55. import java.awt.Graphics2D;
  56. import java.awt.Paint;
  57. import java.awt.Point;
  58. import java.awt.Polygon;
  59. import java.awt.Shape;
  60. import java.awt.Stroke;
  61. import java.awt.geom.Line2D;
  62. import java.awt.geom.Rectangle2D;
  63. import java.io.Serializable;
  64. import java.util.Stack;
  65. import org.jfree.chart.axis.ValueAxis;
  66. import org.jfree.chart.entity.EntityCollection;
  67. import org.jfree.chart.entity.XYItemEntity;
  68. import org.jfree.chart.labels.XYToolTipGenerator;
  69. import org.jfree.chart.plot.CrosshairState;
  70. import org.jfree.chart.plot.PlotOrientation;
  71. import org.jfree.chart.plot.PlotRenderingInfo;
  72. import org.jfree.chart.plot.XYPlot;
  73. import org.jfree.chart.urls.XYURLGenerator;
  74. import org.jfree.data.Range;
  75. import org.jfree.data.general.DatasetUtilities;
  76. import org.jfree.data.xy.TableXYDataset;
  77. import org.jfree.data.xy.XYDataset;
  78. import org.jfree.util.ObjectUtilities;
  79. import org.jfree.util.PublicCloneable;
  80. import org.jfree.util.ShapeUtilities;
  81. /**
  82. * A stacked area renderer for the {@link XYPlot} class.
  83. */
  84. public class StackedXYAreaRenderer extends XYAreaRenderer
  85. implements Cloneable,
  86. PublicCloneable,
  87. Serializable {
  88. /**
  89. * A state object for use by this renderer.
  90. */
  91. static class StackedXYAreaRendererState extends XYItemRendererState {
  92. /** The area for the current series. */
  93. private Polygon seriesArea;
  94. /** The line. */
  95. private Line2D line;
  96. /** The points from the last series. */
  97. private Stack lastSeriesPoints;
  98. /** The points for the current series. */
  99. private Stack currentSeriesPoints;
  100. /**
  101. * Creates a new state for the renderer.
  102. *
  103. * @param info the plot rendering info.
  104. */
  105. public StackedXYAreaRendererState(PlotRenderingInfo info) {
  106. super(info);
  107. this.seriesArea = null;
  108. this.line = null;
  109. this.lastSeriesPoints = new Stack();
  110. this.currentSeriesPoints = null;
  111. }
  112. /**
  113. * Returns the series area.
  114. *
  115. * @return the series area.
  116. */
  117. public Polygon getSeriesArea() {
  118. return this.seriesArea;
  119. }
  120. /**
  121. * Sets the series area.
  122. *
  123. * @param area the area.
  124. */
  125. public void setSeriesArea(Polygon area) {
  126. this.seriesArea = area;
  127. }
  128. /**
  129. * Returns the working line.
  130. *
  131. * @return the working line.
  132. */
  133. public Line2D getLine() {
  134. return this.line;
  135. }
  136. /**
  137. * Returns the current series points.
  138. *
  139. * @return the current series points.
  140. */
  141. public Stack getCurrentSeriesPoints() {
  142. return this.currentSeriesPoints;
  143. }
  144. /**
  145. * Sets the current series points.
  146. *
  147. * @param points the points.
  148. */
  149. public void setCurrentSeriesPoints(Stack points) {
  150. this.currentSeriesPoints = points;
  151. }
  152. /**
  153. * Returns the last series points.
  154. *
  155. * @return the last series points.
  156. */
  157. public Stack getLastSeriesPoints() {
  158. return this.lastSeriesPoints;
  159. }
  160. /**
  161. * Sets the last series points.
  162. *
  163. * @param points the points.
  164. */
  165. public void setLastSeriesPoints(Stack points) {
  166. this.lastSeriesPoints = points;
  167. }
  168. }
  169. /** Custom Paint for drawing all shapes, if null defaults to series shapes */
  170. private Paint shapePaint = null;
  171. /** Custom Stroke for drawing all shapes, if null defaults to series strokes */
  172. private Stroke shapeStroke = null;
  173. /**
  174. * Creates a new renderer.
  175. */
  176. public StackedXYAreaRenderer() {
  177. this(AREA);
  178. }
  179. /**
  180. * Constructs a new renderer.
  181. *
  182. * @param type the type of the renderer.
  183. */
  184. public StackedXYAreaRenderer(int type) {
  185. this(type, null, null);
  186. }
  187. /**
  188. * Constructs a new renderer.
  189. * <p>
  190. * To specify the type of renderer, use one of the constants: SHAPES, LINES,
  191. * SHAPES_AND_LINES, AREA or AREA_AND_SHAPES.
  192. *
  193. * @param type the type of renderer.
  194. * @param labelGenerator the tool tip generator to use. <code>null</code> is none.
  195. * @param urlGenerator the URL generator (null permitted).
  196. */
  197. public StackedXYAreaRenderer(int type,
  198. XYToolTipGenerator labelGenerator, XYURLGenerator urlGenerator) {
  199. super(type, labelGenerator, urlGenerator);
  200. }
  201. /**
  202. * Returns the paint used for rendering shapes, or <code>null</code> if using series paints.
  203. *
  204. * @return The Paint.
  205. */
  206. public Paint getShapePaint() {
  207. return this.shapePaint;
  208. }
  209. /**
  210. * Returns the stroke used for rendering shapes, or <code>null</code> if using series strokes.
  211. *
  212. * @return The stroke.
  213. */
  214. public Stroke getShapeStroke() {
  215. return this.shapeStroke;
  216. }
  217. /**
  218. * Sets the paint for rendering shapes.
  219. *
  220. * @param shapePaint the paint (<code>null</code> permitted).
  221. */
  222. public void setShapePaint(Paint shapePaint) {
  223. this.shapePaint = shapePaint;
  224. }
  225. /**
  226. * Sets the stroke for rendering shapes.
  227. *
  228. * @param shapeStroke the stroke (<code>null</code> permitted).
  229. */
  230. public void setShapeStroke(Stroke shapeStroke) {
  231. this.shapeStroke = shapeStroke;
  232. }
  233. /**
  234. * Initialises the renderer.
  235. * <P>
  236. * This method will be called before the first item is rendered, giving the
  237. * renderer an opportunity to initialise any state information it wants to maintain.
  238. * The renderer can do nothing if it chooses.
  239. *
  240. * @param g2 the graphics device.
  241. * @param dataArea the area inside the axes.
  242. * @param plot the plot.
  243. * @param data the data.
  244. * @param info an optional info collection object to return data back to the caller.
  245. *
  246. * @return A state object that should be passed to subsequent calls to the drawItem() method.
  247. */
  248. public XYItemRendererState initialise(Graphics2D g2,
  249. Rectangle2D dataArea,
  250. XYPlot plot,
  251. XYDataset data,
  252. PlotRenderingInfo info) {
  253. return new StackedXYAreaRendererState(info);
  254. }
  255. /**
  256. * Returns the number of passes required by the renderer.
  257. *
  258. * @return 2.
  259. */
  260. public int getPassCount() {
  261. return 2;
  262. }
  263. /**
  264. * Returns the range of values the renderer requires to display all the items from the
  265. * specified dataset.
  266. *
  267. * @param dataset the dataset (<code>null</code> permitted).
  268. *
  269. * @return The range (<code>null</code> if the dataset is <code>null</code> or empty).
  270. */
  271. public Range findRangeBounds(XYDataset dataset) {
  272. if (dataset != null) {
  273. return DatasetUtilities.findStackedRangeBounds((TableXYDataset) dataset);
  274. }
  275. else {
  276. return null;
  277. }
  278. }
  279. /**
  280. * Draws the visual representation of a single data item.
  281. *
  282. * @param g2 the graphics device.
  283. * @param state the renderer state.
  284. * @param dataArea the area within which the data is being drawn.
  285. * @param info collects information about the drawing.
  286. * @param plot the plot (can be used to obtain standard color information etc).
  287. * @param domainAxis the domain axis.
  288. * @param rangeAxis the range axis.
  289. * @param dataset the dataset.
  290. * @param series the series index (zero-based).
  291. * @param item the item index (zero-based).
  292. * @param crosshairState information about crosshairs on a plot.
  293. * @param pass the pass index.
  294. */
  295. public void drawItem(Graphics2D g2,
  296. XYItemRendererState state,
  297. Rectangle2D dataArea,
  298. PlotRenderingInfo info,
  299. XYPlot plot,
  300. ValueAxis domainAxis,
  301. ValueAxis rangeAxis,
  302. XYDataset dataset,
  303. int series,
  304. int item,
  305. CrosshairState crosshairState,
  306. int pass) {
  307. PlotOrientation orientation = plot.getOrientation();
  308. StackedXYAreaRendererState areaState = (StackedXYAreaRendererState) state;
  309. // Get the item count for the series, so that we can know which is the end of the series.
  310. TableXYDataset tableXYDataset = (TableXYDataset) dataset;
  311. int itemCount = tableXYDataset.getItemCount();
  312. // get the data point...
  313. Number x1 = dataset.getX(series, item);
  314. Number y1 = dataset.getY(series, item);
  315. boolean nullPoint = false;
  316. if (y1 == null) {
  317. y1 = new Double(0);
  318. nullPoint = true;
  319. }
  320. // Get height adjustment based on stack and translate to Java2D values
  321. double ph1 = this.getPreviousHeight(dataset, series, item);
  322. double transX1 = domainAxis.valueToJava2D(
  323. x1.doubleValue(), dataArea, plot.getDomainAxisEdge()
  324. );
  325. double transY1 = rangeAxis.valueToJava2D(
  326. y1.doubleValue() + ph1, dataArea, plot.getRangeAxisEdge()
  327. );
  328. // Get series Paint and Stroke
  329. Paint seriesPaint = getItemPaint(series, item);
  330. Stroke seriesStroke = getItemStroke(series, item);
  331. if (pass == 0) {
  332. // On first pass renderer the areas, line and outlines
  333. if (item == 0) {
  334. // Create a new Area for the series
  335. areaState.setSeriesArea(new Polygon());
  336. areaState.setLastSeriesPoints(areaState.getCurrentSeriesPoints());
  337. areaState.setCurrentSeriesPoints(new Stack());
  338. // start from previous height (ph1)
  339. double transY2 = rangeAxis.valueToJava2D(ph1, dataArea, plot.getRangeAxisEdge());
  340. // The first point is (x, 0)
  341. if (orientation == PlotOrientation.VERTICAL) {
  342. areaState.getSeriesArea().addPoint((int) transX1, (int) transY2);
  343. }
  344. else if (orientation == PlotOrientation.HORIZONTAL) {
  345. areaState.getSeriesArea().addPoint((int) transY2, (int) transX1);
  346. }
  347. }
  348. // Add each point to Area (x, y)
  349. if (orientation == PlotOrientation.VERTICAL) {
  350. Point point = new Point((int) transX1, (int) transY1);
  351. areaState.getSeriesArea().addPoint((int) point.getX(), (int) point.getY());
  352. areaState.getCurrentSeriesPoints().push(point);
  353. }
  354. else if (orientation == PlotOrientation.HORIZONTAL) {
  355. areaState.getSeriesArea().addPoint((int) transY1, (int) transX1);
  356. }
  357. if (this.getPlotLines()) {
  358. if (item > 0) {
  359. // get the previous data point...
  360. Number x0 = dataset.getX(series, item - 1);
  361. Number y0 = dataset.getY(series, item - 1);
  362. double ph0 = this.getPreviousHeight(dataset, series, item - 1);
  363. double transX0 = domainAxis.valueToJava2D(
  364. x0.doubleValue(), dataArea, plot.getDomainAxisEdge()
  365. );
  366. double transY0 = rangeAxis.valueToJava2D(
  367. y0.doubleValue() + ph0, dataArea, plot.getRangeAxisEdge()
  368. );
  369. if (orientation == PlotOrientation.VERTICAL) {
  370. areaState.getLine().setLine(transX0, transY0, transX1, transY1);
  371. }
  372. else if (orientation == PlotOrientation.HORIZONTAL) {
  373. areaState.getLine().setLine(transY0, transX0, transY1, transX1);
  374. }
  375. g2.draw(areaState.getLine());
  376. }
  377. }
  378. // Check if the item is the last item for the series.
  379. // and number of items > 0. We can't draw an area for a single point.
  380. if (this.getPlotArea() && item > 0 && item == (itemCount - 1)) {
  381. double transY2 = rangeAxis.valueToJava2D(ph1, dataArea, plot.getRangeAxisEdge());
  382. if (orientation == PlotOrientation.VERTICAL) {
  383. // Add the last point (x,0)
  384. areaState.getSeriesArea().addPoint((int) transX1, (int) transY2);
  385. }
  386. else if (orientation == PlotOrientation.HORIZONTAL) {
  387. // Add the last point (x,0)
  388. areaState.getSeriesArea().addPoint((int) transY2, (int) transX1);
  389. }
  390. // Add points from last series to complete the base of the polygon
  391. if (series != 0) {
  392. Stack points = areaState.getLastSeriesPoints();
  393. while (!points.empty()) {
  394. Point point = (Point) points.pop();
  395. areaState.getSeriesArea().addPoint((int) point.getX(), (int) point.getY());
  396. }
  397. }
  398. // Fill the polygon
  399. g2.setPaint(seriesPaint);
  400. g2.setStroke(seriesStroke);
  401. g2.fill(areaState.getSeriesArea());
  402. // Draw an outline around the Area.
  403. if (this.isOutline()) {
  404. g2.setStroke(plot.getOutlineStroke());
  405. g2.setPaint(plot.getOutlinePaint());
  406. g2.draw(areaState.getSeriesArea());
  407. }
  408. }
  409. updateCrosshairValues(
  410. crosshairState, x1.doubleValue(), y1.doubleValue(), transX1, transY1, orientation
  411. );
  412. }
  413. else if (pass == 1) {
  414. // On second pass render shapes and collect entity and tooltip information
  415. Shape shape = null;
  416. if (getPlotShapes()) {
  417. shape = getItemShape(series, item);
  418. if (plot.getOrientation() == PlotOrientation.VERTICAL) {
  419. shape = ShapeUtilities.createTranslatedShape(shape, transX1, transY1);
  420. }
  421. else if (plot.getOrientation() == PlotOrientation.HORIZONTAL) {
  422. shape = ShapeUtilities.createTranslatedShape(shape, transY1, transX1);
  423. }
  424. if (!nullPoint) {
  425. if (getShapePaint() != null) {
  426. g2.setPaint(getShapePaint());
  427. }
  428. else {
  429. g2.setPaint(seriesPaint);
  430. }
  431. if (getShapeStroke() != null) {
  432. g2.setStroke(getShapeStroke());
  433. }
  434. else {
  435. g2.setStroke(seriesStroke);
  436. }
  437. g2.draw(shape);
  438. }
  439. }
  440. else {
  441. if (plot.getOrientation() == PlotOrientation.VERTICAL) {
  442. shape = new Rectangle2D.Double(transX1 - 3, transY1 - 3, 6.0, 6.0);
  443. }
  444. else if (plot.getOrientation() == PlotOrientation.HORIZONTAL) {
  445. shape = new Rectangle2D.Double(transY1 - 3, transX1 - 3, 6.0, 6.0);
  446. }
  447. }
  448. // collect entity and tool tip information...
  449. if (state.getInfo() != null) {
  450. EntityCollection entities = state.getInfo().getOwner().getEntityCollection();
  451. if (entities != null && shape != null && !nullPoint) {
  452. String tip = null;
  453. XYToolTipGenerator generator = getToolTipGenerator(series, item);
  454. if (generator != null) {
  455. tip = generator.generateToolTip(dataset, series, item);
  456. }
  457. String url = null;
  458. if (getURLGenerator() != null) {
  459. url = getURLGenerator().generateURL(dataset, series, item);
  460. }
  461. XYItemEntity entity = new XYItemEntity(shape, dataset, series, item, tip, url);
  462. entities.add(entity);
  463. }
  464. }
  465. }
  466. }
  467. /**
  468. * Calculates the stacked value of the all series up to, but not including <code>series</code>
  469. * for the specified category, <code>category</code>. It returns 0.0 if <code>series</code>
  470. * is the first series, i.e. 0.
  471. *
  472. * @param data the data.
  473. * @param series the series.
  474. * @param index the index.
  475. *
  476. * @return double returns a cumulative value for all series' values up to
  477. * but excluding <code>series</code> for <code>index</code>.
  478. */
  479. protected double getPreviousHeight(XYDataset data, int series, int index) {
  480. double result = 0.0;
  481. Number tmp;
  482. for (int i = 0; i < series; i++) {
  483. tmp = data.getY(i, index);
  484. if (tmp != null) {
  485. result += tmp.doubleValue();
  486. }
  487. }
  488. return result;
  489. }
  490. /**
  491. * Tests the renderer for equality with an arbitrary object.
  492. *
  493. * @param obj the object (<code>null</code> permitted).
  494. *
  495. * @return A boolean.
  496. */
  497. public boolean equals(Object obj) {
  498. if (obj == this) {
  499. return true;
  500. }
  501. if (!(obj instanceof StackedXYAreaRenderer) || !super.equals(obj)) {
  502. return false;
  503. }
  504. StackedXYAreaRenderer that = (StackedXYAreaRenderer) obj;
  505. if (!ObjectUtilities.equal(this.shapePaint, that.shapePaint)) {
  506. return false;
  507. }
  508. if (!ObjectUtilities.equal(this.shapeStroke, that.shapeStroke)) {
  509. return false;
  510. }
  511. return true;
  512. }
  513. /**
  514. * Returns a clone of the renderer.
  515. *
  516. * @return A clone.
  517. *
  518. * @throws CloneNotSupportedException if the renderer cannot be cloned.
  519. */
  520. public Object clone() throws CloneNotSupportedException {
  521. return super.clone();
  522. }
  523. }