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-2006, 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-2006, 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.3 2006/08/17 09:21:25 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.0 ---------------------------------------------
  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:  * 
  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:     /** Temporary storage for the maximum volume. */
 181:     private transient double maxVolume;
 182: 
 183:     /**
 184:      * Creates a new renderer for candlestick charts.
 185:      */
 186:     public CandlestickRenderer() {
 187:         this(-1.0);
 188:     }
 189: 
 190:     /**
 191:      * Creates a new renderer for candlestick charts.
 192:      * <P>
 193:      * Use -1 for the candle width if you prefer the width to be calculated 
 194:      * automatically.
 195:      *
 196:      * @param candleWidth  The candle width.
 197:      */
 198:     public CandlestickRenderer(double candleWidth) {
 199:         this(candleWidth, true, new HighLowItemLabelGenerator());
 200:     }
 201: 
 202:     /**
 203:      * Creates a new renderer for candlestick charts.
 204:      * <P>
 205:      * Use -1 for the candle width if you prefer the width to be calculated 
 206:      * automatically.
 207:      *
 208:      * @param candleWidth  the candle width.
 209:      * @param drawVolume  a flag indicating whether or not volume bars should 
 210:      *                    be drawn.
 211:      * @param toolTipGenerator  the tool tip generator. <code>null</code> is 
 212:      *                          none.
 213:      */
 214:     public CandlestickRenderer(double candleWidth, boolean drawVolume,
 215:                                XYToolTipGenerator toolTipGenerator) {
 216: 
 217:         super();
 218:         setToolTipGenerator(toolTipGenerator);
 219:         this.candleWidth = candleWidth;
 220:         this.drawVolume = drawVolume;
 221:         this.upPaint = Color.green;
 222:         this.downPaint = Color.red;
 223: 
 224:     }
 225: 
 226:     /**
 227:      * Returns the width of each candle.
 228:      *
 229:      * @return The candle width.
 230:      * 
 231:      * @see #setCandleWidth(double)
 232:      */
 233:     public double getCandleWidth() {
 234:         return this.candleWidth;
 235:     }
 236: 
 237:     /**
 238:      * Sets the candle width.
 239:      * <P>
 240:      * If you set the width to a negative value, the renderer will calculate
 241:      * the candle width automatically based on the space available on the chart.
 242:      *
 243:      * @param width  The width.
 244:      * @see #setAutoWidthMethod(int)
 245:      * @see #setAutoWidthGap(double)
 246:      * @see #setAutoWidthFactor(double)
 247:      * @see #setMaxCandleWidthInMilliseconds(double)
 248:      */
 249:     public void setCandleWidth(double width) {
 250:         if (width != this.candleWidth) {
 251:             this.candleWidth = width;
 252:             notifyListeners(new RendererChangeEvent(this));
 253:         }
 254:     }
 255: 
 256:     /**
 257:      * Returns the maximum width (in milliseconds) of each candle.
 258:      *
 259:      * @return The maximum candle width in milliseconds.
 260:      */
 261:     public double getMaxCandleWidthInMilliseconds() {
 262:         return this.maxCandleWidthInMilliseconds;
 263:     }
 264: 
 265:     /**
 266:      * Sets the maximum candle width (in milliseconds).  
 267:      *
 268:      * @param millis  The maximum width.
 269:      * @see #setCandleWidth(double)
 270:      * @see #setAutoWidthMethod(int)
 271:      * @see #setAutoWidthGap(double)
 272:      * @see #setAutoWidthFactor(double)
 273:      */
 274:     public void setMaxCandleWidthInMilliseconds(double millis) {
 275:         this.maxCandleWidthInMilliseconds = millis;
 276:         notifyListeners(new RendererChangeEvent(this));
 277:     }
 278: 
 279:     /**
 280:      * Returns the method of automatically calculating the candle width.
 281:      *
 282:      * @return The method of automatically calculating the candle width.
 283:      */
 284:     public int getAutoWidthMethod() {
 285:         return this.autoWidthMethod;
 286:     }
 287: 
 288:     /**
 289:      * Sets the method of automatically calculating the candle width.
 290:      * <p>
 291:      * <code>WIDTHMETHOD_AVERAGE</code>: Divides the entire display (ignoring 
 292:      * scale factor) by the number of items, and uses this as the available 
 293:      * width.<br>
 294:      * <code>WIDTHMETHOD_SMALLEST</code>: Checks the interval between each 
 295:      * item, and uses the smallest as the available width.<br>
 296:      * <code>WIDTHMETHOD_INTERVALDATA</code>: Assumes that the dataset supports
 297:      * the IntervalXYDataset interface, and uses the startXValue - endXValue as 
 298:      * the available width.
 299:      * <br>
 300:      *
 301:      * @param autoWidthMethod  The method of automatically calculating the 
 302:      * candle width.
 303:      *
 304:      * @see #WIDTHMETHOD_AVERAGE
 305:      * @see #WIDTHMETHOD_SMALLEST
 306:      * @see #WIDTHMETHOD_INTERVALDATA
 307:      * @see #setCandleWidth(double)
 308:      * @see #setAutoWidthGap(double)
 309:      * @see #setAutoWidthFactor(double)
 310:      * @see #setMaxCandleWidthInMilliseconds(double)
 311:      */
 312:     public void setAutoWidthMethod(int autoWidthMethod) {
 313:         if (this.autoWidthMethod != autoWidthMethod) {
 314:             this.autoWidthMethod = autoWidthMethod;
 315:             notifyListeners(new RendererChangeEvent(this));
 316:         }
 317:     }
 318: 
 319:     /**
 320:      * Returns the factor by which the available space automatically 
 321:      * calculated for the candles will be multiplied to determine the actual 
 322:      * width to use.
 323:      *
 324:      * @return The width factor (generally between 0.0 and 1.0).
 325:      */
 326:     public double getAutoWidthFactor() {
 327:         return this.autoWidthFactor;
 328:     }
 329: 
 330:     /**
 331:      * Sets the factor by which the available space automatically calculated 
 332:      * for the candles will be multiplied to determine the actual width to use.
 333:      *
 334:      * @param autoWidthFactor The width factor (generally between 0.0 and 1.0).
 335:      * @see #setCandleWidth(double)
 336:      * @see #setAutoWidthMethod(int)
 337:      * @see #setAutoWidthGap(double)
 338:      * @see #setMaxCandleWidthInMilliseconds(double)
 339:      */
 340:     public void setAutoWidthFactor(double autoWidthFactor) {
 341:         if (this.autoWidthFactor != autoWidthFactor) {
 342:             this.autoWidthFactor = autoWidthFactor;
 343:             notifyListeners(new RendererChangeEvent(this));
 344:         }
 345:     }
 346: 
 347:     /**
 348:      * Returns the amount of space to leave on the left and right of each 
 349:      * candle when automatically calculating widths.
 350:      *
 351:      * @return The gap.
 352:      */
 353:     public double getAutoWidthGap() {
 354:         return this.autoWidthGap;
 355:     }
 356: 
 357:     /**
 358:      * Sets the amount of space to leave on the left and right of each candle 
 359:      * when automatically calculating widths.
 360:      *
 361:      * @param autoWidthGap The gap.
 362:      * @see #setCandleWidth(double)
 363:      * @see #setAutoWidthMethod(int)
 364:      * @see #setAutoWidthFactor(double)
 365:      * @see #setMaxCandleWidthInMilliseconds(double)
 366:      */
 367:     public void setAutoWidthGap(double autoWidthGap) {
 368:         if (this.autoWidthGap != autoWidthGap) {
 369:             this.autoWidthGap = autoWidthGap;
 370:             notifyListeners(new RendererChangeEvent(this));
 371:         }
 372:     }
 373: 
 374:     /**
 375:      * Returns the paint used to fill candles when the price moves up from open
 376:      * to close.
 377:      *
 378:      * @return The paint.
 379:      */
 380:     public Paint getUpPaint() {
 381:         return this.upPaint;
 382:     }
 383: 
 384:     /**
 385:      * Sets the paint used to fill candles when the price moves up from open
 386:      * to close.
 387:      * <P>
 388:      * Registered property change listeners are notified that the
 389:      * "CandleStickRenderer.upPaint" property has changed.
 390:      *
 391:      * @param paint The paint.
 392:      */
 393:     public void setUpPaint(Paint paint) {
 394:         this.upPaint = paint;
 395:         notifyListeners(new RendererChangeEvent(this));
 396:     }
 397: 
 398:     /**
 399:      * Returns the paint used to fill candles when the price moves down from
 400:      * open to close.
 401:      *
 402:      * @return The paint.
 403:      */
 404:     public Paint getDownPaint() {
 405:         return this.downPaint;
 406:     }
 407: 
 408:     /**
 409:      * Sets the paint used to fill candles when the price moves down from open
 410:      * to close.
 411:      * <P>
 412:      * Registered property change listeners are notified that the
 413:      * "CandleStickRenderer.downPaint" property has changed.
 414:      *
 415:      * @param paint  The paint.
 416:      */
 417:     public void setDownPaint(Paint paint) {
 418:         this.downPaint = paint;
 419:         notifyListeners(new RendererChangeEvent(this));
 420:     }
 421: 
 422:     /**
 423:      * Returns a flag indicating whether or not volume bars are drawn on the
 424:      * chart.
 425:      *
 426:      * @return <code>true</code> if volume bars are drawn on the chart.
 427:      */
 428:     public boolean drawVolume() {
 429:         return this.drawVolume;
 430:     }
 431: 
 432:     /**
 433:      * Sets a flag that controls whether or not volume bars are drawn in the
 434:      * background.
 435:      *
 436:      * @param flag The flag.
 437:      */
 438:     public void setDrawVolume(boolean flag) {
 439:         if (this.drawVolume != flag) {
 440:             this.drawVolume = flag;
 441:             notifyListeners(new RendererChangeEvent(this));
 442:         }
 443:     }
 444: 
 445:     /**
 446:      * Initialises the renderer then returns the number of 'passes' through the
 447:      * data that the renderer will require (usually just one).  This method 
 448:      * will be called before the first item is rendered, giving the renderer 
 449:      * an opportunity to initialise any state information it wants to maintain.
 450:      * The renderer can do nothing if it chooses.
 451:      *
 452:      * @param g2  the graphics device.
 453:      * @param dataArea  the area inside the axes.
 454:      * @param plot  the plot.
 455:      * @param dataset  the data.
 456:      * @param info  an optional info collection object to return data back to 
 457:      *              the caller.
 458:      *
 459:      * @return The number of passes the renderer requires.
 460:      */
 461:     public XYItemRendererState initialise(Graphics2D g2,
 462:                                           Rectangle2D dataArea,
 463:                                           XYPlot plot,
 464:                                           XYDataset dataset,
 465:                                           PlotRenderingInfo info) {
 466:           
 467:         // calculate the maximum allowed candle width from the axis...
 468:         ValueAxis axis = plot.getDomainAxis();
 469:         double x1 = axis.getLowerBound();
 470:         double x2 = x1 + this.maxCandleWidthInMilliseconds;
 471:         RectangleEdge edge = plot.getDomainAxisEdge();
 472:         double xx1 = axis.valueToJava2D(x1, dataArea, edge);
 473:         double xx2 = axis.valueToJava2D(x2, dataArea, edge);
 474:         this.maxCandleWidth = Math.abs(xx2 - xx1); 
 475:             // Absolute value, since the relative x 
 476:             // positions are reversed for horizontal orientation
 477:         
 478:         // calculate the highest volume in the dataset... 
 479:         if (this.drawVolume) {
 480:             OHLCDataset highLowDataset = (OHLCDataset) dataset;
 481:             this.maxVolume = 0.0;
 482:             for (int series = 0; series < highLowDataset.getSeriesCount(); 
 483:                  series++) {
 484:                 for (int item = 0; item < highLowDataset.getItemCount(series); 
 485:                      item++) {
 486:                     double volume = highLowDataset.getVolumeValue(series, item);
 487:                     if (volume > this.maxVolume) {
 488:                         this.maxVolume = volume;
 489:                     }
 490:                     
 491:                 }    
 492:             }
 493:         }
 494:         
 495:         return new XYItemRendererState(info);
 496:     }
 497: 
 498:     /**
 499:      * Draws the visual representation of a single data item.
 500:      *
 501:      * @param g2  the graphics device.
 502:      * @param state  the renderer state.
 503:      * @param dataArea  the area within which the plot is being drawn.
 504:      * @param info  collects info about the drawing.
 505:      * @param plot  the plot (can be used to obtain standard color 
 506:      *              information etc).
 507:      * @param domainAxis  the domain axis.
 508:      * @param rangeAxis  the range axis.
 509:      * @param dataset  the dataset.
 510:      * @param series  the series index (zero-based).
 511:      * @param item  the item index (zero-based).
 512:      * @param crosshairState  crosshair information for the plot 
 513:      *                        (<code>null</code> permitted).
 514:      * @param pass  the pass index.
 515:      */
 516:     public void drawItem(Graphics2D g2, 
 517:                          XYItemRendererState state,
 518:                          Rectangle2D dataArea,
 519:                          PlotRenderingInfo info,
 520:                          XYPlot plot, 
 521:                          ValueAxis domainAxis, 
 522:                          ValueAxis rangeAxis,
 523:                          XYDataset dataset, 
 524:                          int series, 
 525:                          int item,
 526:                          CrosshairState crosshairState,
 527:                          int pass) {
 528: 
 529:         boolean horiz;
 530:         PlotOrientation orientation = plot.getOrientation();
 531:         if (orientation == PlotOrientation.HORIZONTAL) {
 532:             horiz = true;
 533:         }
 534:         else if (orientation == PlotOrientation.VERTICAL) {
 535:             horiz = false;
 536:         }
 537:         else {
 538:             return;
 539:         }
 540:         
 541:         // setup for collecting optional entity info...
 542:         EntityCollection entities = null;
 543:         if (info != null) {
 544:             entities = info.getOwner().getEntityCollection();
 545:         }
 546: 
 547:         OHLCDataset highLowData = (OHLCDataset) dataset;
 548: 
 549:         double x = highLowData.getXValue(series, item);
 550:         double yHigh = highLowData.getHighValue(series, item);
 551:         double yLow = highLowData.getLowValue(series, item);
 552:         double yOpen = highLowData.getOpenValue(series, item);
 553:         double yClose = highLowData.getCloseValue(series, item);
 554: 
 555:         RectangleEdge domainEdge = plot.getDomainAxisEdge();
 556:         double xx = domainAxis.valueToJava2D(x, dataArea, domainEdge);
 557: 
 558:         RectangleEdge edge = plot.getRangeAxisEdge();
 559:         double yyHigh = rangeAxis.valueToJava2D(yHigh, dataArea, edge);
 560:         double yyLow = rangeAxis.valueToJava2D(yLow, dataArea, edge);
 561:         double yyOpen = rangeAxis.valueToJava2D(yOpen, dataArea, edge);
 562:         double yyClose = rangeAxis.valueToJava2D(yClose, dataArea, edge);
 563: 
 564:         double volumeWidth;
 565:         double stickWidth;
 566:         if (this.candleWidth > 0) {
 567:             // These are deliberately not bounded to minimums/maxCandleWidth to
 568:             //  retain old behaviour.
 569:             volumeWidth = this.candleWidth;
 570:             stickWidth = this.candleWidth;
 571:         }
 572:         else {
 573:             double xxWidth = 0;
 574:             int itemCount;
 575:             switch (this.autoWidthMethod) {
 576:             
 577:                 case WIDTHMETHOD_AVERAGE:
 578:                     itemCount = highLowData.getItemCount(series);
 579:                     if (horiz) {
 580:                         xxWidth = dataArea.getHeight() / itemCount;
 581:                     }
 582:                     else {
 583:                         xxWidth = dataArea.getWidth() / itemCount;
 584:                     }
 585:                     break;
 586:             
 587:                 case WIDTHMETHOD_SMALLEST:
 588:                     // Note: It would be nice to pre-calculate this per series
 589:                     itemCount = highLowData.getItemCount(series);
 590:                     double lastPos = -1;
 591:                     xxWidth = dataArea.getWidth();
 592:                     for (int i = 0; i < itemCount; i++) {
 593:                         double pos = domainAxis.valueToJava2D(
 594:                             highLowData.getXValue(series, i), dataArea, 
 595:                             domainEdge
 596:                         );
 597:                         if (lastPos != -1) {
 598:                             xxWidth = Math.min(
 599:                                 xxWidth, Math.abs(pos - lastPos)
 600:                             );
 601:                         }
 602:                         lastPos = pos;
 603:                     }
 604:                     break;
 605:             
 606:                 case WIDTHMETHOD_INTERVALDATA:
 607:                     IntervalXYDataset intervalXYData 
 608:                         = (IntervalXYDataset) dataset;
 609:                     double startPos = domainAxis.valueToJava2D(
 610:                         intervalXYData.getStartXValue(series, item), dataArea, 
 611:                         plot.getDomainAxisEdge()
 612:                     );
 613:                     double endPos = domainAxis.valueToJava2D(
 614:                         intervalXYData.getEndXValue(series, item), dataArea, 
 615:                         plot.getDomainAxisEdge()
 616:                     );
 617:                     xxWidth = Math.abs(endPos - startPos);
 618:                     break;
 619:                 
 620:             }
 621:             xxWidth -= 2 * this.autoWidthGap;
 622:             xxWidth *= this.autoWidthFactor;
 623:             xxWidth = Math.min(xxWidth, this.maxCandleWidth);
 624:             volumeWidth = Math.max(Math.min(1, this.maxCandleWidth), xxWidth);
 625:             stickWidth = Math.max(Math.min(3, this.maxCandleWidth), xxWidth);
 626:         }
 627: 
 628:         Paint p = getItemPaint(series, item);
 629:         Stroke s = getItemStroke(series, item);
 630: 
 631:         g2.setStroke(s);
 632: 
 633:         if (this.drawVolume) {
 634:             int volume = (int) highLowData.getVolumeValue(series, item);
 635:             double volumeHeight = volume / this.maxVolume;
 636: 
 637:             double min, max;
 638:             if (horiz) {
 639:                 min = dataArea.getMinX();
 640:                 max = dataArea.getMaxX();
 641:             }
 642:             else {
 643:                 min = dataArea.getMinY();
 644:                 max = dataArea.getMaxY();
 645:             }
 646: 
 647:             double zzVolume = volumeHeight * (max - min);
 648: 
 649:             g2.setPaint(Color.gray);
 650:             Composite originalComposite = g2.getComposite();
 651:             g2.setComposite(
 652:                 AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.3f)
 653:             );
 654: 
 655:             if (horiz) {
 656:                 g2.fill(new Rectangle2D.Double(min,
 657:                                                xx - volumeWidth / 2,
 658:                                                zzVolume, volumeWidth));
 659:             }
 660:             else {
 661:                 g2.fill(
 662:                     new Rectangle2D.Double(
 663:                         xx - volumeWidth / 2,
 664:                         max - zzVolume, volumeWidth, zzVolume
 665:                     )
 666:                 );
 667:             }
 668: 
 669:             g2.setComposite(originalComposite);
 670:         }
 671: 
 672:         g2.setPaint(p);
 673: 
 674:         double yyMaxOpenClose = Math.max(yyOpen, yyClose);
 675:         double yyMinOpenClose = Math.min(yyOpen, yyClose);
 676:         double maxOpenClose = Math.max(yOpen, yClose);
 677:         double minOpenClose = Math.min(yOpen, yClose);
 678: 
 679:         // draw the upper shadow
 680:         if (yHigh > maxOpenClose) {
 681:             if (horiz) {
 682:                 g2.draw(new Line2D.Double(yyHigh, xx, yyMaxOpenClose, xx));
 683:             }
 684:             else {
 685:                 g2.draw(new Line2D.Double(xx, yyHigh, xx, yyMaxOpenClose));
 686:             }
 687:         }
 688: 
 689:         // draw the lower shadow
 690:         if (yLow < minOpenClose) {
 691:             if (horiz) {
 692:                 g2.draw(new Line2D.Double(yyLow, xx, yyMinOpenClose, xx));
 693:             }
 694:             else {
 695:                 g2.draw(new Line2D.Double(xx, yyLow, xx, yyMinOpenClose));
 696:             }
 697:         }
 698: 
 699:         // draw the body
 700:         Shape body = null;
 701:         if (horiz) {
 702:             body = new Rectangle2D.Double(
 703:                 yyMinOpenClose, xx - stickWidth / 2, 
 704:                 yyMaxOpenClose - yyMinOpenClose, stickWidth
 705:             );
 706:         } 
 707:         else {
 708:             body = new Rectangle2D.Double(
 709:                 xx - stickWidth / 2, yyMinOpenClose,
 710:                 stickWidth, yyMaxOpenClose - yyMinOpenClose
 711:             );
 712:         }
 713:         if (yClose > yOpen) {
 714:             if (this.upPaint != null) {
 715:                 g2.setPaint(this.upPaint);
 716:                 g2.fill(body);
 717:             }
 718:         }
 719:         else {
 720:             if (this.downPaint != null) {
 721:                 g2.setPaint(this.downPaint);
 722:             }
 723:             g2.fill(body);
 724:         }
 725:         g2.setPaint(p);
 726:         g2.draw(body);
 727: 
 728:         // add an entity for the item...
 729:         if (entities != null) {
 730:             String tip = null;
 731:             XYToolTipGenerator generator = getToolTipGenerator(series, item);
 732:             if (generator != null) {
 733:                 tip = generator.generateToolTip(dataset, series, item);
 734:             }
 735:             String url = null;
 736:             if (getURLGenerator() != null) {
 737:                 url = getURLGenerator().generateURL(dataset, series, item);
 738:             }
 739:             XYItemEntity entity = new XYItemEntity(body, dataset, series, item, 
 740:                     tip, url);
 741:             entities.add(entity);
 742:         }
 743: 
 744:     }
 745: 
 746:     /**
 747:      * Tests this renderer for equality with another object.
 748:      *
 749:      * @param obj  the object (<code>null</code> permitted).
 750:      *
 751:      * @return <code>true</code> or <code>false</code>.
 752:      */
 753:     public boolean equals(Object obj) {
 754:         if (obj == this) {
 755:             return true;
 756:         }
 757:         if (! (obj instanceof CandlestickRenderer)) {
 758:             return false;
 759:         }
 760:         CandlestickRenderer that = (CandlestickRenderer) obj;
 761:         if (this.candleWidth != that.candleWidth) {
 762:             return false;
 763:         }
 764:         if (!PaintUtilities.equal(this.upPaint, that.upPaint)) {
 765:             return false;
 766:         }
 767:         if (!PaintUtilities.equal(this.downPaint, that.downPaint)) {
 768:             return false;
 769:         }
 770:         if (this.drawVolume != that.drawVolume) {
 771:             return false;
 772:         }
 773:         if (this.maxCandleWidthInMilliseconds 
 774:                 != that.maxCandleWidthInMilliseconds) {
 775:             return false;
 776:         }
 777:         if (this.autoWidthMethod != that.autoWidthMethod) {
 778:             return false;
 779:         }
 780:         if (this.autoWidthFactor != that.autoWidthFactor) {
 781:             return false;
 782:         }
 783:         if (this.autoWidthGap != that.autoWidthGap) {
 784:             return false;
 785:         }
 786:         return super.equals(obj);
 787:     }
 788: 
 789:     /**
 790:      * Returns a clone of the renderer.
 791:      * 
 792:      * @return A clone.
 793:      * 
 794:      * @throws CloneNotSupportedException  if the renderer cannot be cloned.
 795:      */
 796:     public Object clone() throws CloneNotSupportedException {
 797:         return super.clone();
 798:     }
 799: 
 800:     /**
 801:      * Provides serialization support.
 802:      *
 803:      * @param stream  the output stream.
 804:      *
 805:      * @throws IOException  if there is an I/O error.
 806:      */
 807:     private void writeObject(ObjectOutputStream stream) throws IOException {
 808:         stream.defaultWriteObject();
 809:         SerialUtilities.writePaint(this.upPaint, stream);
 810:         SerialUtilities.writePaint(this.downPaint, stream);
 811:     }
 812: 
 813:     /**
 814:      * Provides serialization support.
 815:      *
 816:      * @param stream  the input stream.
 817:      *
 818:      * @throws IOException  if there is an I/O error.
 819:      * @throws ClassNotFoundException  if there is a classpath problem.
 820:      */
 821:     private void readObject(ObjectInputStream stream) 
 822:             throws IOException, ClassNotFoundException {
 823:         stream.defaultReadObject();
 824:         this.upPaint = SerialUtilities.readPaint(stream);
 825:         this.downPaint = SerialUtilities.readPaint(stream);
 826:     }
 827:     
 828: }