001: /* ===========================================================
002: * JFreeChart : a free chart library for the Java(tm) platform
003: * ===========================================================
004: *
005: * (C) Copyright 2000-2007, by Object Refinery Limited and Contributors.
006: *
007: * Project Info: http://www.jfree.org/jfreechart/index.html
008: *
009: * This library is free software; you can redistribute it and/or modify it
010: * under the terms of the GNU Lesser General Public License as published by
011: * the Free Software Foundation; either version 2.1 of the License, or
012: * (at your option) any later version.
013: *
014: * This library is distributed in the hope that it will be useful, but
015: * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
016: * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
017: * License for more details.
018: *
019: * You should have received a copy of the GNU Lesser General Public
020: * License along with this library; if not, write to the Free Software
021: * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
022: * USA.
023: *
024: * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
025: * in the United States and other countries.]
026: *
027: * ------------------------
028: * CandlestickRenderer.java
029: * ------------------------
030: * (C) Copyright 2001-2007, by Object Refinery Limited.
031: *
032: * Original Authors: David Gilbert (for Object Refinery Limited);
033: * Sylvain Vieujot;
034: * Contributor(s): Richard Atkinson;
035: * Christian W. Zuckschwerdt;
036: * Jerome Fisher;
037: *
038: * $Id: CandlestickRenderer.java,v 1.7.2.6 2007/06/11 11:07:18 mungady Exp $
039: *
040: * Changes
041: * -------
042: * 13-Dec-2001 : Version 1. Based on code in the (now redundant)
043: * CandlestickPlot class, written by Sylvain Vieujot (DG);
044: * 23-Jan-2002 : Added DrawInfo parameter to drawItem() method (DG);
045: * 28-Mar-2002 : Added a property change listener mechanism so that renderers
046: * no longer need to be immutable. Added properties for up and
047: * down colors (DG);
048: * 04-Apr-2002 : Updated with new automatic width calculation and optional
049: * volume display, contributed by Sylvain Vieujot (DG);
050: * 09-Apr-2002 : Removed translatedRangeZero from the drawItem() method, and
051: * changed the return type of the drawItem method to void,
052: * reflecting a change in the XYItemRenderer interface. Added
053: * tooltip code to drawItem() method (DG);
054: * 25-Jun-2002 : Removed redundant code (DG);
055: * 05-Aug-2002 : Small modification to drawItem method to support URLs for HTML
056: * image maps (RA);
057: * 19-Sep-2002 : Fixed errors reported by Checkstyle (DG);
058: * 25-Mar-2003 : Implemented Serializable (DG);
059: * 01-May-2003 : Modified drawItem() method signature (DG);
060: * 30-Jun-2003 : Added support for PlotOrientation (for completeness, this
061: * renderer is unlikely to be used with a HORIZONTAL
062: * orientation) (DG);
063: * 30-Jul-2003 : Modified entity constructor (CZ);
064: * 20-Aug-2003 : Implemented Cloneable and PublicCloneable (DG);
065: * 29-Aug-2003 : Moved maxVolume calculation to initialise method (see bug
066: * report 796619) (DG);
067: * 02-Sep-2003 : Added maxCandleWidthInMilliseconds as workaround for bug
068: * 796621 (DG);
069: * 08-Sep-2003 : Changed ValueAxis API (DG);
070: * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG);
071: * 13-Oct-2003 : Applied patch from Jerome Fisher to improve auto width
072: * calculations (DG);
073: * 23-Dec-2003 : Fixed bug where up and down paint are used incorrectly (DG);
074: * 25-Feb-2004 : Replaced CrosshairInfo with CrosshairState (DG);
075: * 15-Jul-2004 : Switched getX() with getXValue() and getY() with
076: * getYValue() (DG);
077: * ------------- JFREECHART 1.0.x ---------------------------------------------
078: * 06-Jul-2006 : Swapped calls to getX() --> getXValue(), and the same for the
079: * other data values (DG);
080: * 17-Aug-2006 : Corrections to the equals() method (DG);
081: * 05-Mar-2007 : Added flag to allow optional use of outline paint (DG);
082: *
083: */
084:
085: package org.jfree.chart.renderer.xy;
086:
087: import java.awt.AlphaComposite;
088: import java.awt.Color;
089: import java.awt.Composite;
090: import java.awt.Graphics2D;
091: import java.awt.Paint;
092: import java.awt.Shape;
093: import java.awt.Stroke;
094: import java.awt.geom.Line2D;
095: import java.awt.geom.Rectangle2D;
096: import java.io.IOException;
097: import java.io.ObjectInputStream;
098: import java.io.ObjectOutputStream;
099: import java.io.Serializable;
100:
101: import org.jfree.chart.axis.ValueAxis;
102: import org.jfree.chart.entity.EntityCollection;
103: import org.jfree.chart.entity.XYItemEntity;
104: import org.jfree.chart.event.RendererChangeEvent;
105: import org.jfree.chart.labels.HighLowItemLabelGenerator;
106: import org.jfree.chart.labels.XYToolTipGenerator;
107: import org.jfree.chart.plot.CrosshairState;
108: import org.jfree.chart.plot.PlotOrientation;
109: import org.jfree.chart.plot.PlotRenderingInfo;
110: import org.jfree.chart.plot.XYPlot;
111: import org.jfree.data.xy.IntervalXYDataset;
112: import org.jfree.data.xy.OHLCDataset;
113: import org.jfree.data.xy.XYDataset;
114: import org.jfree.io.SerialUtilities;
115: import org.jfree.ui.RectangleEdge;
116: import org.jfree.util.PaintUtilities;
117: import org.jfree.util.PublicCloneable;
118:
119: /**
120: * A renderer that draws candlesticks on an {@link XYPlot} (requires a
121: * {@link OHLCDataset}).
122: * <P>
123: * This renderer does not include code to calculate the crosshair point for the
124: * plot.
125: */
126: public class CandlestickRenderer extends AbstractXYItemRenderer
127: implements XYItemRenderer, Cloneable, PublicCloneable,
128: Serializable {
129:
130: /** For serialization. */
131: private static final long serialVersionUID = 50390395841817121L;
132:
133: /** The average width method. */
134: public static final int WIDTHMETHOD_AVERAGE = 0;
135:
136: /** The smallest width method. */
137: public static final int WIDTHMETHOD_SMALLEST = 1;
138:
139: /** The interval data method. */
140: public static final int WIDTHMETHOD_INTERVALDATA = 2;
141:
142: /** The method of automatically calculating the candle width. */
143: private int autoWidthMethod = WIDTHMETHOD_AVERAGE;
144:
145: /**
146: * The number (generally between 0.0 and 1.0) by which the available space
147: * automatically calculated for the candles will be multiplied to determine
148: * the actual width to use.
149: */
150: private double autoWidthFactor = 4.5 / 7;
151:
152: /** The minimum gap between one candle and the next */
153: private double autoWidthGap = 0.0;
154:
155: /** The candle width. */
156: private double candleWidth;
157:
158: /** The maximum candlewidth in milliseconds. */
159: private double maxCandleWidthInMilliseconds = 1000.0 * 60.0 * 60.0 * 20.0;
160:
161: /** Temporary storage for the maximum candle width. */
162: private double maxCandleWidth;
163:
164: /**
165: * The paint used to fill the candle when the price moved up from open to
166: * close.
167: */
168: private transient Paint upPaint;
169:
170: /**
171: * The paint used to fill the candle when the price moved down from open
172: * to close.
173: */
174: private transient Paint downPaint;
175:
176: /** A flag controlling whether or not volume bars are drawn on the chart. */
177: private boolean drawVolume;
178:
179: /** Temporary storage for the maximum volume. */
180: private transient double maxVolume;
181:
182: /**
183: * A flag that controls whether or not the renderer's outline paint is
184: * used to draw the outline of the candlestick. The default value is
185: * <code>false</code> to avoid a change of behaviour for existing code.
186: *
187: * @since 1.0.5
188: */
189: private boolean useOutlinePaint;
190:
191: /**
192: * Creates a new renderer for candlestick charts.
193: */
194: public CandlestickRenderer() {
195: this (-1.0);
196: }
197:
198: /**
199: * Creates a new renderer for candlestick charts.
200: * <P>
201: * Use -1 for the candle width if you prefer the width to be calculated
202: * automatically.
203: *
204: * @param candleWidth The candle width.
205: */
206: public CandlestickRenderer(double candleWidth) {
207: this (candleWidth, true, new HighLowItemLabelGenerator());
208: }
209:
210: /**
211: * Creates a new renderer for candlestick charts.
212: * <P>
213: * Use -1 for the candle width if you prefer the width to be calculated
214: * automatically.
215: *
216: * @param candleWidth the candle width.
217: * @param drawVolume a flag indicating whether or not volume bars should
218: * be drawn.
219: * @param toolTipGenerator the tool tip generator. <code>null</code> is
220: * none.
221: */
222: public CandlestickRenderer(double candleWidth, boolean drawVolume,
223: XYToolTipGenerator toolTipGenerator) {
224: super ();
225: setBaseToolTipGenerator(toolTipGenerator);
226: this .candleWidth = candleWidth;
227: this .drawVolume = drawVolume;
228: this .upPaint = Color.green;
229: this .downPaint = Color.red;
230: this .useOutlinePaint = false; // false preserves the old behaviour
231: // prior to introducing this flag
232: }
233:
234: /**
235: * Returns the width of each candle.
236: *
237: * @return The candle width.
238: *
239: * @see #setCandleWidth(double)
240: */
241: public double getCandleWidth() {
242: return this .candleWidth;
243: }
244:
245: /**
246: * Sets the candle width.
247: * <P>
248: * If you set the width to a negative value, the renderer will calculate
249: * the candle width automatically based on the space available on the chart.
250: *
251: * @param width The width.
252: * @see #setAutoWidthMethod(int)
253: * @see #setAutoWidthGap(double)
254: * @see #setAutoWidthFactor(double)
255: * @see #setMaxCandleWidthInMilliseconds(double)
256: */
257: public void setCandleWidth(double width) {
258: if (width != this .candleWidth) {
259: this .candleWidth = width;
260: notifyListeners(new RendererChangeEvent(this ));
261: }
262: }
263:
264: /**
265: * Returns the maximum width (in milliseconds) of each candle.
266: *
267: * @return The maximum candle width in milliseconds.
268: *
269: * @see #setMaxCandleWidthInMilliseconds(double)
270: */
271: public double getMaxCandleWidthInMilliseconds() {
272: return this .maxCandleWidthInMilliseconds;
273: }
274:
275: /**
276: * Sets the maximum candle width (in milliseconds).
277: *
278: * @param millis The maximum width.
279: *
280: * @see #getMaxCandleWidthInMilliseconds()
281: * @see #setCandleWidth(double)
282: * @see #setAutoWidthMethod(int)
283: * @see #setAutoWidthGap(double)
284: * @see #setAutoWidthFactor(double)
285: */
286: public void setMaxCandleWidthInMilliseconds(double millis) {
287: this .maxCandleWidthInMilliseconds = millis;
288: notifyListeners(new RendererChangeEvent(this ));
289: }
290:
291: /**
292: * Returns the method of automatically calculating the candle width.
293: *
294: * @return The method of automatically calculating the candle width.
295: *
296: * @see #setAutoWidthMethod(int)
297: */
298: public int getAutoWidthMethod() {
299: return this .autoWidthMethod;
300: }
301:
302: /**
303: * Sets the method of automatically calculating the candle width.
304: * <p>
305: * <code>WIDTHMETHOD_AVERAGE</code>: Divides the entire display (ignoring
306: * scale factor) by the number of items, and uses this as the available
307: * width.<br>
308: * <code>WIDTHMETHOD_SMALLEST</code>: Checks the interval between each
309: * item, and uses the smallest as the available width.<br>
310: * <code>WIDTHMETHOD_INTERVALDATA</code>: Assumes that the dataset supports
311: * the IntervalXYDataset interface, and uses the startXValue - endXValue as
312: * the available width.
313: * <br>
314: *
315: * @param autoWidthMethod The method of automatically calculating the
316: * candle width.
317: *
318: * @see #WIDTHMETHOD_AVERAGE
319: * @see #WIDTHMETHOD_SMALLEST
320: * @see #WIDTHMETHOD_INTERVALDATA
321: * @see #getAutoWidthMethod()
322: * @see #setCandleWidth(double)
323: * @see #setAutoWidthGap(double)
324: * @see #setAutoWidthFactor(double)
325: * @see #setMaxCandleWidthInMilliseconds(double)
326: */
327: public void setAutoWidthMethod(int autoWidthMethod) {
328: if (this .autoWidthMethod != autoWidthMethod) {
329: this .autoWidthMethod = autoWidthMethod;
330: notifyListeners(new RendererChangeEvent(this ));
331: }
332: }
333:
334: /**
335: * Returns the factor by which the available space automatically
336: * calculated for the candles will be multiplied to determine the actual
337: * width to use.
338: *
339: * @return The width factor (generally between 0.0 and 1.0).
340: *
341: * @see #setAutoWidthFactor(double)
342: */
343: public double getAutoWidthFactor() {
344: return this .autoWidthFactor;
345: }
346:
347: /**
348: * Sets the factor by which the available space automatically calculated
349: * for the candles will be multiplied to determine the actual width to use.
350: *
351: * @param autoWidthFactor The width factor (generally between 0.0 and 1.0).
352: *
353: * @see #getAutoWidthFactor()
354: * @see #setCandleWidth(double)
355: * @see #setAutoWidthMethod(int)
356: * @see #setAutoWidthGap(double)
357: * @see #setMaxCandleWidthInMilliseconds(double)
358: */
359: public void setAutoWidthFactor(double autoWidthFactor) {
360: if (this .autoWidthFactor != autoWidthFactor) {
361: this .autoWidthFactor = autoWidthFactor;
362: notifyListeners(new RendererChangeEvent(this ));
363: }
364: }
365:
366: /**
367: * Returns the amount of space to leave on the left and right of each
368: * candle when automatically calculating widths.
369: *
370: * @return The gap.
371: *
372: * @see #setAutoWidthGap(double)
373: */
374: public double getAutoWidthGap() {
375: return this .autoWidthGap;
376: }
377:
378: /**
379: * Sets the amount of space to leave on the left and right of each candle
380: * when automatically calculating widths.
381: *
382: * @param autoWidthGap The gap.
383: *
384: * @see #getAutoWidthGap()
385: * @see #setCandleWidth(double)
386: * @see #setAutoWidthMethod(int)
387: * @see #setAutoWidthFactor(double)
388: * @see #setMaxCandleWidthInMilliseconds(double)
389: */
390: public void setAutoWidthGap(double autoWidthGap) {
391: if (this .autoWidthGap != autoWidthGap) {
392: this .autoWidthGap = autoWidthGap;
393: notifyListeners(new RendererChangeEvent(this ));
394: }
395: }
396:
397: /**
398: * Returns the paint used to fill candles when the price moves up from open
399: * to close.
400: *
401: * @return The paint (possibly <code>null</code>).
402: *
403: * @see #setUpPaint(Paint)
404: */
405: public Paint getUpPaint() {
406: return this .upPaint;
407: }
408:
409: /**
410: * Sets the paint used to fill candles when the price moves up from open
411: * to close and sends a {@link RendererChangeEvent} to all registered
412: * listeners.
413: *
414: * @param paint the paint (<code>null</code> permitted).
415: *
416: * @see #getUpPaint()
417: */
418: public void setUpPaint(Paint paint) {
419: this .upPaint = paint;
420: notifyListeners(new RendererChangeEvent(this ));
421: }
422:
423: /**
424: * Returns the paint used to fill candles when the price moves down from
425: * open to close.
426: *
427: * @return The paint (possibly <code>null</code>).
428: *
429: * @see #setDownPaint(Paint)
430: */
431: public Paint getDownPaint() {
432: return this .downPaint;
433: }
434:
435: /**
436: * Sets the paint used to fill candles when the price moves down from open
437: * to close and sends a {@link RendererChangeEvent} to all registered
438: * listeners.
439: *
440: * @param paint The paint (<code>null</code> permitted).
441: */
442: public void setDownPaint(Paint paint) {
443: this .downPaint = paint;
444: notifyListeners(new RendererChangeEvent(this ));
445: }
446:
447: /**
448: * Returns a flag indicating whether or not volume bars are drawn on the
449: * chart.
450: *
451: * @return A boolean.
452: *
453: * @since 1.0.5
454: *
455: * @see #setDrawVolume(boolean)
456: */
457: public boolean getDrawVolume() {
458: return this .drawVolume;
459: }
460:
461: /**
462: * Sets a flag that controls whether or not volume bars are drawn in the
463: * background and sends a {@link RendererChangeEvent} to all registered
464: * listeners.
465: *
466: * @param flag the flag.
467: *
468: * @see #getDrawVolume()
469: */
470: public void setDrawVolume(boolean flag) {
471: if (this .drawVolume != flag) {
472: this .drawVolume = flag;
473: notifyListeners(new RendererChangeEvent(this ));
474: }
475: }
476:
477: /**
478: * Returns the flag that controls whether or not the renderer's outline
479: * paint is used to draw the candlestick outline. The default value is
480: * <code>false</code>.
481: *
482: * @return A boolean.
483: *
484: * @since 1.0.5
485: *
486: * @see #setUseOutlinePaint(boolean)
487: */
488: public boolean getUseOutlinePaint() {
489: return this .useOutlinePaint;
490: }
491:
492: /**
493: * Sets the flag that controls whether or not the renderer's outline
494: * paint is used to draw the candlestick outline, and sends a
495: * {@link RendererChangeEvent} to all registered listeners.
496: *
497: * @param use the new flag value.
498: *
499: * @since 1.0.5
500: *
501: * @see #getUseOutlinePaint()
502: */
503: public void setUseOutlinePaint(boolean use) {
504: if (this .useOutlinePaint != use) {
505: this .useOutlinePaint = use;
506: fireChangeEvent();
507: }
508: }
509:
510: /**
511: * Initialises the renderer then returns the number of 'passes' through the
512: * data that the renderer will require (usually just one). This method
513: * will be called before the first item is rendered, giving the renderer
514: * an opportunity to initialise any state information it wants to maintain.
515: * The renderer can do nothing if it chooses.
516: *
517: * @param g2 the graphics device.
518: * @param dataArea the area inside the axes.
519: * @param plot the plot.
520: * @param dataset the data.
521: * @param info an optional info collection object to return data back to
522: * the caller.
523: *
524: * @return The number of passes the renderer requires.
525: */
526: public XYItemRendererState initialise(Graphics2D g2,
527: Rectangle2D dataArea, XYPlot plot, XYDataset dataset,
528: PlotRenderingInfo info) {
529:
530: // calculate the maximum allowed candle width from the axis...
531: ValueAxis axis = plot.getDomainAxis();
532: double x1 = axis.getLowerBound();
533: double x2 = x1 + this .maxCandleWidthInMilliseconds;
534: RectangleEdge edge = plot.getDomainAxisEdge();
535: double xx1 = axis.valueToJava2D(x1, dataArea, edge);
536: double xx2 = axis.valueToJava2D(x2, dataArea, edge);
537: this .maxCandleWidth = Math.abs(xx2 - xx1);
538: // Absolute value, since the relative x
539: // positions are reversed for horizontal orientation
540:
541: // calculate the highest volume in the dataset...
542: if (this .drawVolume) {
543: OHLCDataset highLowDataset = (OHLCDataset) dataset;
544: this .maxVolume = 0.0;
545: for (int series = 0; series < highLowDataset
546: .getSeriesCount(); series++) {
547: for (int item = 0; item < highLowDataset
548: .getItemCount(series); item++) {
549: double volume = highLowDataset.getVolumeValue(
550: series, item);
551: if (volume > this .maxVolume) {
552: this .maxVolume = volume;
553: }
554:
555: }
556: }
557: }
558:
559: return new XYItemRendererState(info);
560: }
561:
562: /**
563: * Draws the visual representation of a single data item.
564: *
565: * @param g2 the graphics device.
566: * @param state the renderer state.
567: * @param dataArea the area within which the plot is being drawn.
568: * @param info collects info about the drawing.
569: * @param plot the plot (can be used to obtain standard color
570: * information etc).
571: * @param domainAxis the domain axis.
572: * @param rangeAxis the range axis.
573: * @param dataset the dataset.
574: * @param series the series index (zero-based).
575: * @param item the item index (zero-based).
576: * @param crosshairState crosshair information for the plot
577: * (<code>null</code> permitted).
578: * @param pass the pass index.
579: */
580: public void drawItem(Graphics2D g2, XYItemRendererState state,
581: Rectangle2D dataArea, PlotRenderingInfo info, XYPlot plot,
582: ValueAxis domainAxis, ValueAxis rangeAxis,
583: XYDataset dataset, int series, int item,
584: CrosshairState crosshairState, int pass) {
585:
586: boolean horiz;
587: PlotOrientation orientation = plot.getOrientation();
588: if (orientation == PlotOrientation.HORIZONTAL) {
589: horiz = true;
590: } else if (orientation == PlotOrientation.VERTICAL) {
591: horiz = false;
592: } else {
593: return;
594: }
595:
596: // setup for collecting optional entity info...
597: EntityCollection entities = null;
598: if (info != null) {
599: entities = info.getOwner().getEntityCollection();
600: }
601:
602: OHLCDataset highLowData = (OHLCDataset) dataset;
603:
604: double x = highLowData.getXValue(series, item);
605: double yHigh = highLowData.getHighValue(series, item);
606: double yLow = highLowData.getLowValue(series, item);
607: double yOpen = highLowData.getOpenValue(series, item);
608: double yClose = highLowData.getCloseValue(series, item);
609:
610: RectangleEdge domainEdge = plot.getDomainAxisEdge();
611: double xx = domainAxis.valueToJava2D(x, dataArea, domainEdge);
612:
613: RectangleEdge edge = plot.getRangeAxisEdge();
614: double yyHigh = rangeAxis.valueToJava2D(yHigh, dataArea, edge);
615: double yyLow = rangeAxis.valueToJava2D(yLow, dataArea, edge);
616: double yyOpen = rangeAxis.valueToJava2D(yOpen, dataArea, edge);
617: double yyClose = rangeAxis
618: .valueToJava2D(yClose, dataArea, edge);
619:
620: double volumeWidth;
621: double stickWidth;
622: if (this .candleWidth > 0) {
623: // These are deliberately not bounded to minimums/maxCandleWidth to
624: // retain old behaviour.
625: volumeWidth = this .candleWidth;
626: stickWidth = this .candleWidth;
627: } else {
628: double xxWidth = 0;
629: int itemCount;
630: switch (this .autoWidthMethod) {
631:
632: case WIDTHMETHOD_AVERAGE:
633: itemCount = highLowData.getItemCount(series);
634: if (horiz) {
635: xxWidth = dataArea.getHeight() / itemCount;
636: } else {
637: xxWidth = dataArea.getWidth() / itemCount;
638: }
639: break;
640:
641: case WIDTHMETHOD_SMALLEST:
642: // Note: It would be nice to pre-calculate this per series
643: itemCount = highLowData.getItemCount(series);
644: double lastPos = -1;
645: xxWidth = dataArea.getWidth();
646: for (int i = 0; i < itemCount; i++) {
647: double pos = domainAxis
648: .valueToJava2D(highLowData.getXValue(
649: series, i), dataArea, domainEdge);
650: if (lastPos != -1) {
651: xxWidth = Math.min(xxWidth, Math.abs(pos
652: - lastPos));
653: }
654: lastPos = pos;
655: }
656: break;
657:
658: case WIDTHMETHOD_INTERVALDATA:
659: IntervalXYDataset intervalXYData = (IntervalXYDataset) dataset;
660: double startPos = domainAxis.valueToJava2D(
661: intervalXYData.getStartXValue(series, item),
662: dataArea, plot.getDomainAxisEdge());
663: double endPos = domainAxis.valueToJava2D(intervalXYData
664: .getEndXValue(series, item), dataArea, plot
665: .getDomainAxisEdge());
666: xxWidth = Math.abs(endPos - startPos);
667: break;
668:
669: }
670: xxWidth -= 2 * this .autoWidthGap;
671: xxWidth *= this .autoWidthFactor;
672: xxWidth = Math.min(xxWidth, this .maxCandleWidth);
673: volumeWidth = Math.max(Math.min(1, this .maxCandleWidth),
674: xxWidth);
675: stickWidth = Math.max(Math.min(3, this .maxCandleWidth),
676: xxWidth);
677: }
678:
679: Paint p = getItemPaint(series, item);
680: Paint outlinePaint = null;
681: if (this .useOutlinePaint) {
682: outlinePaint = getItemOutlinePaint(series, item);
683: }
684: Stroke s = getItemStroke(series, item);
685:
686: g2.setStroke(s);
687:
688: if (this .drawVolume) {
689: int volume = (int) highLowData.getVolumeValue(series, item);
690: double volumeHeight = volume / this .maxVolume;
691:
692: double min, max;
693: if (horiz) {
694: min = dataArea.getMinX();
695: max = dataArea.getMaxX();
696: } else {
697: min = dataArea.getMinY();
698: max = dataArea.getMaxY();
699: }
700:
701: double zzVolume = volumeHeight * (max - min);
702:
703: g2.setPaint(Color.gray);
704: Composite originalComposite = g2.getComposite();
705: g2.setComposite(AlphaComposite.getInstance(
706: AlphaComposite.SRC_OVER, 0.3f));
707:
708: if (horiz) {
709: g2.fill(new Rectangle2D.Double(min, xx - volumeWidth
710: / 2, zzVolume, volumeWidth));
711: } else {
712: g2.fill(new Rectangle2D.Double(xx - volumeWidth / 2,
713: max - zzVolume, volumeWidth, zzVolume));
714: }
715:
716: g2.setComposite(originalComposite);
717: }
718:
719: if (this .useOutlinePaint) {
720: g2.setPaint(outlinePaint);
721: } else {
722: g2.setPaint(p);
723: }
724:
725: double yyMaxOpenClose = Math.max(yyOpen, yyClose);
726: double yyMinOpenClose = Math.min(yyOpen, yyClose);
727: double maxOpenClose = Math.max(yOpen, yClose);
728: double minOpenClose = Math.min(yOpen, yClose);
729:
730: // draw the upper shadow
731: if (yHigh > maxOpenClose) {
732: if (horiz) {
733: g2.draw(new Line2D.Double(yyHigh, xx, yyMaxOpenClose,
734: xx));
735: } else {
736: g2.draw(new Line2D.Double(xx, yyHigh, xx,
737: yyMaxOpenClose));
738: }
739: }
740:
741: // draw the lower shadow
742: if (yLow < minOpenClose) {
743: if (horiz) {
744: g2
745: .draw(new Line2D.Double(yyLow, xx,
746: yyMinOpenClose, xx));
747: } else {
748: g2
749: .draw(new Line2D.Double(xx, yyLow, xx,
750: yyMinOpenClose));
751: }
752: }
753:
754: // draw the body
755: Shape body = null;
756: if (horiz) {
757: body = new Rectangle2D.Double(yyMinOpenClose, xx
758: - stickWidth / 2, yyMaxOpenClose - yyMinOpenClose,
759: stickWidth);
760: } else {
761: body = new Rectangle2D.Double(xx - stickWidth / 2,
762: yyMinOpenClose, stickWidth, yyMaxOpenClose
763: - yyMinOpenClose);
764: }
765: if (yClose > yOpen) {
766: if (this .upPaint != null) {
767: g2.setPaint(this .upPaint);
768: } else {
769: g2.setPaint(p);
770: }
771: g2.fill(body);
772: } else {
773: if (this .downPaint != null) {
774: g2.setPaint(this .downPaint);
775: } else {
776: g2.setPaint(p);
777: }
778: g2.fill(body);
779: }
780: if (this .useOutlinePaint) {
781: g2.setPaint(outlinePaint);
782: } else {
783: g2.setPaint(p);
784: }
785: g2.draw(body);
786:
787: // add an entity for the item...
788: if (entities != null) {
789: String tip = null;
790: XYToolTipGenerator generator = getToolTipGenerator(series,
791: item);
792: if (generator != null) {
793: tip = generator.generateToolTip(dataset, series, item);
794: }
795: String url = null;
796: if (getURLGenerator() != null) {
797: url = getURLGenerator().generateURL(dataset, series,
798: item);
799: }
800: XYItemEntity entity = new XYItemEntity(body, dataset,
801: series, item, tip, url);
802: entities.add(entity);
803: }
804:
805: }
806:
807: /**
808: * Tests this renderer for equality with another object.
809: *
810: * @param obj the object (<code>null</code> permitted).
811: *
812: * @return <code>true</code> or <code>false</code>.
813: */
814: public boolean equals(Object obj) {
815: if (obj == this ) {
816: return true;
817: }
818: if (!(obj instanceof CandlestickRenderer)) {
819: return false;
820: }
821: CandlestickRenderer that = (CandlestickRenderer) obj;
822: if (this .candleWidth != that.candleWidth) {
823: return false;
824: }
825: if (!PaintUtilities.equal(this .upPaint, that.upPaint)) {
826: return false;
827: }
828: if (!PaintUtilities.equal(this .downPaint, that.downPaint)) {
829: return false;
830: }
831: if (this .drawVolume != that.drawVolume) {
832: return false;
833: }
834: if (this .maxCandleWidthInMilliseconds != that.maxCandleWidthInMilliseconds) {
835: return false;
836: }
837: if (this .autoWidthMethod != that.autoWidthMethod) {
838: return false;
839: }
840: if (this .autoWidthFactor != that.autoWidthFactor) {
841: return false;
842: }
843: if (this .autoWidthGap != that.autoWidthGap) {
844: return false;
845: }
846: if (this .useOutlinePaint != that.useOutlinePaint) {
847: return false;
848: }
849: return super .equals(obj);
850: }
851:
852: /**
853: * Returns a clone of the renderer.
854: *
855: * @return A clone.
856: *
857: * @throws CloneNotSupportedException if the renderer cannot be cloned.
858: */
859: public Object clone() throws CloneNotSupportedException {
860: return super .clone();
861: }
862:
863: /**
864: * Provides serialization support.
865: *
866: * @param stream the output stream.
867: *
868: * @throws IOException if there is an I/O error.
869: */
870: private void writeObject(ObjectOutputStream stream)
871: throws IOException {
872: stream.defaultWriteObject();
873: SerialUtilities.writePaint(this .upPaint, stream);
874: SerialUtilities.writePaint(this .downPaint, stream);
875: }
876:
877: /**
878: * Provides serialization support.
879: *
880: * @param stream the input stream.
881: *
882: * @throws IOException if there is an I/O error.
883: * @throws ClassNotFoundException if there is a classpath problem.
884: */
885: private void readObject(ObjectInputStream stream)
886: throws IOException, ClassNotFoundException {
887: stream.defaultReadObject();
888: this .upPaint = SerialUtilities.readPaint(stream);
889: this .downPaint = SerialUtilities.readPaint(stream);
890: }
891:
892: // --- DEPRECATED CODE ----------------------------------------------------
893:
894: /**
895: * Returns a flag indicating whether or not volume bars are drawn on the
896: * chart.
897: *
898: * @return <code>true</code> if volume bars are drawn on the chart.
899: *
900: * @deprecated As of 1.0.5, you should use the {@link #getDrawVolume()}
901: * method.
902: */
903: public boolean drawVolume() {
904: return this.drawVolume;
905: }
906:
907: }
|