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