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