Source for org.jfree.chart.renderer.xy.CandlestickRenderer

   1: /* ===========================================================
   2:  * JFreeChart : a free chart library for the Java(tm) platform
   3:  * ===========================================================
   4:  *
   5:  * (C) Copyright 2000-2007, by Object Refinery Limited and Contributors.
   6:  *
   7:  * Project Info:  http://www.jfree.org/jfreechart/index.html
   8:  *
   9:  * This library is free software; you can redistribute it and/or modify it 
  10:  * under the terms of the GNU Lesser General Public License as published by 
  11:  * the Free Software Foundation; either version 2.1 of the License, or 
  12:  * (at your option) any later version.
  13:  *
  14:  * This library is distributed in the hope that it will be useful, but 
  15:  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
  16:  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 
  17:  * License for more details.
  18:  *
  19:  * You should have received a copy of the GNU Lesser General Public
  20:  * License along with this library; if not, write to the Free Software
  21:  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, 
  22:  * USA.  
  23:  *
  24:  * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 
  25:  * in the United States and other countries.]
  26:  *
  27:  * ------------------------
  28:  * CandlestickRenderer.java
  29:  * ------------------------
  30:  * (C) Copyright 2001-2007, by Object Refinery Limited.
  31:  *
  32:  * Original Authors:  David Gilbert (for Object Refinery Limited);
  33:  *                    Sylvain Vieujot;
  34:  * Contributor(s):    Richard Atkinson;
  35:  *                    Christian W. Zuckschwerdt;
  36:  *                    Jerome Fisher;
  37:  *
  38:  * Changes
  39:  * -------
  40:  * 13-Dec-2001 : Version 1.  Based on code in the (now redundant) 
  41:  *               CandlestickPlot class, written by Sylvain Vieujot (DG);
  42:  * 23-Jan-2002 : Added DrawInfo parameter to drawItem() method (DG);
  43:  * 28-Mar-2002 : Added a property change listener mechanism so that renderers 
  44:  *               no longer need to be immutable.  Added properties for up and 
  45:  *               down colors (DG);
  46:  * 04-Apr-2002 : Updated with new automatic width calculation and optional 
  47:  *               volume display, contributed by Sylvain Vieujot (DG);
  48:  * 09-Apr-2002 : Removed translatedRangeZero from the drawItem() method, and 
  49:  *               changed the return type of the drawItem method to void, 
  50:  *               reflecting a change in the XYItemRenderer interface.  Added 
  51:  *               tooltip code to drawItem() method (DG);
  52:  * 25-Jun-2002 : Removed redundant code (DG);
  53:  * 05-Aug-2002 : Small modification to drawItem method to support URLs for HTML 
  54:  *               image maps (RA);
  55:  * 19-Sep-2002 : Fixed errors reported by Checkstyle (DG);
  56:  * 25-Mar-2003 : Implemented Serializable (DG);
  57:  * 01-May-2003 : Modified drawItem() method signature (DG);
  58:  * 30-Jun-2003 : Added support for PlotOrientation (for completeness, this 
  59:  *               renderer is unlikely to be used with a HORIZONTAL 
  60:  *               orientation) (DG);
  61:  * 30-Jul-2003 : Modified entity constructor (CZ);
  62:  * 20-Aug-2003 : Implemented Cloneable and PublicCloneable (DG);
  63:  * 29-Aug-2003 : Moved maxVolume calculation to initialise method (see bug 
  64:  *               report 796619) (DG);
  65:  * 02-Sep-2003 : Added maxCandleWidthInMilliseconds as workaround for bug 
  66:  *               796621 (DG);
  67:  * 08-Sep-2003 : Changed ValueAxis API (DG);
  68:  * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG);
  69:  * 13-Oct-2003 : Applied patch from Jerome Fisher to improve auto width 
  70:  *               calculations (DG);
  71:  * 23-Dec-2003 : Fixed bug where up and down paint are used incorrectly (DG);
  72:  * 25-Feb-2004 : Replaced CrosshairInfo with CrosshairState (DG);
  73:  * 15-Jul-2004 : Switched getX() with getXValue() and getY() with 
  74:  *               getYValue() (DG);
  75:  * ------------- JFREECHART 1.0.x ---------------------------------------------
  76:  * 06-Jul-2006 : Swapped calls to getX() --> getXValue(), and the same for the
  77:  *               other data values (DG);
  78:  * 17-Aug-2006 : Corrections to the equals() method (DG);
  79:  * 05-Mar-2007 : Added flag to allow optional use of outline paint (DG);
  80:  * 08-Oct-2007 : Added new volumePaint field (DG);
  81:  * 
  82:  */
  83: 
  84: package org.jfree.chart.renderer.xy;
  85: 
  86: import java.awt.AlphaComposite;
  87: import java.awt.Color;
  88: import java.awt.Composite;
  89: import java.awt.Graphics2D;
  90: import java.awt.Paint;
  91: import java.awt.Shape;
  92: import java.awt.Stroke;
  93: import java.awt.geom.Line2D;
  94: import java.awt.geom.Rectangle2D;
  95: import java.io.IOException;
  96: import java.io.ObjectInputStream;
  97: import java.io.ObjectOutputStream;
  98: import java.io.Serializable;
  99: 
 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: }