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