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