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. * CandlestickRenderer.java
  26. * ------------------------
  27. * (C) Copyright 2001-2004, by Object Refinery Limited.
  28. *
  29. * Original Authors: David Gilbert (for Object Refinery Limited);
  30. * Sylvain Vieujot;
  31. * Contributor(s): Richard Atkinson;
  32. * Christian W. Zuckschwerdt;
  33. * Jerome Fisher;
  34. *
  35. * $Id: CandlestickRenderer.java,v 1.3 2004/09/30 16:44:13 mungady Exp $
  36. *
  37. * Changes
  38. * -------
  39. * 13-Dec-2001 : Version 1. Based on code in the CandlestickPlot class, written by Sylvain
  40. * Vieujot, which now is redundant (DG);
  41. * 23-Jan-2002 : Added DrawInfo parameter to drawItem(...) method (DG);
  42. * 28-Mar-2002 : Added a property change listener mechanism so that renderers no longer need to be
  43. * immutable. Added properties for up and down colors (DG);
  44. * 04-Apr-2002 : Updated with new automatic width calculation and optional volume display,
  45. * contributed by Sylvain Vieujot (DG);
  46. * 09-Apr-2002 : Removed translatedRangeZero from the drawItem(...) method, and changed the return
  47. * type of the drawItem method to void, reflecting a change in the XYItemRenderer
  48. * interface. Added tooltip code to drawItem(...) method (DG);
  49. * 25-Jun-2002 : Removed redundant code (DG);
  50. * 05-Aug-2002 : Small modification to drawItem method to support URLs for HTML image maps (RA);
  51. * 19-Sep-2002 : Fixed errors reported by Checkstyle (DG);
  52. * 25-Mar-2003 : Implemented Serializable (DG);
  53. * 01-May-2003 : Modified drawItem(...) method signature (DG);
  54. * 30-Jun-2003 : Added support for PlotOrientation (for completeness, this renderer is unlikely
  55. * to be used with a HORIZONTAL orientation) (DG);
  56. * 30-Jul-2003 : Modified entity constructor (CZ);
  57. * 20-Aug-2003 : Implemented Cloneable and PublicCloneable (DG);
  58. * 29-Aug-2003 : Moved maxVolume calculation to initialise method (see bug report 796619) (DG);
  59. * 02-Sep-2003 : Added maxCandleWidthInMilliseconds as workaround for bug 796621 (DG);
  60. * 08-Sep-2003 : Changed ValueAxis API (DG);
  61. * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG);
  62. * 13-Oct-2003 : Applied patch from Jerome Fisher to improve auto width calculations (DG);
  63. * 23-Dec-2003 : Fixed bug where up and down paint are used incorrectly (DG);
  64. * 25-Feb-2004 : Replaced CrosshairInfo with CrosshairState (DG);
  65. * 15-Jul-2004 : Switched getX() with getXValue() and getY() with getYValue() (DG);
  66. *
  67. */
  68. package org.jfree.chart.renderer.xy;
  69. import java.awt.AlphaComposite;
  70. import java.awt.Color;
  71. import java.awt.Composite;
  72. import java.awt.Graphics2D;
  73. import java.awt.Paint;
  74. import java.awt.Shape;
  75. import java.awt.Stroke;
  76. import java.awt.geom.Line2D;
  77. import java.awt.geom.Rectangle2D;
  78. import java.io.IOException;
  79. import java.io.ObjectInputStream;
  80. import java.io.ObjectOutputStream;
  81. import java.io.Serializable;
  82. import org.jfree.chart.axis.ValueAxis;
  83. import org.jfree.chart.entity.EntityCollection;
  84. import org.jfree.chart.entity.XYItemEntity;
  85. import org.jfree.chart.event.RendererChangeEvent;
  86. import org.jfree.chart.labels.HighLowItemLabelGenerator;
  87. import org.jfree.chart.labels.XYToolTipGenerator;
  88. import org.jfree.chart.plot.CrosshairState;
  89. import org.jfree.chart.plot.PlotOrientation;
  90. import org.jfree.chart.plot.PlotRenderingInfo;
  91. import org.jfree.chart.plot.XYPlot;
  92. import org.jfree.data.xy.OHLCDataset;
  93. import org.jfree.data.xy.IntervalXYDataset;
  94. import org.jfree.data.xy.XYDataset;
  95. import org.jfree.io.SerialUtilities;
  96. import org.jfree.ui.RectangleEdge;
  97. import org.jfree.util.PublicCloneable;
  98. /**
  99. * A renderer that draws candlesticks on an {@link XYPlot} (requires a {@link OHLCDataset}).
  100. * <P>
  101. * This renderer does not include code to calculate the crosshair point for the plot.
  102. *
  103. * @author Sylvain Vieujot
  104. */
  105. public class CandlestickRenderer extends AbstractXYItemRenderer
  106. implements XYItemRenderer,
  107. Cloneable,
  108. PublicCloneable,
  109. Serializable {
  110. /** The average width method. */
  111. public static final int WIDTHMETHOD_AVERAGE = 0;
  112. /** The smallest width method. */
  113. public static final int WIDTHMETHOD_SMALLEST = 1;
  114. /** The interval data method. */
  115. public static final int WIDTHMETHOD_INTERVALDATA = 2;
  116. /** The method of automatically calculating the candle width. */
  117. private int autoWidthMethod = WIDTHMETHOD_AVERAGE;
  118. /**
  119. * The number (generally between 0.0 and 1.0) by which the available space automatically
  120. * calculated for the candles will be multiplied to determine the actual width to use.
  121. */
  122. private double autoWidthFactor = 4.5 / 7;
  123. /** The minimum gap between one candle and the next */
  124. private double autoWidthGap = 0.0;
  125. /** The candle width. */
  126. private double candleWidth;
  127. /** The maximum candlewidth in milliseconds. */
  128. private double maxCandleWidthInMilliseconds = 1000.0 * 60.0 * 60.0 * 20.0;
  129. /** Temporary storage for the maximum candle width. */
  130. private double maxCandleWidth;
  131. /** The paint used to fill the candle when the price moved up from open to close. */
  132. private transient Paint upPaint;
  133. /** The paint used to fill the candle when the price moved down from open to close. */
  134. private transient Paint downPaint;
  135. /** A flag controlling whether or not volume bars are drawn on the chart. */
  136. private boolean drawVolume;
  137. /** Temporary storage for the maximum volume. */
  138. private transient double maxVolume;
  139. /**
  140. * Creates a new renderer for candlestick charts.
  141. */
  142. public CandlestickRenderer() {
  143. this(-1.0);
  144. }
  145. /**
  146. * Creates a new renderer for candlestick charts.
  147. * <P>
  148. * Use -1 for the candle width if you prefer the width to be calculated automatically.
  149. *
  150. * @param candleWidth The candle width.
  151. */
  152. public CandlestickRenderer(double candleWidth) {
  153. this(candleWidth, true, new HighLowItemLabelGenerator());
  154. }
  155. /**
  156. * Creates a new renderer for candlestick charts.
  157. * <P>
  158. * Use -1 for the candle width if you prefer the width to be calculated automatically.
  159. *
  160. * @param candleWidth the candle width.
  161. * @param drawVolume a flag indicating whether or not volume bars should be drawn.
  162. * @param toolTipGenerator the tool tip generator. <code>null</code> is none.
  163. */
  164. public CandlestickRenderer(double candleWidth, boolean drawVolume,
  165. XYToolTipGenerator toolTipGenerator) {
  166. super();
  167. setToolTipGenerator(toolTipGenerator);
  168. this.candleWidth = candleWidth;
  169. this.drawVolume = drawVolume;
  170. this.upPaint = Color.green;
  171. this.downPaint = Color.red;
  172. }
  173. /**
  174. * Returns the width of each candle.
  175. *
  176. * @return the candle width.
  177. *
  178. * @see org.jfree.chart.renderer.xy.CandlestickRenderer#setCandleWidth(double)
  179. */
  180. public double getCandleWidth() {
  181. return this.candleWidth;
  182. }
  183. /**
  184. * Sets the candle width.
  185. * <P>
  186. * If you set the width to a negative value, the renderer will calculate
  187. * the candle width automatically based on the space available on the chart.
  188. *
  189. * @param width The width.
  190. * @see org.jfree.chart.renderer.xy.CandlestickRenderer#setAutoWidthMethod(int)
  191. * @see org.jfree.chart.renderer.xy.CandlestickRenderer#setAutoWidthGap(double)
  192. * @see org.jfree.chart.renderer.xy.CandlestickRenderer#setAutoWidthFactor(double)
  193. * @see org.jfree.chart.renderer.xy.CandlestickRenderer#setMaxCandleWidthInMilliseconds(double)
  194. */
  195. public void setCandleWidth(double width) {
  196. if (width != this.candleWidth) {
  197. this.candleWidth = width;
  198. notifyListeners(new RendererChangeEvent(this));
  199. }
  200. }
  201. /**
  202. * Returns the maximum width (in milliseconds) of each candle.
  203. *
  204. * @return The maximum candle width in milliseconds.
  205. */
  206. public double getMaxCandleWidthInMilliseconds() {
  207. return this.maxCandleWidthInMilliseconds;
  208. }
  209. /**
  210. * Sets the maximum candle width (in milliseconds).
  211. *
  212. * @param millis The maximum width.
  213. * @see org.jfree.chart.renderer.xy.CandlestickRenderer#setCandleWidth(double)
  214. * @see org.jfree.chart.renderer.xy.CandlestickRenderer#setAutoWidthMethod(int)
  215. * @see org.jfree.chart.renderer.xy.CandlestickRenderer#setAutoWidthGap(double)
  216. * @see org.jfree.chart.renderer.xy.CandlestickRenderer#setAutoWidthFactor(double)
  217. */
  218. public void setMaxCandleWidthInMilliseconds(double millis) {
  219. this.maxCandleWidthInMilliseconds = millis;
  220. notifyListeners(new RendererChangeEvent(this));
  221. }
  222. /**
  223. * Returns the method of automatically calculating the candle width.
  224. *
  225. * @return The method of automatically calculating the candle width.
  226. */
  227. public int getAutoWidthMethod() {
  228. return this.autoWidthMethod;
  229. }
  230. /**
  231. * Sets the method of automatically calculating the candle width.
  232. * <p>
  233. * <code>WIDTHMETHOD_AVERAGE</code>: Divides the entire display (ignoring scale factor) by the
  234. * number of items, and uses this as the available width.<br>
  235. * <code>WIDTHMETHOD_SMALLEST</code>: Checks the interval between each item, and uses the
  236. * smallest as the available width.<br>
  237. * <code>WIDTHMETHOD_INTERVALDATA</code>: Assumes that the dataset supports the
  238. * IntervalXYDataset interface, and uses the startXValue - endXValue as the available width.
  239. * <br>
  240. *
  241. * @param autoWidthMethod The method of automatically calculating the candle width.
  242. *
  243. * @see org.jfree.chart.renderer.xy.CandlestickRenderer#WIDTHMETHOD_AVERAGE
  244. * @see org.jfree.chart.renderer.xy.CandlestickRenderer#WIDTHMETHOD_SMALLEST
  245. * @see org.jfree.chart.renderer.xy.CandlestickRenderer#WIDTHMETHOD_INTERVALDATA
  246. * @see org.jfree.chart.renderer.xy.CandlestickRenderer#setCandleWidth(double)
  247. * @see org.jfree.chart.renderer.xy.CandlestickRenderer#setAutoWidthGap(double)
  248. * @see org.jfree.chart.renderer.xy.CandlestickRenderer#setAutoWidthFactor(double)
  249. * @see org.jfree.chart.renderer.xy.CandlestickRenderer#setMaxCandleWidthInMilliseconds(double)
  250. */
  251. public void setAutoWidthMethod(int autoWidthMethod) {
  252. if (this.autoWidthMethod != autoWidthMethod) {
  253. this.autoWidthMethod = autoWidthMethod;
  254. notifyListeners(new RendererChangeEvent(this));
  255. }
  256. }
  257. /**
  258. * Returns the factor by which the available space automatically calculated for the candles
  259. * will be multiplied to determine the actual width to use.
  260. *
  261. * @return The width factor (generally between 0.0 and 1.0).
  262. */
  263. public double getAutoWidthFactor() {
  264. return this.autoWidthFactor;
  265. }
  266. /**
  267. * Sets the factor by which the available space automatically calculated for the candles will
  268. * be multiplied to determine the actual width to use.
  269. *
  270. * @param autoWidthFactor The width factor (generally between 0.0 and 1.0).
  271. * @see org.jfree.chart.renderer.xy.CandlestickRenderer#setCandleWidth(double)
  272. * @see org.jfree.chart.renderer.xy.CandlestickRenderer#setAutoWidthMethod(int)
  273. * @see org.jfree.chart.renderer.xy.CandlestickRenderer#setAutoWidthGap(double)
  274. * @see org.jfree.chart.renderer.xy.CandlestickRenderer#setMaxCandleWidthInMilliseconds(double)
  275. */
  276. public void setAutoWidthFactor(double autoWidthFactor) {
  277. if (this.autoWidthFactor != autoWidthFactor) {
  278. this.autoWidthFactor = autoWidthFactor;
  279. notifyListeners(new RendererChangeEvent(this));
  280. }
  281. }
  282. /**
  283. * Returns the amount of space to leave on the left and right of each candle when
  284. * automatically calculating widths.
  285. *
  286. * @return The gap.
  287. */
  288. public double getAutoWidthGap() {
  289. return this.autoWidthGap;
  290. }
  291. /**
  292. * Sets the amount of space to leave on the left and right of each candle when automatically
  293. * calculating widths.
  294. *
  295. * @param autoWidthGap The gap.
  296. * @see org.jfree.chart.renderer.xy.CandlestickRenderer#setCandleWidth(double)
  297. * @see org.jfree.chart.renderer.xy.CandlestickRenderer#setAutoWidthMethod(int)
  298. * @see org.jfree.chart.renderer.xy.CandlestickRenderer#setAutoWidthFactor(double)
  299. * @see org.jfree.chart.renderer.xy.CandlestickRenderer#setMaxCandleWidthInMilliseconds(double)
  300. */
  301. public void setAutoWidthGap(double autoWidthGap) {
  302. if (this.autoWidthGap != autoWidthGap) {
  303. this.autoWidthGap = autoWidthGap;
  304. notifyListeners(new RendererChangeEvent(this));
  305. }
  306. }
  307. /**
  308. * Returns the paint used to fill candles when the price moves up from open
  309. * to close.
  310. *
  311. * @return The paint.
  312. */
  313. public Paint getUpPaint() {
  314. return this.upPaint;
  315. }
  316. /**
  317. * Sets the paint used to fill candles when the price moves up from open
  318. * to close.
  319. * <P>
  320. * Registered property change listeners are notified that the
  321. * "CandleStickRenderer.upPaint" property has changed.
  322. *
  323. * @param paint The paint.
  324. */
  325. public void setUpPaint(Paint paint) {
  326. this.upPaint = paint;
  327. notifyListeners(new RendererChangeEvent(this));
  328. }
  329. /**
  330. * Returns the paint used to fill candles when the price moves down from
  331. * open to close.
  332. *
  333. * @return The paint.
  334. */
  335. public Paint getDownPaint() {
  336. return this.downPaint;
  337. }
  338. /**
  339. * Sets the paint used to fill candles when the price moves down from open
  340. * to close.
  341. * <P>
  342. * Registered property change listeners are notified that the
  343. * "CandleStickRenderer.downPaint" property has changed.
  344. *
  345. * @param paint The paint.
  346. */
  347. public void setDownPaint(Paint paint) {
  348. this.downPaint = paint;
  349. notifyListeners(new RendererChangeEvent(this));
  350. }
  351. /**
  352. * Returns a flag indicating whether or not volume bars are drawn on the
  353. * chart.
  354. *
  355. * @return <code>true</code> if volume bars are drawn on the chart.
  356. */
  357. public boolean drawVolume() {
  358. return this.drawVolume;
  359. }
  360. /**
  361. * Sets a flag that controls whether or not volume bars are drawn in the
  362. * background.
  363. *
  364. * @param flag The flag.
  365. */
  366. public void setDrawVolume(boolean flag) {
  367. if (this.drawVolume != flag) {
  368. this.drawVolume = flag;
  369. notifyListeners(new RendererChangeEvent(this));
  370. }
  371. }
  372. /**
  373. * Initialises the renderer then returns the number of 'passes' through the data that the
  374. * renderer will require (usually just one). This method will be called before the first
  375. * item is rendered, giving the renderer an opportunity to initialise any
  376. * state information it wants to maintain. The renderer can do nothing if it chooses.
  377. *
  378. * @param g2 the graphics device.
  379. * @param dataArea the area inside the axes.
  380. * @param plot the plot.
  381. * @param dataset the data.
  382. * @param info an optional info collection object to return data back to the caller.
  383. *
  384. * @return The number of passes the renderer requires.
  385. */
  386. public XYItemRendererState initialise(Graphics2D g2,
  387. Rectangle2D dataArea,
  388. XYPlot plot,
  389. XYDataset dataset,
  390. PlotRenderingInfo info) {
  391. // calculate the maximum allowed candle width from the axis...
  392. ValueAxis axis = plot.getDomainAxis();
  393. double x1 = axis.getLowerBound();
  394. double x2 = x1 + this.maxCandleWidthInMilliseconds;
  395. RectangleEdge edge = plot.getDomainAxisEdge();
  396. double xx1 = axis.valueToJava2D(x1, dataArea, edge);
  397. double xx2 = axis.valueToJava2D(x2, dataArea, edge);
  398. this.maxCandleWidth = Math.abs(xx2 - xx1); // Absolute value, since the relative x
  399. // positions are reversed for horizontal orientation
  400. // calculate the highest volume in the dataset...
  401. if (this.drawVolume) {
  402. OHLCDataset highLowDataset = (OHLCDataset) dataset;
  403. this.maxVolume = 0.0;
  404. for (int series = 0; series < highLowDataset.getSeriesCount(); series++) {
  405. for (int item = 0; item < highLowDataset.getItemCount(series); item++) {
  406. double volume = highLowDataset.getVolumeValue(series, item);
  407. if (volume > this.maxVolume) {
  408. this.maxVolume = volume;
  409. }
  410. }
  411. }
  412. }
  413. return new XYItemRendererState(info);
  414. }
  415. /**
  416. * Draws the visual representation of a single data item.
  417. *
  418. * @param g2 the graphics device.
  419. * @param state the renderer state.
  420. * @param dataArea the area within which the plot is being drawn.
  421. * @param info collects info about the drawing.
  422. * @param plot the plot (can be used to obtain standard color information etc).
  423. * @param domainAxis the domain axis.
  424. * @param rangeAxis the range axis.
  425. * @param dataset the dataset.
  426. * @param series the series index (zero-based).
  427. * @param item the item index (zero-based).
  428. * @param crosshairState crosshair information for the plot (<code>null</code> permitted).
  429. * @param pass the pass index.
  430. */
  431. public void drawItem(Graphics2D g2,
  432. XYItemRendererState state,
  433. Rectangle2D dataArea,
  434. PlotRenderingInfo info,
  435. XYPlot plot,
  436. ValueAxis domainAxis,
  437. ValueAxis rangeAxis,
  438. XYDataset dataset,
  439. int series,
  440. int item,
  441. CrosshairState crosshairState,
  442. int pass) {
  443. boolean horiz;
  444. PlotOrientation orientation = plot.getOrientation();
  445. if (orientation == PlotOrientation.HORIZONTAL) {
  446. horiz = true;
  447. }
  448. else if (orientation == PlotOrientation.VERTICAL) {
  449. horiz = false;
  450. }
  451. else {
  452. return;
  453. }
  454. // setup for collecting optional entity info...
  455. EntityCollection entities = null;
  456. if (info != null) {
  457. entities = info.getOwner().getEntityCollection();
  458. }
  459. OHLCDataset highLowData = (OHLCDataset) dataset;
  460. Number x = highLowData.getX(series, item);
  461. Number yHigh = highLowData.getHigh(series, item);
  462. Number yLow = highLowData.getLow(series, item);
  463. Number yOpen = highLowData.getOpen(series, item);
  464. Number yClose = highLowData.getClose(series, item);
  465. RectangleEdge domainEdge = plot.getDomainAxisEdge();
  466. double xx = domainAxis.valueToJava2D(x.doubleValue(), dataArea, domainEdge);
  467. RectangleEdge edge = plot.getRangeAxisEdge();
  468. double yyHigh = rangeAxis.valueToJava2D(yHigh.doubleValue(), dataArea, edge);
  469. double yyLow = rangeAxis.valueToJava2D(yLow.doubleValue(), dataArea, edge);
  470. double yyOpen = rangeAxis.valueToJava2D(yOpen.doubleValue(), dataArea, edge);
  471. double yyClose = rangeAxis.valueToJava2D(yClose.doubleValue(), dataArea, edge);
  472. double volumeWidth;
  473. double stickWidth;
  474. if (this.candleWidth > 0) {
  475. // These are deliberately not bounded to minimums/maxCandleWidth to retain old
  476. // behaviour.
  477. volumeWidth = this.candleWidth;
  478. stickWidth = this.candleWidth;
  479. }
  480. else {
  481. double xxWidth = 0;
  482. int itemCount;
  483. switch (this.autoWidthMethod) {
  484. case WIDTHMETHOD_AVERAGE:
  485. itemCount = highLowData.getItemCount(series);
  486. if (horiz) {
  487. xxWidth = dataArea.getHeight() / itemCount;
  488. }
  489. else {
  490. xxWidth = dataArea.getWidth() / itemCount;
  491. }
  492. break;
  493. case WIDTHMETHOD_SMALLEST:
  494. // Note: It would be nice to pre-calculate this per series
  495. itemCount = highLowData.getItemCount(series);
  496. double lastPos = -1;
  497. xxWidth = dataArea.getWidth();
  498. for (int i = 0; i < itemCount; i++) {
  499. double pos = domainAxis.valueToJava2D(
  500. highLowData.getXValue(series, i), dataArea, domainEdge
  501. );
  502. if (lastPos != -1) {
  503. xxWidth = Math.min(xxWidth, Math.abs(pos - lastPos));
  504. }
  505. lastPos = pos;
  506. }
  507. break;
  508. case WIDTHMETHOD_INTERVALDATA:
  509. IntervalXYDataset intervalXYData = (IntervalXYDataset) dataset;
  510. double startPos = domainAxis.valueToJava2D(
  511. intervalXYData.getStartXValue(series, item), dataArea,
  512. plot.getDomainAxisEdge()
  513. );
  514. double endPos = domainAxis.valueToJava2D(
  515. intervalXYData.getEndXValue(series, item), dataArea,
  516. plot.getDomainAxisEdge()
  517. );
  518. xxWidth = Math.abs(endPos - startPos);
  519. break;
  520. }
  521. xxWidth -= 2 * this.autoWidthGap;
  522. xxWidth *= this.autoWidthFactor;
  523. xxWidth = Math.min(xxWidth, this.maxCandleWidth);
  524. volumeWidth = Math.max(Math.min(1, this.maxCandleWidth), xxWidth);
  525. stickWidth = Math.max(Math.min(3, this.maxCandleWidth), xxWidth);
  526. }
  527. Paint p = getItemPaint(series, item);
  528. Stroke s = getItemStroke(series, item);
  529. g2.setStroke(s);
  530. if (this.drawVolume) {
  531. int volume = (int) highLowData.getVolumeValue(series, item);
  532. double volumeHeight = volume / this.maxVolume;
  533. double min, max;
  534. if (horiz) {
  535. min = dataArea.getMinX();
  536. max = dataArea.getMaxX();
  537. }
  538. else {
  539. min = dataArea.getMinY();
  540. max = dataArea.getMaxY();
  541. }
  542. double zzVolume = volumeHeight * (max - min);
  543. g2.setPaint(Color.gray);
  544. Composite originalComposite = g2.getComposite();
  545. g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.3f));
  546. if (horiz) {
  547. g2.fill(new Rectangle2D.Double(min,
  548. xx - volumeWidth / 2,
  549. zzVolume, volumeWidth));
  550. }
  551. else {
  552. g2.fill(new Rectangle2D.Double(xx - volumeWidth / 2,
  553. max - zzVolume, volumeWidth, zzVolume));
  554. }
  555. g2.setComposite(originalComposite);
  556. }
  557. g2.setPaint(p);
  558. double yyMaxOpenClose = Math.max(yyOpen, yyClose);
  559. double yyMinOpenClose = Math.min(yyOpen, yyClose);
  560. double maxOpenClose = Math.max(yOpen.doubleValue(), yClose.doubleValue());
  561. double minOpenClose = Math.min(yOpen.doubleValue(), yClose.doubleValue());
  562. // draw the upper shadow
  563. if (yHigh.doubleValue() > maxOpenClose) {
  564. if (horiz) {
  565. g2.draw(new Line2D.Double(yyHigh, xx, yyMaxOpenClose, xx));
  566. }
  567. else {
  568. g2.draw(new Line2D.Double(xx, yyHigh, xx, yyMaxOpenClose));
  569. }
  570. }
  571. // draw the lower shadow
  572. if (yLow.doubleValue() < minOpenClose) {
  573. if (horiz) {
  574. g2.draw(new Line2D.Double(yyLow, xx, yyMinOpenClose, xx));
  575. }
  576. else {
  577. g2.draw(new Line2D.Double(xx, yyLow, xx, yyMinOpenClose));
  578. }
  579. }
  580. // draw the body
  581. Shape body = null;
  582. if (horiz) {
  583. body = new Rectangle2D.Double(yyMinOpenClose, xx - stickWidth / 2,
  584. yyMaxOpenClose - yyMinOpenClose, stickWidth);
  585. }
  586. else {
  587. body = new Rectangle2D.Double(xx - stickWidth / 2, yyMinOpenClose,
  588. stickWidth, yyMaxOpenClose - yyMinOpenClose);
  589. }
  590. if (yClose.doubleValue() > yOpen.doubleValue()) {
  591. if (this.upPaint != null) {
  592. g2.setPaint(this.upPaint);
  593. g2.fill(body);
  594. }
  595. }
  596. else {
  597. if (this.downPaint != null) {
  598. g2.setPaint(this.downPaint);
  599. }
  600. g2.fill(body);
  601. }
  602. g2.setPaint(p);
  603. g2.draw(body);
  604. // add an entity for the item...
  605. if (entities != null) {
  606. String tip = null;
  607. XYToolTipGenerator generator = getToolTipGenerator(series, item);
  608. if (generator != null) {
  609. tip = generator.generateToolTip(dataset, series, item);
  610. }
  611. String url = null;
  612. if (getURLGenerator() != null) {
  613. url = getURLGenerator().generateURL(dataset, series, item);
  614. }
  615. XYItemEntity entity = new XYItemEntity(body, dataset, series, item, tip, url);
  616. entities.add(entity);
  617. }
  618. }
  619. /**
  620. * Tests this renderer for equality with another object.
  621. *
  622. * @param obj the object.
  623. *
  624. * @return <code>true</code> or <code>false</code>.
  625. */
  626. public boolean equals(Object obj) {
  627. if (obj == null) {
  628. return false;
  629. }
  630. if (obj == this) {
  631. return true;
  632. }
  633. if (obj instanceof CandlestickRenderer) {
  634. CandlestickRenderer renderer = (CandlestickRenderer) obj;
  635. boolean result = super.equals(obj);
  636. result = result && (this.candleWidth == renderer.getCandleWidth());
  637. result = result && (this.upPaint.equals(renderer.getUpPaint()));
  638. result = result && (this.downPaint.equals(renderer.getDownPaint()));
  639. result = result && (this.drawVolume == renderer.drawVolume);
  640. return result;
  641. }
  642. return false;
  643. }
  644. /**
  645. * Returns a clone of the renderer.
  646. *
  647. * @return A clone.
  648. *
  649. * @throws CloneNotSupportedException if the renderer cannot be cloned.
  650. */
  651. public Object clone() throws CloneNotSupportedException {
  652. return super.clone();
  653. }
  654. /**
  655. * Provides serialization support.
  656. *
  657. * @param stream the output stream.
  658. *
  659. * @throws IOException if there is an I/O error.
  660. */
  661. private void writeObject(ObjectOutputStream stream) throws IOException {
  662. stream.defaultWriteObject();
  663. SerialUtilities.writePaint(this.upPaint, stream);
  664. SerialUtilities.writePaint(this.downPaint, stream);
  665. }
  666. /**
  667. * Provides serialization support.
  668. *
  669. * @param stream the input stream.
  670. *
  671. * @throws IOException if there is an I/O error.
  672. * @throws ClassNotFoundException if there is a classpath problem.
  673. */
  674. private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
  675. stream.defaultReadObject();
  676. this.upPaint = SerialUtilities.readPaint(stream);
  677. this.downPaint = SerialUtilities.readPaint(stream);
  678. }
  679. }