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:  * $Id: CandlestickRenderer.java,v 1.7.2.5 2007/03/05 14:40:33 mungady Exp $
  39:  *
  40:  * Changes
  41:  * -------
  42:  * 13-Dec-2001 : Version 1.  Based on code in the (now redundant) 
  43:  *               CandlestickPlot class, written by Sylvain Vieujot (DG);
  44:  * 23-Jan-2002 : Added DrawInfo parameter to drawItem() method (DG);
  45:  * 28-Mar-2002 : Added a property change listener mechanism so that renderers 
  46:  *               no longer need to be immutable.  Added properties for up and 
  47:  *               down colors (DG);
  48:  * 04-Apr-2002 : Updated with new automatic width calculation and optional 
  49:  *               volume display, contributed by Sylvain Vieujot (DG);
  50:  * 09-Apr-2002 : Removed translatedRangeZero from the drawItem() method, and 
  51:  *               changed the return type of the drawItem method to void, 
  52:  *               reflecting a change in the XYItemRenderer interface.  Added 
  53:  *               tooltip code to drawItem() method (DG);
  54:  * 25-Jun-2002 : Removed redundant code (DG);
  55:  * 05-Aug-2002 : Small modification to drawItem method to support URLs for HTML 
  56:  *               image maps (RA);
  57:  * 19-Sep-2002 : Fixed errors reported by Checkstyle (DG);
  58:  * 25-Mar-2003 : Implemented Serializable (DG);
  59:  * 01-May-2003 : Modified drawItem() method signature (DG);
  60:  * 30-Jun-2003 : Added support for PlotOrientation (for completeness, this 
  61:  *               renderer is unlikely to be used with a HORIZONTAL 
  62:  *               orientation) (DG);
  63:  * 30-Jul-2003 : Modified entity constructor (CZ);
  64:  * 20-Aug-2003 : Implemented Cloneable and PublicCloneable (DG);
  65:  * 29-Aug-2003 : Moved maxVolume calculation to initialise method (see bug 
  66:  *               report 796619) (DG);
  67:  * 02-Sep-2003 : Added maxCandleWidthInMilliseconds as workaround for bug 
  68:  *               796621 (DG);
  69:  * 08-Sep-2003 : Changed ValueAxis API (DG);
  70:  * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG);
  71:  * 13-Oct-2003 : Applied patch from Jerome Fisher to improve auto width 
  72:  *               calculations (DG);
  73:  * 23-Dec-2003 : Fixed bug where up and down paint are used incorrectly (DG);
  74:  * 25-Feb-2004 : Replaced CrosshairInfo with CrosshairState (DG);
  75:  * 15-Jul-2004 : Switched getX() with getXValue() and getY() with 
  76:  *               getYValue() (DG);
  77:  * ------------- JFREECHART 1.0.x ---------------------------------------------
  78:  * 06-Jul-2006 : Swapped calls to getX() --> getXValue(), and the same for the
  79:  *               other data values (DG);
  80:  * 17-Aug-2006 : Corrections to the equals() method (DG);
  81:  * 05-Mar-2007 : Added flag to allow optional use of outline paint (DG);
  82:  * 
  83:  */
  84: 
  85: package org.jfree.chart.renderer.xy;
  86: 
  87: import java.awt.AlphaComposite;
  88: import java.awt.Color;
  89: import java.awt.Composite;
  90: import java.awt.Graphics2D;
  91: import java.awt.Paint;
  92: import java.awt.Shape;
  93: import java.awt.Stroke;
  94: import java.awt.geom.Line2D;
  95: import java.awt.geom.Rectangle2D;
  96: import java.io.IOException;
  97: import java.io.ObjectInputStream;
  98: import java.io.ObjectOutputStream;
  99: 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: }