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.
 257:      * <P>
 258:      * If you set the width to a negative value, the renderer will calculate
 259:      * the candle width automatically based on the space available on the chart.
 260:      *
 261:      * @param width  The width.
 262:      * @see #setAutoWidthMethod(int)
 263:      * @see #setAutoWidthGap(double)
 264:      * @see #setAutoWidthFactor(double)
 265:      * @see #setMaxCandleWidthInMilliseconds(double)
 266:      */
 267:     public void setCandleWidth(double width) {
 268:         if (width != this.candleWidth) {
 269:             this.candleWidth = width;
 270:             notifyListeners(new RendererChangeEvent(this));
 271:         }
 272:     }
 273: 
 274:     /**
 275:      * Returns the maximum width (in milliseconds) of each candle.
 276:      *
 277:      * @return The maximum candle width in milliseconds.
 278:      * 
 279:      * @see #setMaxCandleWidthInMilliseconds(double)
 280:      */
 281:     public double getMaxCandleWidthInMilliseconds() {
 282:         return this.maxCandleWidthInMilliseconds;
 283:     }
 284: 
 285:     /**
 286:      * Sets the maximum candle width (in milliseconds).  
 287:      *
 288:      * @param millis  The maximum width.
 289:      * 
 290:      * @see #getMaxCandleWidthInMilliseconds()
 291:      * @see #setCandleWidth(double)
 292:      * @see #setAutoWidthMethod(int)
 293:      * @see #setAutoWidthGap(double)
 294:      * @see #setAutoWidthFactor(double)
 295:      */
 296:     public void setMaxCandleWidthInMilliseconds(double millis) {
 297:         this.maxCandleWidthInMilliseconds = millis;
 298:         notifyListeners(new RendererChangeEvent(this));
 299:     }
 300: 
 301:     /**
 302:      * Returns the method of automatically calculating the candle width.
 303:      *
 304:      * @return The method of automatically calculating the candle width.
 305:      * 
 306:      * @see #setAutoWidthMethod(int)
 307:      */
 308:     public int getAutoWidthMethod() {
 309:         return this.autoWidthMethod;
 310:     }
 311: 
 312:     /**
 313:      * Sets the method of automatically calculating the candle width.
 314:      * <p>
 315:      * <code>WIDTHMETHOD_AVERAGE</code>: Divides the entire display (ignoring 
 316:      * scale factor) by the number of items, and uses this as the available 
 317:      * width.<br>
 318:      * <code>WIDTHMETHOD_SMALLEST</code>: Checks the interval between each 
 319:      * item, and uses the smallest as the available width.<br>
 320:      * <code>WIDTHMETHOD_INTERVALDATA</code>: Assumes that the dataset supports
 321:      * the IntervalXYDataset interface, and uses the startXValue - endXValue as 
 322:      * the available width.
 323:      * <br>
 324:      *
 325:      * @param autoWidthMethod  The method of automatically calculating the 
 326:      * candle width.
 327:      *
 328:      * @see #WIDTHMETHOD_AVERAGE
 329:      * @see #WIDTHMETHOD_SMALLEST
 330:      * @see #WIDTHMETHOD_INTERVALDATA
 331:      * @see #getAutoWidthMethod()
 332:      * @see #setCandleWidth(double)
 333:      * @see #setAutoWidthGap(double)
 334:      * @see #setAutoWidthFactor(double)
 335:      * @see #setMaxCandleWidthInMilliseconds(double)
 336:      */
 337:     public void setAutoWidthMethod(int autoWidthMethod) {
 338:         if (this.autoWidthMethod != autoWidthMethod) {
 339:             this.autoWidthMethod = autoWidthMethod;
 340:             notifyListeners(new RendererChangeEvent(this));
 341:         }
 342:     }
 343: 
 344:     /**
 345:      * Returns the factor by which the available space automatically 
 346:      * calculated for the candles will be multiplied to determine the actual 
 347:      * width to use.
 348:      *
 349:      * @return The width factor (generally between 0.0 and 1.0).
 350:      * 
 351:      * @see #setAutoWidthFactor(double)
 352:      */
 353:     public double getAutoWidthFactor() {
 354:         return this.autoWidthFactor;
 355:     }
 356: 
 357:     /**
 358:      * Sets the factor by which the available space automatically calculated 
 359:      * for the candles will be multiplied to determine the actual width to use.
 360:      *
 361:      * @param autoWidthFactor The width factor (generally between 0.0 and 1.0).
 362:      * 
 363:      * @see #getAutoWidthFactor()
 364:      * @see #setCandleWidth(double)
 365:      * @see #setAutoWidthMethod(int)
 366:      * @see #setAutoWidthGap(double)
 367:      * @see #setMaxCandleWidthInMilliseconds(double)
 368:      */
 369:     public void setAutoWidthFactor(double autoWidthFactor) {
 370:         if (this.autoWidthFactor != autoWidthFactor) {
 371:             this.autoWidthFactor = autoWidthFactor;
 372:             notifyListeners(new RendererChangeEvent(this));
 373:         }
 374:     }
 375: 
 376:     /**
 377:      * Returns the amount of space to leave on the left and right of each 
 378:      * candle when automatically calculating widths.
 379:      *
 380:      * @return The gap.
 381:      * 
 382:      * @see #setAutoWidthGap(double)
 383:      */
 384:     public double getAutoWidthGap() {
 385:         return this.autoWidthGap;
 386:     }
 387: 
 388:     /**
 389:      * Sets the amount of space to leave on the left and right of each candle 
 390:      * when automatically calculating widths.
 391:      *
 392:      * @param autoWidthGap The gap.
 393:      * 
 394:      * @see #getAutoWidthGap()
 395:      * @see #setCandleWidth(double)
 396:      * @see #setAutoWidthMethod(int)
 397:      * @see #setAutoWidthFactor(double)
 398:      * @see #setMaxCandleWidthInMilliseconds(double)
 399:      */
 400:     public void setAutoWidthGap(double autoWidthGap) {
 401:         if (this.autoWidthGap != autoWidthGap) {
 402:             this.autoWidthGap = autoWidthGap;
 403:             notifyListeners(new RendererChangeEvent(this));
 404:         }
 405:     }
 406: 
 407:     /**
 408:      * Returns the paint used to fill candles when the price moves up from open
 409:      * to close.
 410:      *
 411:      * @return The paint (possibly <code>null</code>).
 412:      * 
 413:      * @see #setUpPaint(Paint)
 414:      */
 415:     public Paint getUpPaint() {
 416:         return this.upPaint;
 417:     }
 418: 
 419:     /**
 420:      * Sets the paint used to fill candles when the price moves up from open
 421:      * to close and sends a {@link RendererChangeEvent} to all registered
 422:      * listeners.
 423:      *
 424:      * @param paint  the paint (<code>null</code> permitted).
 425:      * 
 426:      * @see #getUpPaint()
 427:      */
 428:     public void setUpPaint(Paint paint) {
 429:         this.upPaint = paint;
 430:         notifyListeners(new RendererChangeEvent(this));
 431:     }
 432: 
 433:     /**
 434:      * Returns the paint used to fill candles when the price moves down from
 435:      * open to close.
 436:      *
 437:      * @return The paint (possibly <code>null</code>).
 438:      * 
 439:      * @see #setDownPaint(Paint)
 440:      */
 441:     public Paint getDownPaint() {
 442:         return this.downPaint;
 443:     }
 444: 
 445:     /**
 446:      * Sets the paint used to fill candles when the price moves down from open
 447:      * to close and sends a {@link RendererChangeEvent} to all registered
 448:      * listeners.
 449:      *
 450:      * @param paint  The paint (<code>null</code> permitted).
 451:      */
 452:     public void setDownPaint(Paint paint) {
 453:         this.downPaint = paint;
 454:         notifyListeners(new RendererChangeEvent(this));
 455:     }
 456: 
 457:     /**
 458:      * Returns a flag indicating whether or not volume bars are drawn on the
 459:      * chart.
 460:      * 
 461:      * @return A boolean.
 462:      * 
 463:      * @since 1.0.5
 464:      * 
 465:      * @see #setDrawVolume(boolean)
 466:      */
 467:     public boolean getDrawVolume() {
 468:         return this.drawVolume;
 469:     }
 470: 
 471:     /**
 472:      * Sets a flag that controls whether or not volume bars are drawn in the
 473:      * background and sends a {@link RendererChangeEvent} to all registered
 474:      * listeners.
 475:      *
 476:      * @param flag  the flag.
 477:      * 
 478:      * @see #getDrawVolume()
 479:      */
 480:     public void setDrawVolume(boolean flag) {
 481:         if (this.drawVolume != flag) {
 482:             this.drawVolume = flag;
 483:             notifyListeners(new RendererChangeEvent(this));
 484:         }
 485:     }
 486:     
 487:     /**
 488:      * Returns the paint that is used to fill the volume bars if they are
 489:      * visible.
 490:      * 
 491:      * @return The paint (never <code>null</code>).
 492:      * 
 493:      * @see #setVolumePaint(Paint)
 494:      * 
 495:      * @since 1.0.7
 496:      */
 497:     public Paint getVolumePaint() {
 498:         return this.volumePaint;    
 499:     }
 500:     
 501:     /**
 502:      * Sets the paint used to fill the volume bars, and sends a 
 503:      * {@link RendererChangeEvent} to all registered listeners.
 504:      * 
 505:      * @param paint  the paint (<code>null</code> not permitted).
 506:      * 
 507:      * @see #getVolumePaint()
 508:      * @see #getDrawVolume()
 509:      * 
 510:      * @since 1.0.7
 511:      */
 512:     public void setVolumePaint(Paint paint) {
 513:         if (paint == null) { 
 514:             throw new IllegalArgumentException("Null 'paint' argument.");
 515:         }
 516:         this.volumePaint = paint;
 517:         notifyListeners(new RendererChangeEvent(this));        
 518:     }
 519: 
 520:     /**
 521:      * Returns the flag that controls whether or not the renderer's outline
 522:      * paint is used to draw the candlestick outline.  The default value is
 523:      * <code>false</code>.
 524:      * 
 525:      * @return A boolean.
 526:      * 
 527:      * @since 1.0.5
 528:      * 
 529:      * @see #setUseOutlinePaint(boolean)
 530:      */
 531:     public boolean getUseOutlinePaint() {
 532:         return this.useOutlinePaint;
 533:     }
 534:     
 535:     /**
 536:      * Sets the flag that controls whether or not the renderer's outline
 537:      * paint is used to draw the candlestick outline, and sends a 
 538:      * {@link RendererChangeEvent} to all registered listeners.
 539:      * 
 540:      * @param use  the new flag value.
 541:      * 
 542:      * @since 1.0.5
 543:      * 
 544:      * @see #getUseOutlinePaint()
 545:      */
 546:     public void setUseOutlinePaint(boolean use) {
 547:         if (this.useOutlinePaint != use) {
 548:             this.useOutlinePaint = use;
 549:             fireChangeEvent();
 550:         }
 551:     }
 552:     
 553:     /**
 554:      * Initialises the renderer then returns the number of 'passes' through the
 555:      * data that the renderer will require (usually just one).  This method 
 556:      * will be called before the first item is rendered, giving the renderer 
 557:      * an opportunity to initialise any state information it wants to maintain.
 558:      * The renderer can do nothing if it chooses.
 559:      *
 560:      * @param g2  the graphics device.
 561:      * @param dataArea  the area inside the axes.
 562:      * @param plot  the plot.
 563:      * @param dataset  the data.
 564:      * @param info  an optional info collection object to return data back to 
 565:      *              the caller.
 566:      *
 567:      * @return The number of passes the renderer requires.
 568:      */
 569:     public XYItemRendererState initialise(Graphics2D g2,
 570:                                           Rectangle2D dataArea,
 571:                                           XYPlot plot,
 572:                                           XYDataset dataset,
 573:                                           PlotRenderingInfo info) {
 574:           
 575:         // calculate the maximum allowed candle width from the axis...
 576:         ValueAxis axis = plot.getDomainAxis();
 577:         double x1 = axis.getLowerBound();
 578:         double x2 = x1 + this.maxCandleWidthInMilliseconds;
 579:         RectangleEdge edge = plot.getDomainAxisEdge();
 580:         double xx1 = axis.valueToJava2D(x1, dataArea, edge);
 581:         double xx2 = axis.valueToJava2D(x2, dataArea, edge);
 582:         this.maxCandleWidth = Math.abs(xx2 - xx1); 
 583:             // Absolute value, since the relative x 
 584:             // positions are reversed for horizontal orientation
 585:         
 586:         // calculate the highest volume in the dataset... 
 587:         if (this.drawVolume) {
 588:             OHLCDataset highLowDataset = (OHLCDataset) dataset;
 589:             this.maxVolume = 0.0;
 590:             for (int series = 0; series < highLowDataset.getSeriesCount(); 
 591:                  series++) {
 592:                 for (int item = 0; item < highLowDataset.getItemCount(series); 
 593:                      item++) {
 594:                     double volume = highLowDataset.getVolumeValue(series, item);
 595:                     if (volume > this.maxVolume) {
 596:                         this.maxVolume = volume;
 597:                     }
 598:                     
 599:                 }    
 600:             }
 601:         }
 602:         
 603:         return new XYItemRendererState(info);
 604:     }
 605: 
 606:     /**
 607:      * Draws the visual representation of a single data item.
 608:      *
 609:      * @param g2  the graphics device.
 610:      * @param state  the renderer state.
 611:      * @param dataArea  the area within which the plot is being drawn.
 612:      * @param info  collects info about the drawing.
 613:      * @param plot  the plot (can be used to obtain standard color 
 614:      *              information etc).
 615:      * @param domainAxis  the domain axis.
 616:      * @param rangeAxis  the range axis.
 617:      * @param dataset  the dataset.
 618:      * @param series  the series index (zero-based).
 619:      * @param item  the item index (zero-based).
 620:      * @param crosshairState  crosshair information for the plot 
 621:      *                        (<code>null</code> permitted).
 622:      * @param pass  the pass index.
 623:      */
 624:     public void drawItem(Graphics2D g2, 
 625:                          XYItemRendererState state,
 626:                          Rectangle2D dataArea,
 627:                          PlotRenderingInfo info,
 628:                          XYPlot plot, 
 629:                          ValueAxis domainAxis, 
 630:                          ValueAxis rangeAxis,
 631:                          XYDataset dataset, 
 632:                          int series, 
 633:                          int item,
 634:                          CrosshairState crosshairState,
 635:                          int pass) {
 636: 
 637:         boolean horiz;
 638:         PlotOrientation orientation = plot.getOrientation();
 639:         if (orientation == PlotOrientation.HORIZONTAL) {
 640:             horiz = true;
 641:         }
 642:         else if (orientation == PlotOrientation.VERTICAL) {
 643:             horiz = false;
 644:         }
 645:         else {
 646:             return;
 647:         }
 648:         
 649:         // setup for collecting optional entity info...
 650:         EntityCollection entities = null;
 651:         if (info != null) {
 652:             entities = info.getOwner().getEntityCollection();
 653:         }
 654: 
 655:         OHLCDataset highLowData = (OHLCDataset) dataset;
 656: 
 657:         double x = highLowData.getXValue(series, item);
 658:         double yHigh = highLowData.getHighValue(series, item);
 659:         double yLow = highLowData.getLowValue(series, item);
 660:         double yOpen = highLowData.getOpenValue(series, item);
 661:         double yClose = highLowData.getCloseValue(series, item);
 662: 
 663:         RectangleEdge domainEdge = plot.getDomainAxisEdge();
 664:         double xx = domainAxis.valueToJava2D(x, dataArea, domainEdge);
 665: 
 666:         RectangleEdge edge = plot.getRangeAxisEdge();
 667:         double yyHigh = rangeAxis.valueToJava2D(yHigh, dataArea, edge);
 668:         double yyLow = rangeAxis.valueToJava2D(yLow, dataArea, edge);
 669:         double yyOpen = rangeAxis.valueToJava2D(yOpen, dataArea, edge);
 670:         double yyClose = rangeAxis.valueToJava2D(yClose, dataArea, edge);
 671: 
 672:         double volumeWidth;
 673:         double stickWidth;
 674:         if (this.candleWidth > 0) {
 675:             // These are deliberately not bounded to minimums/maxCandleWidth to
 676:             //  retain old behaviour.
 677:             volumeWidth = this.candleWidth;
 678:             stickWidth = this.candleWidth;
 679:         }
 680:         else {
 681:             double xxWidth = 0;
 682:             int itemCount;
 683:             switch (this.autoWidthMethod) {
 684:             
 685:                 case WIDTHMETHOD_AVERAGE:
 686:                     itemCount = highLowData.getItemCount(series);
 687:                     if (horiz) {
 688:                         xxWidth = dataArea.getHeight() / itemCount;
 689:                     }
 690:                     else {
 691:                         xxWidth = dataArea.getWidth() / itemCount;
 692:                     }
 693:                     break;
 694:             
 695:                 case WIDTHMETHOD_SMALLEST:
 696:                     // Note: It would be nice to pre-calculate this per series
 697:                     itemCount = highLowData.getItemCount(series);
 698:                     double lastPos = -1;
 699:                     xxWidth = dataArea.getWidth();
 700:                     for (int i = 0; i < itemCount; i++) {
 701:                         double pos = domainAxis.valueToJava2D(
 702:                                 highLowData.getXValue(series, i), dataArea, 
 703:                                 domainEdge);
 704:                         if (lastPos != -1) {
 705:                             xxWidth = Math.min(xxWidth, 
 706:                                     Math.abs(pos - lastPos));
 707:                         }
 708:                         lastPos = pos;
 709:                     }
 710:                     break;
 711:             
 712:                 case WIDTHMETHOD_INTERVALDATA:
 713:                     IntervalXYDataset intervalXYData 
 714:                             = (IntervalXYDataset) dataset;
 715:                     double startPos = domainAxis.valueToJava2D(
 716:                             intervalXYData.getStartXValue(series, item), 
 717:                             dataArea, plot.getDomainAxisEdge());
 718:                     double endPos = domainAxis.valueToJava2D(
 719:                             intervalXYData.getEndXValue(series, item), 
 720:                             dataArea, plot.getDomainAxisEdge());
 721:                     xxWidth = Math.abs(endPos - startPos);
 722:                     break;
 723:                 
 724:             }
 725:             xxWidth -= 2 * this.autoWidthGap;
 726:             xxWidth *= this.autoWidthFactor;
 727:             xxWidth = Math.min(xxWidth, this.maxCandleWidth);
 728:             volumeWidth = Math.max(Math.min(1, this.maxCandleWidth), xxWidth);
 729:             stickWidth = Math.max(Math.min(3, this.maxCandleWidth), xxWidth);
 730:         }
 731: 
 732:         Paint p = getItemPaint(series, item);
 733:         Paint outlinePaint = null;
 734:         if (this.useOutlinePaint) {
 735:             outlinePaint = getItemOutlinePaint(series, item);
 736:         }
 737:         Stroke s = getItemStroke(series, item);
 738: 
 739:         g2.setStroke(s);
 740: 
 741:         if (this.drawVolume) {
 742:             int volume = (int) highLowData.getVolumeValue(series, item);
 743:             double volumeHeight = volume / this.maxVolume;
 744: 
 745:             double min, max;
 746:             if (horiz) {
 747:                 min = dataArea.getMinX();
 748:                 max = dataArea.getMaxX();
 749:             }
 750:             else {
 751:                 min = dataArea.getMinY();
 752:                 max = dataArea.getMaxY();
 753:             }
 754: 
 755:             double zzVolume = volumeHeight * (max - min);
 756: 
 757:             g2.setPaint(getVolumePaint());
 758:             Composite originalComposite = g2.getComposite();
 759:             g2.setComposite(
 760:                 AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.3f)
 761:             );
 762: 
 763:             if (horiz) {
 764:                 g2.fill(new Rectangle2D.Double(min, xx - volumeWidth / 2,
 765:                         zzVolume, volumeWidth));
 766:             }
 767:             else {
 768:                 g2.fill(new Rectangle2D.Double(xx - volumeWidth / 2,
 769:                         max - zzVolume, volumeWidth, zzVolume));
 770:             }
 771: 
 772:             g2.setComposite(originalComposite);
 773:         }
 774: 
 775:         if (this.useOutlinePaint) {
 776:             g2.setPaint(outlinePaint);
 777:         }
 778:         else {
 779:             g2.setPaint(p);
 780:         }
 781: 
 782:         double yyMaxOpenClose = Math.max(yyOpen, yyClose);
 783:         double yyMinOpenClose = Math.min(yyOpen, yyClose);
 784:         double maxOpenClose = Math.max(yOpen, yClose);
 785:         double minOpenClose = Math.min(yOpen, yClose);
 786: 
 787:         // draw the upper shadow
 788:         if (yHigh > maxOpenClose) {
 789:             if (horiz) {
 790:                 g2.draw(new Line2D.Double(yyHigh, xx, yyMaxOpenClose, xx));
 791:             }
 792:             else {
 793:                 g2.draw(new Line2D.Double(xx, yyHigh, xx, yyMaxOpenClose));
 794:             }
 795:         }
 796: 
 797:         // draw the lower shadow
 798:         if (yLow < minOpenClose) {
 799:             if (horiz) {
 800:                 g2.draw(new Line2D.Double(yyLow, xx, yyMinOpenClose, xx));
 801:             }
 802:             else {
 803:                 g2.draw(new Line2D.Double(xx, yyLow, xx, yyMinOpenClose));
 804:             }
 805:         }
 806: 
 807:         // draw the body
 808:         Shape body = null;
 809:         if (horiz) {
 810:             body = new Rectangle2D.Double(yyMinOpenClose, xx - stickWidth / 2, 
 811:                     yyMaxOpenClose - yyMinOpenClose, stickWidth);
 812:         } 
 813:         else {
 814:             body = new Rectangle2D.Double(xx - stickWidth / 2, yyMinOpenClose,
 815:                     stickWidth, yyMaxOpenClose - yyMinOpenClose);
 816:         }
 817:         if (yClose > yOpen) {
 818:             if (this.upPaint != null) {
 819:                 g2.setPaint(this.upPaint);
 820:             }
 821:             else {
 822:                 g2.setPaint(p);
 823:             }
 824:             g2.fill(body);
 825:         }
 826:         else {
 827:             if (this.downPaint != null) {
 828:                 g2.setPaint(this.downPaint);
 829:             }
 830:             else {
 831:                 g2.setPaint(p);
 832:             }
 833:             g2.fill(body);
 834:         }
 835:         if (this.useOutlinePaint) {
 836:             g2.setPaint(outlinePaint);
 837:         }
 838:         else {
 839:             g2.setPaint(p);
 840:         }
 841:         g2.draw(body);
 842: 
 843:         // add an entity for the item...
 844:         if (entities != null) {
 845:             String tip = null;
 846:             XYToolTipGenerator generator = getToolTipGenerator(series, item);
 847:             if (generator != null) {
 848:                 tip = generator.generateToolTip(dataset, series, item);
 849:             }
 850:             String url = null;
 851:             if (getURLGenerator() != null) {
 852:                 url = getURLGenerator().generateURL(dataset, series, item);
 853:             }
 854:             XYItemEntity entity = new XYItemEntity(body, dataset, series, item, 
 855:                     tip, url);
 856:             entities.add(entity);
 857:         }
 858: 
 859:     }
 860: 
 861:     /**
 862:      * Tests this renderer for equality with another object.
 863:      *
 864:      * @param obj  the object (<code>null</code> permitted).
 865:      *
 866:      * @return <code>true</code> or <code>false</code>.
 867:      */
 868:     public boolean equals(Object obj) {
 869:         if (obj == this) {
 870:             return true;
 871:         }
 872:         if (!(obj instanceof CandlestickRenderer)) {
 873:             return false;
 874:         }
 875:         CandlestickRenderer that = (CandlestickRenderer) obj;
 876:         if (this.candleWidth != that.candleWidth) {
 877:             return false;
 878:         }
 879:         if (!PaintUtilities.equal(this.upPaint, that.upPaint)) {
 880:             return false;
 881:         }
 882:         if (!PaintUtilities.equal(this.downPaint, that.downPaint)) {
 883:             return false;
 884:         }
 885:         if (this.drawVolume != that.drawVolume) {
 886:             return false;
 887:         }
 888:         if (this.maxCandleWidthInMilliseconds 
 889:                 != that.maxCandleWidthInMilliseconds) {
 890:             return false;
 891:         }
 892:         if (this.autoWidthMethod != that.autoWidthMethod) {
 893:             return false;
 894:         }
 895:         if (this.autoWidthFactor != that.autoWidthFactor) {
 896:             return false;
 897:         }
 898:         if (this.autoWidthGap != that.autoWidthGap) {
 899:             return false;
 900:         }
 901:         if (this.useOutlinePaint != that.useOutlinePaint) {
 902:             return false;
 903:         }
 904:         if (!PaintUtilities.equal(this.volumePaint, that.volumePaint)) {
 905:             return false;
 906:         }
 907:         return super.equals(obj);
 908:     }
 909: 
 910:     /**
 911:      * Returns a clone of the renderer.
 912:      * 
 913:      * @return A clone.
 914:      * 
 915:      * @throws CloneNotSupportedException  if the renderer cannot be cloned.
 916:      */
 917:     public Object clone() throws CloneNotSupportedException {
 918:         return super.clone();
 919:     }
 920: 
 921:     /**
 922:      * Provides serialization support.
 923:      *
 924:      * @param stream  the output stream.
 925:      *
 926:      * @throws IOException  if there is an I/O error.
 927:      */
 928:     private void writeObject(ObjectOutputStream stream) throws IOException {
 929:         stream.defaultWriteObject();
 930:         SerialUtilities.writePaint(this.upPaint, stream);
 931:         SerialUtilities.writePaint(this.downPaint, stream);
 932:         SerialUtilities.writePaint(this.volumePaint, stream);
 933:     }
 934: 
 935:     /**
 936:      * Provides serialization support.
 937:      *
 938:      * @param stream  the input stream.
 939:      *
 940:      * @throws IOException  if there is an I/O error.
 941:      * @throws ClassNotFoundException  if there is a classpath problem.
 942:      */
 943:     private void readObject(ObjectInputStream stream) 
 944:             throws IOException, ClassNotFoundException {
 945:         stream.defaultReadObject();
 946:         this.upPaint = SerialUtilities.readPaint(stream);
 947:         this.downPaint = SerialUtilities.readPaint(stream);
 948:         this.volumePaint = SerialUtilities.readPaint(stream);
 949:     }
 950: 
 951:     // --- DEPRECATED CODE ----------------------------------------------------
 952:     
 953:     /**
 954:      * Returns a flag indicating whether or not volume bars are drawn on the
 955:      * chart.
 956:      *
 957:      * @return <code>true</code> if volume bars are drawn on the chart.
 958:      * 
 959:      * @deprecated As of 1.0.5, you should use the {@link #getDrawVolume()} 
 960:      *         method.
 961:      */
 962:     public boolean drawVolume() {
 963:         return this.drawVolume;
 964:     }
 965: 
 966: }