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