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. * XYBarRenderer.java
  26. * ------------------
  27. * (C) Copyright 2001-2005, by Object Refinery Limited.
  28. *
  29. * Original Author: David Gilbert (for Object Refinery Limited);
  30. * Contributor(s): Richard Atkinson;
  31. * Christian W. Zuckschwerdt;
  32. * Bill Kelemen;
  33. *
  34. * $Id: XYBarRenderer.java,v 1.8 2005/01/27 17:46:22 mungady Exp $
  35. *
  36. * Changes
  37. * -------
  38. * 13-Dec-2001 : Version 1, makes VerticalXYBarPlot class redundant (DG);
  39. * 23-Jan-2002 : Added DrawInfo parameter to drawItem(...) method (DG);
  40. * 09-Apr-2002 : Removed the translated zero from the drawItem method. Override the initialise()
  41. * method to calculate it (DG);
  42. * 24-May-2002 : Incorporated tooltips into chart entities (DG);
  43. * 25-Jun-2002 : Removed redundant import (DG);
  44. * 05-Aug-2002 : Small modification to drawItem method to support URLs for HTML image maps (RA);
  45. * 25-Mar-2003 : Implemented Serializable (DG);
  46. * 01-May-2003 : Modified drawItem(...) method signature (DG);
  47. * 30-Jul-2003 : Modified entity constructor (CZ);
  48. * 20-Aug-2003 : Implemented Cloneable and PublicCloneable (DG);
  49. * 24-Aug-2003 : Added null checks in drawItem (BK);
  50. * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG);
  51. * 07-Oct-2003 : Added renderer state (DG);
  52. * 05-Dec-2003 : Changed call to obtain outline paint (DG);
  53. * 10-Feb-2004 : Added state class, updated drawItem() method to make cut-and-paste overriding
  54. * easier, and replaced property change with RendererChangeEvent (DG);
  55. * 25-Feb-2004 : Replaced CrosshairInfo with CrosshairState (DG);
  56. * 26-Apr-2004 : Added gradient paint transformer (DG);
  57. * 19-May-2004 : Fixed bug (879709) with bar zero value for secondary axis (DG);
  58. * 15-Jul-2004 : Switched getX() with getXValue() and getY() with getYValue() (DG);
  59. * 01-Sep-2004 : Added a flag to control whether or not the bar outlines are drawn (DG);
  60. * 03-Sep-2004 : Added option to use y-interval from dataset to determine the length of the
  61. * bars (DG);
  62. * 08-Sep-2004 : Added equals() method and updated clone() method (DG);
  63. * 26-Jan-2005 : Added override for getLegendItem() method (DG);
  64. *
  65. */
  66. package org.jfree.chart.renderer.xy;
  67. import java.awt.GradientPaint;
  68. import java.awt.Graphics2D;
  69. import java.awt.Paint;
  70. import java.awt.Shape;
  71. import java.awt.Stroke;
  72. import java.awt.geom.Rectangle2D;
  73. import java.io.Serializable;
  74. import org.jfree.chart.LegendItem;
  75. import org.jfree.chart.axis.ValueAxis;
  76. import org.jfree.chart.entity.EntityCollection;
  77. import org.jfree.chart.entity.XYItemEntity;
  78. import org.jfree.chart.event.RendererChangeEvent;
  79. import org.jfree.chart.labels.XYToolTipGenerator;
  80. import org.jfree.chart.plot.CrosshairState;
  81. import org.jfree.chart.plot.PlotOrientation;
  82. import org.jfree.chart.plot.PlotRenderingInfo;
  83. import org.jfree.chart.plot.XYPlot;
  84. import org.jfree.data.Range;
  85. import org.jfree.data.general.DatasetUtilities;
  86. import org.jfree.data.xy.IntervalXYDataset;
  87. import org.jfree.data.xy.XYDataset;
  88. import org.jfree.ui.GradientPaintTransformer;
  89. import org.jfree.ui.RectangleEdge;
  90. import org.jfree.ui.StandardGradientPaintTransformer;
  91. import org.jfree.util.ObjectUtilities;
  92. import org.jfree.util.PublicCloneable;
  93. /**
  94. * A renderer that draws bars on an {@link XYPlot} (requires an {@link IntervalXYDataset}).
  95. * <P>
  96. * This renderer does not include any code for calculating the crosshair point.
  97. */
  98. public class XYBarRenderer extends AbstractXYItemRenderer
  99. implements XYItemRenderer,
  100. Cloneable,
  101. PublicCloneable,
  102. Serializable {
  103. /**
  104. * The state class used by this renderer.
  105. */
  106. protected class XYBarRendererState extends XYItemRendererState {
  107. /** Base for bars against the range axis, in Java 2D space. */
  108. private double g2Base;
  109. /**
  110. * Creates a new state object.
  111. *
  112. * @param info the plot rendering info.
  113. */
  114. public XYBarRendererState(PlotRenderingInfo info) {
  115. super(info);
  116. }
  117. /**
  118. * Returns the base (range) value in Java 2D space.
  119. *
  120. * @return The base value.
  121. */
  122. public double getG2Base() {
  123. return this.g2Base;
  124. }
  125. /**
  126. * Sets the range axis base in Java2D space.
  127. *
  128. * @param value the value.
  129. */
  130. public void setG2Base(double value) {
  131. this.g2Base = value;
  132. }
  133. }
  134. /** The default base value for the bars. */
  135. private double base;
  136. /** A flag that controls whether the bars use the YInterval supplied by the dataset. */
  137. private boolean useYInterval;
  138. /** Percentage margin (to reduce the width of bars). */
  139. private double margin;
  140. /** A flag that controls whether or not bar outlines are drawn. */
  141. private boolean drawBarOutline;
  142. /** An optional class used to transform gradient paint objects to fit each bar. */
  143. private GradientPaintTransformer gradientPaintTransformer;
  144. /**
  145. * The default constructor.
  146. */
  147. public XYBarRenderer() {
  148. this(0.0);
  149. }
  150. /**
  151. * Constructs a new renderer.
  152. *
  153. * @param margin the percentage amount to trim from the width of each bar.
  154. */
  155. public XYBarRenderer(double margin) {
  156. super();
  157. this.margin = margin;
  158. this.base = 0.0;
  159. this.useYInterval = false;
  160. this.gradientPaintTransformer = new StandardGradientPaintTransformer();
  161. this.drawBarOutline = true;
  162. }
  163. /**
  164. * Returns the base value for the bars.
  165. *
  166. * @return The base value for the bars.
  167. */
  168. public double getBase() {
  169. return this.base;
  170. }
  171. /**
  172. * Sets the base value for the bars and sends a {@link RendererChangeEvent}
  173. * to all registered listeners. The base value is not used if the dataset's
  174. * y-interval is being used to determine the bar length.
  175. *
  176. * @param base the new base value.
  177. */
  178. public void setBase(double base) {
  179. this.base = base;
  180. notifyListeners(new RendererChangeEvent(this));
  181. }
  182. /**
  183. * Returns a flag that determines whether the y-interval from the dataset is
  184. * used to calculate the length of each bar.
  185. *
  186. * @return A boolean.
  187. */
  188. public boolean getUseYInterval() {
  189. return this.useYInterval;
  190. }
  191. /**
  192. * Sets the flag that determines whether the y-interval from the dataset is
  193. * used to calculate the length of each bar, and sends a
  194. * {@link RendererChangeEvent} to all registered listeners..
  195. *
  196. * @param use the flag.
  197. */
  198. public void setUseYInterval(boolean use) {
  199. this.useYInterval = use;
  200. notifyListeners(new RendererChangeEvent(this));
  201. }
  202. /**
  203. * Returns the margin which is a percentage amount by which the bars are trimmed.
  204. *
  205. * @return The margin.
  206. */
  207. public double getMargin() {
  208. return this.margin;
  209. }
  210. /**
  211. * Sets the percentage amount by which the bars are trimmed and sends a
  212. * {@link RendererChangeEvent} to all registered listeners.
  213. *
  214. * @param margin the new margin.
  215. */
  216. public void setMargin(double margin) {
  217. this.margin = margin;
  218. notifyListeners(new RendererChangeEvent(this));
  219. }
  220. /**
  221. * Returns a flag that controls whether or not bar outlines are drawn.
  222. *
  223. * @return A boolean.
  224. */
  225. public boolean isDrawBarOutline() {
  226. return this.drawBarOutline;
  227. }
  228. /**
  229. * Sets the flag that controls whether or not bar outlines are drawn and sends a
  230. * {@link RendererChangeEvent} to all registered listeners.
  231. *
  232. * @param draw the flag.
  233. */
  234. public void setDrawBarOutline(boolean draw) {
  235. this.drawBarOutline = draw;
  236. notifyListeners(new RendererChangeEvent(this));
  237. }
  238. /**
  239. * Returns the gradient paint transformer (an object used to transform gradient paint objects
  240. * to fit each bar.
  241. *
  242. * @return A transformer (<code>null</code> possible).
  243. */
  244. public GradientPaintTransformer getGradientPaintTransformer() {
  245. return this.gradientPaintTransformer;
  246. }
  247. /**
  248. * Sets the gradient paint transformer and sends a {@link RendererChangeEvent} to all registered
  249. * listeners.
  250. *
  251. * @param transformer the transformer (<code>null</code> permitted).
  252. */
  253. public void setGradientPaintTransformer(GradientPaintTransformer transformer) {
  254. this.gradientPaintTransformer = transformer;
  255. notifyListeners(new RendererChangeEvent(this));
  256. }
  257. /**
  258. * Initialises the renderer and returns a state object that should be passed to all subsequent
  259. * calls to the drawItem() method. Here we calculate the Java2D y-coordinate for zero, since
  260. * all the bars have their bases fixed at zero.
  261. *
  262. * @param g2 the graphics device.
  263. * @param dataArea the area inside the axes.
  264. * @param plot the plot.
  265. * @param dataset the data.
  266. * @param info an optional info collection object to return data back to the caller.
  267. *
  268. * @return A state object.
  269. */
  270. public XYItemRendererState initialise(Graphics2D g2,
  271. Rectangle2D dataArea,
  272. XYPlot plot,
  273. XYDataset dataset,
  274. PlotRenderingInfo info) {
  275. XYBarRendererState state = new XYBarRendererState(info);
  276. ValueAxis rangeAxis = plot.getRangeAxisForDataset(plot.indexOf(dataset));
  277. state.setG2Base(rangeAxis.valueToJava2D(this.base, dataArea, plot.getRangeAxisEdge()));
  278. return state;
  279. }
  280. /**
  281. * Returns a default legend item for the specified series. Subclasses
  282. * should override this method to generate customised items.
  283. *
  284. * @param datasetIndex the dataset index (zero-based).
  285. * @param series the series index (zero-based).
  286. *
  287. * @return A legend item for the series.
  288. */
  289. public LegendItem getLegendItem(int datasetIndex, int series) {
  290. LegendItem result = null;
  291. XYPlot xyplot = getPlot();
  292. if (xyplot != null) {
  293. XYDataset dataset = xyplot.getDataset(datasetIndex);
  294. if (dataset != null) {
  295. String label = getLegendItemLabelGenerator().generateLabel(dataset, series);
  296. String description = label;
  297. Shape shape = new Rectangle2D.Double(-4.0, -4.0, 8.0, 8.0);
  298. Paint paint = getSeriesPaint(series);
  299. Paint outlinePaint = getSeriesOutlinePaint(series);
  300. Stroke outlineStroke = getSeriesOutlineStroke(series);
  301. result = new LegendItem(
  302. label, description, shape, paint, outlineStroke, outlinePaint
  303. );
  304. }
  305. }
  306. return result;
  307. }
  308. /**
  309. * Draws the visual representation of a single data item.
  310. *
  311. * @param g2 the graphics device.
  312. * @param state the renderer state.
  313. * @param dataArea the area within which the plot is being drawn.
  314. * @param info collects information about the drawing.
  315. * @param plot the plot (can be used to obtain standard color information etc).
  316. * @param domainAxis the domain axis.
  317. * @param rangeAxis the range axis.
  318. * @param dataset the dataset.
  319. * @param series the series index (zero-based).
  320. * @param item the item index (zero-based).
  321. * @param crosshairState crosshair information for the plot (<code>null</code> permitted).
  322. * @param pass the pass index.
  323. */
  324. public void drawItem(Graphics2D g2,
  325. XYItemRendererState state,
  326. Rectangle2D dataArea,
  327. PlotRenderingInfo info,
  328. XYPlot plot,
  329. ValueAxis domainAxis,
  330. ValueAxis rangeAxis,
  331. XYDataset dataset,
  332. int series,
  333. int item,
  334. CrosshairState crosshairState,
  335. int pass) {
  336. IntervalXYDataset intervalDataset = (IntervalXYDataset) dataset;
  337. double value0;
  338. double value1;
  339. if (this.useYInterval) {
  340. value0 = intervalDataset.getStartYValue(series, item);
  341. value1 = intervalDataset.getEndYValue(series, item);
  342. }
  343. else {
  344. value0 = this.base;
  345. value1 = intervalDataset.getYValue(series, item);
  346. }
  347. if (Double.isNaN(value0) || Double.isNaN(value1)) {
  348. return;
  349. }
  350. double translatedValue0 = rangeAxis.valueToJava2D(
  351. value0, dataArea, plot.getRangeAxisEdge()
  352. );
  353. double translatedValue1 = rangeAxis.valueToJava2D(
  354. value1, dataArea, plot.getRangeAxisEdge()
  355. );
  356. RectangleEdge location = plot.getDomainAxisEdge();
  357. Number startXNumber = intervalDataset.getStartX(series, item);
  358. if (startXNumber == null) {
  359. return;
  360. }
  361. double translatedStartX = domainAxis.valueToJava2D(
  362. startXNumber.doubleValue(), dataArea, location
  363. );
  364. Number endXNumber = intervalDataset.getEndX(series, item);
  365. if (endXNumber == null) {
  366. return;
  367. }
  368. double translatedEndX = domainAxis.valueToJava2D(
  369. endXNumber.doubleValue(), dataArea, location
  370. );
  371. double translatedWidth = Math.max(1, Math.abs(translatedEndX - translatedStartX));
  372. double translatedHeight = Math.abs(translatedValue1 - translatedValue0);
  373. if (getMargin() > 0.0) {
  374. double cut = translatedWidth * getMargin();
  375. translatedWidth = translatedWidth - cut;
  376. translatedStartX = translatedStartX + cut / 2;
  377. }
  378. Rectangle2D bar = null;
  379. PlotOrientation orientation = plot.getOrientation();
  380. if (orientation == PlotOrientation.HORIZONTAL) {
  381. bar = new Rectangle2D.Double(
  382. Math.min(translatedValue0, translatedValue1), translatedEndX,
  383. translatedHeight, translatedWidth
  384. );
  385. }
  386. else if (orientation == PlotOrientation.VERTICAL) {
  387. bar = new Rectangle2D.Double(
  388. translatedStartX, Math.min(translatedValue0, translatedValue1),
  389. translatedWidth, translatedHeight
  390. );
  391. }
  392. Paint itemPaint = getItemPaint(series, item);
  393. if (getGradientPaintTransformer() != null && itemPaint instanceof GradientPaint) {
  394. GradientPaint gp = (GradientPaint) itemPaint;
  395. itemPaint = getGradientPaintTransformer().transform(gp, bar);
  396. }
  397. g2.setPaint(itemPaint);
  398. g2.fill(bar);
  399. if (isDrawBarOutline() && Math.abs(translatedEndX - translatedStartX) > 3) {
  400. Stroke stroke = getItemOutlineStroke(series, item);
  401. Paint paint = getItemOutlinePaint(series, item);
  402. if (stroke != null && paint != null) {
  403. g2.setStroke(stroke);
  404. g2.setPaint(paint);
  405. g2.draw(bar);
  406. }
  407. }
  408. // add an entity for the item...
  409. if (info != null) {
  410. EntityCollection entities = info.getOwner().getEntityCollection();
  411. if (entities != null) {
  412. String tip = null;
  413. XYToolTipGenerator generator = getToolTipGenerator(series, item);
  414. if (generator != null) {
  415. tip = generator.generateToolTip(dataset, series, item);
  416. }
  417. String url = null;
  418. if (getURLGenerator() != null) {
  419. url = getURLGenerator().generateURL(dataset, series, item);
  420. }
  421. XYItemEntity entity = new XYItemEntity(bar, dataset, series, item, tip, url);
  422. entities.add(entity);
  423. }
  424. }
  425. }
  426. /**
  427. * Returns the lower and upper bounds (range) of the x-values in the
  428. * specified dataset. Since this renderer uses the x-interval in the
  429. * dataset, this is taken into account for the range.
  430. *
  431. * @param dataset the dataset (<code>null</code> permitted).
  432. *
  433. * @return The range (<code>null</code> if the dataset is <code>null</code> or empty).
  434. */
  435. public Range findDomainBounds(XYDataset dataset) {
  436. if (dataset != null) {
  437. return DatasetUtilities.findDomainBounds(dataset, true);
  438. }
  439. else {
  440. return null;
  441. }
  442. }
  443. /**
  444. * Returns a clone of the renderer.
  445. *
  446. * @return A clone.
  447. *
  448. * @throws CloneNotSupportedException if the renderer cannot be cloned.
  449. */
  450. public Object clone() throws CloneNotSupportedException {
  451. XYBarRenderer result = (XYBarRenderer) super.clone();
  452. if (this.gradientPaintTransformer != null) {
  453. result.gradientPaintTransformer = (GradientPaintTransformer)
  454. ObjectUtilities.clone(this.gradientPaintTransformer);
  455. }
  456. return result;
  457. }
  458. /**
  459. * Tests this renderer for equality with an arbitrary object.
  460. *
  461. * @param obj the object to test against (<code>null</code> permitted).
  462. *
  463. * @return A boolean.
  464. */
  465. public boolean equals(Object obj) {
  466. if (obj == this) {
  467. return true;
  468. }
  469. if (!(obj instanceof XYBarRenderer)) {
  470. return false;
  471. }
  472. if (!super.equals(obj)) {
  473. return false;
  474. }
  475. XYBarRenderer that = (XYBarRenderer) obj;
  476. if (this.base != that.base) {
  477. return false;
  478. }
  479. if (this.drawBarOutline != that.drawBarOutline) {
  480. return false;
  481. }
  482. if (this.margin != that.margin) {
  483. return false;
  484. }
  485. if (this.useYInterval != that.useYInterval) {
  486. return false;
  487. }
  488. if (!ObjectUtilities.equal(this.gradientPaintTransformer, that.gradientPaintTransformer)) {
  489. return false;
  490. }
  491. return true;
  492. }
  493. }