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