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