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