    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    }