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