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