    001/* ===========================================================
    002 * JFreeChart : a free chart library for the Java(tm) platform
    003 * ===========================================================
    004 *
    005 * (C) Copyright 2000-2011, 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 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates.
    025 * Other names may be trademarks of their respective owners.]
    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
    088package org.jfree.chart.renderer.xy;
    089
    090import java.awt.AlphaComposite;
    091import java.awt.Color;
    092import java.awt.Composite;
    093import java.awt.Graphics2D;
    094import java.awt.Paint;
    095import java.awt.Stroke;
    096import java.awt.geom.Line2D;
    097import java.awt.geom.Rectangle2D;
    098import java.io.IOException;
    099import java.io.ObjectInputStream;
    100import java.io.ObjectOutputStream;
    101import java.io.Serializable;
    102
    103import org.jfree.chart.axis.ValueAxis;
    104import org.jfree.chart.entity.EntityCollection;
    105import org.jfree.chart.event.RendererChangeEvent;
    106import org.jfree.chart.labels.HighLowItemLabelGenerator;
    107import org.jfree.chart.labels.XYToolTipGenerator;
    108import org.jfree.chart.plot.CrosshairState;
    109import org.jfree.chart.plot.PlotOrientation;
    110import org.jfree.chart.plot.PlotRenderingInfo;
    111import org.jfree.chart.plot.XYPlot;
    112import org.jfree.data.Range;
    113import org.jfree.data.general.DatasetUtilities;
    114import org.jfree.data.xy.IntervalXYDataset;
    115import org.jfree.data.xy.OHLCDataset;
    116import org.jfree.data.xy.XYDataset;
    117import org.jfree.io.SerialUtilities;
    118import org.jfree.ui.RectangleEdge;
    119import org.jfree.util.PaintUtilities;
    120import 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 */
    134public 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}