001 /* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2007, by Object Refinery Limited and Contributors.
006 *
007 * Project Info: http://www.jfree.org/jfreechart/index.html
008 *
009 * This library is free software; you can redistribute it and/or modify it
010 * under the terms of the GNU Lesser General Public License as published by
011 * the Free Software Foundation; either version 2.1 of the License, or
012 * (at your option) any later version.
013 *
014 * This library is distributed in the hope that it will be useful, but
015 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
016 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
017 * License for more details.
018 *
019 * You should have received a copy of the GNU Lesser General Public
020 * License along with this library; if not, write to the Free Software
021 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
022 * USA.
023 *
024 * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
025 * in the United States and other countries.]
026 *
027 * ------------------------
028 * CandlestickRenderer.java
029 * ------------------------
030 * (C) Copyright 2001-2007, by Object Refinery Limited.
031 *
032 * Original Authors: David Gilbert (for Object Refinery Limited);
033 * Sylvain Vieujot;
034 * Contributor(s): Richard Atkinson;
035 * Christian W. Zuckschwerdt;
036 * Jerome Fisher;
037 *
038 * $Id: CandlestickRenderer.java,v 1.7.2.5 2007/03/05 14:40:33 mungady Exp $
039 *
040 * Changes
041 * -------
042 * 13-Dec-2001 : Version 1. Based on code in the (now redundant)
043 * CandlestickPlot class, written by Sylvain Vieujot (DG);
044 * 23-Jan-2002 : Added DrawInfo parameter to drawItem() method (DG);
045 * 28-Mar-2002 : Added a property change listener mechanism so that renderers
046 * no longer need to be immutable. Added properties for up and
047 * down colors (DG);
048 * 04-Apr-2002 : Updated with new automatic width calculation and optional
049 * volume display, contributed by Sylvain Vieujot (DG);
050 * 09-Apr-2002 : Removed translatedRangeZero from the drawItem() method, and
051 * changed the return type of the drawItem method to void,
052 * reflecting a change in the XYItemRenderer interface. Added
053 * tooltip code to drawItem() method (DG);
054 * 25-Jun-2002 : Removed redundant code (DG);
055 * 05-Aug-2002 : Small modification to drawItem method to support URLs for HTML
056 * image maps (RA);
057 * 19-Sep-2002 : Fixed errors reported by Checkstyle (DG);
058 * 25-Mar-2003 : Implemented Serializable (DG);
059 * 01-May-2003 : Modified drawItem() method signature (DG);
060 * 30-Jun-2003 : Added support for PlotOrientation (for completeness, this
061 * renderer is unlikely to be used with a HORIZONTAL
062 * orientation) (DG);
063 * 30-Jul-2003 : Modified entity constructor (CZ);
064 * 20-Aug-2003 : Implemented Cloneable and PublicCloneable (DG);
065 * 29-Aug-2003 : Moved maxVolume calculation to initialise method (see bug
066 * report 796619) (DG);
067 * 02-Sep-2003 : Added maxCandleWidthInMilliseconds as workaround for bug
068 * 796621 (DG);
069 * 08-Sep-2003 : Changed ValueAxis API (DG);
070 * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG);
071 * 13-Oct-2003 : Applied patch from Jerome Fisher to improve auto width
072 * calculations (DG);
073 * 23-Dec-2003 : Fixed bug where up and down paint are used incorrectly (DG);
074 * 25-Feb-2004 : Replaced CrosshairInfo with CrosshairState (DG);
075 * 15-Jul-2004 : Switched getX() with getXValue() and getY() with
076 * getYValue() (DG);
077 * ------------- JFREECHART 1.0.x ---------------------------------------------
078 * 06-Jul-2006 : Swapped calls to getX() --> getXValue(), and the same for the
079 * other data values (DG);
080 * 17-Aug-2006 : Corrections to the equals() method (DG);
081 * 05-Mar-2007 : Added flag to allow optional use of outline paint (DG);
082 *
083 */
084
085 package org.jfree.chart.renderer.xy;
086
087 import java.awt.AlphaComposite;
088 import java.awt.Color;
089 import java.awt.Composite;
090 import java.awt.Graphics2D;
091 import java.awt.Paint;
092 import java.awt.Shape;
093 import java.awt.Stroke;
094 import java.awt.geom.Line2D;
095 import java.awt.geom.Rectangle2D;
096 import java.io.IOException;
097 import java.io.ObjectInputStream;
098 import java.io.ObjectOutputStream;
099 import java.io.Serializable;
100
101 import org.jfree.chart.axis.ValueAxis;
102 import org.jfree.chart.entity.EntityCollection;
103 import org.jfree.chart.entity.XYItemEntity;
104 import org.jfree.chart.event.RendererChangeEvent;
105 import org.jfree.chart.labels.HighLowItemLabelGenerator;
106 import org.jfree.chart.labels.XYToolTipGenerator;
107 import org.jfree.chart.plot.CrosshairState;
108 import org.jfree.chart.plot.PlotOrientation;
109 import org.jfree.chart.plot.PlotRenderingInfo;
110 import org.jfree.chart.plot.XYPlot;
111 import org.jfree.data.xy.IntervalXYDataset;
112 import org.jfree.data.xy.OHLCDataset;
113 import org.jfree.data.xy.XYDataset;
114 import org.jfree.io.SerialUtilities;
115 import org.jfree.ui.RectangleEdge;
116 import org.jfree.util.PaintUtilities;
117 import org.jfree.util.PublicCloneable;
118
119 /**
120 * A renderer that draws candlesticks on an {@link XYPlot} (requires a
121 * {@link OHLCDataset}).
122 * <P>
123 * This renderer does not include code to calculate the crosshair point for the
124 * plot.
125 */
126 public class CandlestickRenderer extends AbstractXYItemRenderer
127 implements XYItemRenderer,
128 Cloneable,
129 PublicCloneable,
130 Serializable {
131
132 /** For serialization. */
133 private static final long serialVersionUID = 50390395841817121L;
134
135 /** The average width method. */
136 public static final int WIDTHMETHOD_AVERAGE = 0;
137
138 /** The smallest width method. */
139 public static final int WIDTHMETHOD_SMALLEST = 1;
140
141 /** The interval data method. */
142 public static final int WIDTHMETHOD_INTERVALDATA = 2;
143
144 /** The method of automatically calculating the candle width. */
145 private int autoWidthMethod = WIDTHMETHOD_AVERAGE;
146
147 /**
148 * The number (generally between 0.0 and 1.0) by which the available space
149 * automatically calculated for the candles will be multiplied to determine
150 * the actual width to use.
151 */
152 private double autoWidthFactor = 4.5 / 7;
153
154 /** The minimum gap between one candle and the next */
155 private double autoWidthGap = 0.0;
156
157 /** The candle width. */
158 private double candleWidth;
159
160 /** The maximum candlewidth in milliseconds. */
161 private double maxCandleWidthInMilliseconds = 1000.0 * 60.0 * 60.0 * 20.0;
162
163 /** Temporary storage for the maximum candle width. */
164 private double maxCandleWidth;
165
166 /**
167 * The paint used to fill the candle when the price moved up from open to
168 * close.
169 */
170 private transient Paint upPaint;
171
172 /**
173 * The paint used to fill the candle when the price moved down from open
174 * to close.
175 */
176 private transient Paint downPaint;
177
178 /** A flag controlling whether or not volume bars are drawn on the chart. */
179 private boolean drawVolume;
180
181 /** Temporary storage for the maximum volume. */
182 private transient double maxVolume;
183
184 /**
185 * A flag that controls whether or not the renderer's outline paint is
186 * used to draw the outline of the candlestick. The default value is
187 * <code>false</code> to avoid a change of behaviour for existing code.
188 *
189 * @since 1.0.5
190 */
191 private boolean useOutlinePaint;
192
193 /**
194 * Creates a new renderer for candlestick charts.
195 */
196 public CandlestickRenderer() {
197 this(-1.0);
198 }
199
200 /**
201 * Creates a new renderer for candlestick charts.
202 * <P>
203 * Use -1 for the candle width if you prefer the width to be calculated
204 * automatically.
205 *
206 * @param candleWidth The candle width.
207 */
208 public CandlestickRenderer(double candleWidth) {
209 this(candleWidth, true, new HighLowItemLabelGenerator());
210 }
211
212 /**
213 * Creates a new renderer for candlestick charts.
214 * <P>
215 * Use -1 for the candle width if you prefer the width to be calculated
216 * automatically.
217 *
218 * @param candleWidth the candle width.
219 * @param drawVolume a flag indicating whether or not volume bars should
220 * be drawn.
221 * @param toolTipGenerator the tool tip generator. <code>null</code> is
222 * none.
223 */
224 public CandlestickRenderer(double candleWidth, boolean drawVolume,
225 XYToolTipGenerator toolTipGenerator) {
226 super();
227 setToolTipGenerator(toolTipGenerator);
228 this.candleWidth = candleWidth;
229 this.drawVolume = drawVolume;
230 this.upPaint = Color.green;
231 this.downPaint = Color.red;
232 this.useOutlinePaint = false; // false preserves the old behaviour
233 // prior to introducing this flag
234 }
235
236 /**
237 * Returns the width of each candle.
238 *
239 * @return The candle width.
240 *
241 * @see #setCandleWidth(double)
242 */
243 public double getCandleWidth() {
244 return this.candleWidth;
245 }
246
247 /**
248 * Sets the candle width.
249 * <P>
250 * If you set the width to a negative value, the renderer will calculate
251 * the candle width automatically based on the space available on the chart.
252 *
253 * @param width The width.
254 * @see #setAutoWidthMethod(int)
255 * @see #setAutoWidthGap(double)
256 * @see #setAutoWidthFactor(double)
257 * @see #setMaxCandleWidthInMilliseconds(double)
258 */
259 public void setCandleWidth(double width) {
260 if (width != this.candleWidth) {
261 this.candleWidth = width;
262 notifyListeners(new RendererChangeEvent(this));
263 }
264 }
265
266 /**
267 * Returns the maximum width (in milliseconds) of each candle.
268 *
269 * @return The maximum candle width in milliseconds.
270 *
271 * @see #setMaxCandleWidthInMilliseconds(double)
272 */
273 public double getMaxCandleWidthInMilliseconds() {
274 return this.maxCandleWidthInMilliseconds;
275 }
276
277 /**
278 * Sets the maximum candle width (in milliseconds).
279 *
280 * @param millis The maximum width.
281 *
282 * @see #getMaxCandleWidthInMilliseconds()
283 * @see #setCandleWidth(double)
284 * @see #setAutoWidthMethod(int)
285 * @see #setAutoWidthGap(double)
286 * @see #setAutoWidthFactor(double)
287 */
288 public void setMaxCandleWidthInMilliseconds(double millis) {
289 this.maxCandleWidthInMilliseconds = millis;
290 notifyListeners(new RendererChangeEvent(this));
291 }
292
293 /**
294 * Returns the method of automatically calculating the candle width.
295 *
296 * @return The method of automatically calculating the candle width.
297 *
298 * @see #setAutoWidthMethod(int)
299 */
300 public int getAutoWidthMethod() {
301 return this.autoWidthMethod;
302 }
303
304 /**
305 * Sets the method of automatically calculating the candle width.
306 * <p>
307 * <code>WIDTHMETHOD_AVERAGE</code>: Divides the entire display (ignoring
308 * scale factor) by the number of items, and uses this as the available
309 * width.<br>
310 * <code>WIDTHMETHOD_SMALLEST</code>: Checks the interval between each
311 * item, and uses the smallest as the available width.<br>
312 * <code>WIDTHMETHOD_INTERVALDATA</code>: Assumes that the dataset supports
313 * the IntervalXYDataset interface, and uses the startXValue - endXValue as
314 * the available width.
315 * <br>
316 *
317 * @param autoWidthMethod The method of automatically calculating the
318 * candle width.
319 *
320 * @see #WIDTHMETHOD_AVERAGE
321 * @see #WIDTHMETHOD_SMALLEST
322 * @see #WIDTHMETHOD_INTERVALDATA
323 * @see #getAutoWidthMethod()
324 * @see #setCandleWidth(double)
325 * @see #setAutoWidthGap(double)
326 * @see #setAutoWidthFactor(double)
327 * @see #setMaxCandleWidthInMilliseconds(double)
328 */
329 public void setAutoWidthMethod(int autoWidthMethod) {
330 if (this.autoWidthMethod != autoWidthMethod) {
331 this.autoWidthMethod = autoWidthMethod;
332 notifyListeners(new RendererChangeEvent(this));
333 }
334 }
335
336 /**
337 * Returns the factor by which the available space automatically
338 * calculated for the candles will be multiplied to determine the actual
339 * width to use.
340 *
341 * @return The width factor (generally between 0.0 and 1.0).
342 *
343 * @see #setAutoWidthFactor(double)
344 */
345 public double getAutoWidthFactor() {
346 return this.autoWidthFactor;
347 }
348
349 /**
350 * Sets the factor by which the available space automatically calculated
351 * for the candles will be multiplied to determine the actual width to use.
352 *
353 * @param autoWidthFactor The width factor (generally between 0.0 and 1.0).
354 *
355 * @see #getAutoWidthFactor()
356 * @see #setCandleWidth(double)
357 * @see #setAutoWidthMethod(int)
358 * @see #setAutoWidthGap(double)
359 * @see #setMaxCandleWidthInMilliseconds(double)
360 */
361 public void setAutoWidthFactor(double autoWidthFactor) {
362 if (this.autoWidthFactor != autoWidthFactor) {
363 this.autoWidthFactor = autoWidthFactor;
364 notifyListeners(new RendererChangeEvent(this));
365 }
366 }
367
368 /**
369 * Returns the amount of space to leave on the left and right of each
370 * candle when automatically calculating widths.
371 *
372 * @return The gap.
373 *
374 * @see #setAutoWidthGap(double)
375 */
376 public double getAutoWidthGap() {
377 return this.autoWidthGap;
378 }
379
380 /**
381 * Sets the amount of space to leave on the left and right of each candle
382 * when automatically calculating widths.
383 *
384 * @param autoWidthGap The gap.
385 *
386 * @see #getAutoWidthGap()
387 * @see #setCandleWidth(double)
388 * @see #setAutoWidthMethod(int)
389 * @see #setAutoWidthFactor(double)
390 * @see #setMaxCandleWidthInMilliseconds(double)
391 */
392 public void setAutoWidthGap(double autoWidthGap) {
393 if (this.autoWidthGap != autoWidthGap) {
394 this.autoWidthGap = autoWidthGap;
395 notifyListeners(new RendererChangeEvent(this));
396 }
397 }
398
399 /**
400 * Returns the paint used to fill candles when the price moves up from open
401 * to close.
402 *
403 * @return The paint (possibly <code>null</code>).
404 *
405 * @see #setUpPaint(Paint)
406 */
407 public Paint getUpPaint() {
408 return this.upPaint;
409 }
410
411 /**
412 * Sets the paint used to fill candles when the price moves up from open
413 * to close and sends a {@link RendererChangeEvent} to all registered
414 * listeners.
415 *
416 * @param paint the paint (<code>null</code> permitted).
417 *
418 * @see #getUpPaint()
419 */
420 public void setUpPaint(Paint paint) {
421 this.upPaint = paint;
422 notifyListeners(new RendererChangeEvent(this));
423 }
424
425 /**
426 * Returns the paint used to fill candles when the price moves down from
427 * open to close.
428 *
429 * @return The paint (possibly <code>null</code>).
430 *
431 * @see #setDownPaint(Paint)
432 */
433 public Paint getDownPaint() {
434 return this.downPaint;
435 }
436
437 /**
438 * Sets the paint used to fill candles when the price moves down from open
439 * to close and sends a {@link RendererChangeEvent} to all registered
440 * listeners.
441 *
442 * @param paint The paint (<code>null</code> permitted).
443 */
444 public void setDownPaint(Paint paint) {
445 this.downPaint = paint;
446 notifyListeners(new RendererChangeEvent(this));
447 }
448
449 /**
450 * Returns a flag indicating whether or not volume bars are drawn on the
451 * chart.
452 *
453 * @return <code>true</code> if volume bars are drawn on the chart.
454 *
455 * @deprecated As of 1.0.5, you should use the {@link #getDrawVolume()}
456 * method.
457 */
458 public boolean drawVolume() {
459 return this.drawVolume;
460 }
461
462 /**
463 * Returns a flag indicating whether or not volume bars are drawn on the
464 * chart.
465 *
466 * @return A boolean.
467 *
468 * @since 1.0.5
469 *
470 * @see #setDrawVolume(boolean)
471 */
472 public boolean getDrawVolume() {
473 return this.drawVolume;
474 }
475
476 /**
477 * Sets a flag that controls whether or not volume bars are drawn in the
478 * background and sends a {@link RendererChangeEvent} to all registered
479 * listeners.
480 *
481 * @param flag the flag.
482 *
483 * @see #getDrawVolume()
484 */
485 public void setDrawVolume(boolean flag) {
486 if (this.drawVolume != flag) {
487 this.drawVolume = flag;
488 notifyListeners(new RendererChangeEvent(this));
489 }
490 }
491
492 /**
493 * Returns the flag that controls whether or not the renderer's outline
494 * paint is used to draw the candlestick outline. The default value is
495 * <code>false</code>.
496 *
497 * @return A boolean.
498 *
499 * @since 1.0.5
500 *
501 * @see #setUseOutlinePaint(boolean)
502 */
503 public boolean getUseOutlinePaint() {
504 return this.useOutlinePaint;
505 }
506
507 /**
508 * Sets the flag that controls whether or not the renderer's outline
509 * paint is used to draw the candlestick outline, and sends a
510 * {@link RendererChangeEvent} to all registered listeners.
511 *
512 * @param use the new flag value.
513 *
514 * @since 1.0.5
515 *
516 * @see #getUseOutlinePaint()
517 */
518 public void setUseOutlinePaint(boolean use) {
519 if (this.useOutlinePaint != use) {
520 this.useOutlinePaint = use;
521 fireChangeEvent();
522 }
523 }
524
525 /**
526 * Initialises the renderer then returns the number of 'passes' through the
527 * data that the renderer will require (usually just one). This method
528 * will be called before the first item is rendered, giving the renderer
529 * an opportunity to initialise any state information it wants to maintain.
530 * The renderer can do nothing if it chooses.
531 *
532 * @param g2 the graphics device.
533 * @param dataArea the area inside the axes.
534 * @param plot the plot.
535 * @param dataset the data.
536 * @param info an optional info collection object to return data back to
537 * the caller.
538 *
539 * @return The number of passes the renderer requires.
540 */
541 public XYItemRendererState initialise(Graphics2D g2,
542 Rectangle2D dataArea,
543 XYPlot plot,
544 XYDataset dataset,
545 PlotRenderingInfo info) {
546
547 // calculate the maximum allowed candle width from the axis...
548 ValueAxis axis = plot.getDomainAxis();
549 double x1 = axis.getLowerBound();
550 double x2 = x1 + this.maxCandleWidthInMilliseconds;
551 RectangleEdge edge = plot.getDomainAxisEdge();
552 double xx1 = axis.valueToJava2D(x1, dataArea, edge);
553 double xx2 = axis.valueToJava2D(x2, dataArea, edge);
554 this.maxCandleWidth = Math.abs(xx2 - xx1);
555 // Absolute value, since the relative x
556 // positions are reversed for horizontal orientation
557
558 // calculate the highest volume in the dataset...
559 if (this.drawVolume) {
560 OHLCDataset highLowDataset = (OHLCDataset) dataset;
561 this.maxVolume = 0.0;
562 for (int series = 0; series < highLowDataset.getSeriesCount();
563 series++) {
564 for (int item = 0; item < highLowDataset.getItemCount(series);
565 item++) {
566 double volume = highLowDataset.getVolumeValue(series, item);
567 if (volume > this.maxVolume) {
568 this.maxVolume = volume;
569 }
570
571 }
572 }
573 }
574
575 return new XYItemRendererState(info);
576 }
577
578 /**
579 * Draws the visual representation of a single data item.
580 *
581 * @param g2 the graphics device.
582 * @param state the renderer state.
583 * @param dataArea the area within which the plot is being drawn.
584 * @param info collects info about the drawing.
585 * @param plot the plot (can be used to obtain standard color
586 * information etc).
587 * @param domainAxis the domain axis.
588 * @param rangeAxis the range axis.
589 * @param dataset the dataset.
590 * @param series the series index (zero-based).
591 * @param item the item index (zero-based).
592 * @param crosshairState crosshair information for the plot
593 * (<code>null</code> permitted).
594 * @param pass the pass index.
595 */
596 public void drawItem(Graphics2D g2,
597 XYItemRendererState state,
598 Rectangle2D dataArea,
599 PlotRenderingInfo info,
600 XYPlot plot,
601 ValueAxis domainAxis,
602 ValueAxis rangeAxis,
603 XYDataset dataset,
604 int series,
605 int item,
606 CrosshairState crosshairState,
607 int pass) {
608
609 boolean horiz;
610 PlotOrientation orientation = plot.getOrientation();
611 if (orientation == PlotOrientation.HORIZONTAL) {
612 horiz = true;
613 }
614 else if (orientation == PlotOrientation.VERTICAL) {
615 horiz = false;
616 }
617 else {
618 return;
619 }
620
621 // setup for collecting optional entity info...
622 EntityCollection entities = null;
623 if (info != null) {
624 entities = info.getOwner().getEntityCollection();
625 }
626
627 OHLCDataset highLowData = (OHLCDataset) dataset;
628
629 double x = highLowData.getXValue(series, item);
630 double yHigh = highLowData.getHighValue(series, item);
631 double yLow = highLowData.getLowValue(series, item);
632 double yOpen = highLowData.getOpenValue(series, item);
633 double yClose = highLowData.getCloseValue(series, item);
634
635 RectangleEdge domainEdge = plot.getDomainAxisEdge();
636 double xx = domainAxis.valueToJava2D(x, dataArea, domainEdge);
637
638 RectangleEdge edge = plot.getRangeAxisEdge();
639 double yyHigh = rangeAxis.valueToJava2D(yHigh, dataArea, edge);
640 double yyLow = rangeAxis.valueToJava2D(yLow, dataArea, edge);
641 double yyOpen = rangeAxis.valueToJava2D(yOpen, dataArea, edge);
642 double yyClose = rangeAxis.valueToJava2D(yClose, dataArea, edge);
643
644 double volumeWidth;
645 double stickWidth;
646 if (this.candleWidth > 0) {
647 // These are deliberately not bounded to minimums/maxCandleWidth to
648 // retain old behaviour.
649 volumeWidth = this.candleWidth;
650 stickWidth = this.candleWidth;
651 }
652 else {
653 double xxWidth = 0;
654 int itemCount;
655 switch (this.autoWidthMethod) {
656
657 case WIDTHMETHOD_AVERAGE:
658 itemCount = highLowData.getItemCount(series);
659 if (horiz) {
660 xxWidth = dataArea.getHeight() / itemCount;
661 }
662 else {
663 xxWidth = dataArea.getWidth() / itemCount;
664 }
665 break;
666
667 case WIDTHMETHOD_SMALLEST:
668 // Note: It would be nice to pre-calculate this per series
669 itemCount = highLowData.getItemCount(series);
670 double lastPos = -1;
671 xxWidth = dataArea.getWidth();
672 for (int i = 0; i < itemCount; i++) {
673 double pos = domainAxis.valueToJava2D(
674 highLowData.getXValue(series, i), dataArea,
675 domainEdge);
676 if (lastPos != -1) {
677 xxWidth = Math.min(xxWidth,
678 Math.abs(pos - lastPos));
679 }
680 lastPos = pos;
681 }
682 break;
683
684 case WIDTHMETHOD_INTERVALDATA:
685 IntervalXYDataset intervalXYData
686 = (IntervalXYDataset) dataset;
687 double startPos = domainAxis.valueToJava2D(
688 intervalXYData.getStartXValue(series, item),
689 dataArea, plot.getDomainAxisEdge());
690 double endPos = domainAxis.valueToJava2D(
691 intervalXYData.getEndXValue(series, item),
692 dataArea, plot.getDomainAxisEdge());
693 xxWidth = Math.abs(endPos - startPos);
694 break;
695
696 }
697 xxWidth -= 2 * this.autoWidthGap;
698 xxWidth *= this.autoWidthFactor;
699 xxWidth = Math.min(xxWidth, this.maxCandleWidth);
700 volumeWidth = Math.max(Math.min(1, this.maxCandleWidth), xxWidth);
701 stickWidth = Math.max(Math.min(3, this.maxCandleWidth), xxWidth);
702 }
703
704 Paint p = getItemPaint(series, item);
705 Paint outlinePaint = null;
706 if (this.useOutlinePaint) {
707 outlinePaint = getItemOutlinePaint(series, item);
708 }
709 Stroke s = getItemStroke(series, item);
710
711 g2.setStroke(s);
712
713 if (this.drawVolume) {
714 int volume = (int) highLowData.getVolumeValue(series, item);
715 double volumeHeight = volume / this.maxVolume;
716
717 double min, max;
718 if (horiz) {
719 min = dataArea.getMinX();
720 max = dataArea.getMaxX();
721 }
722 else {
723 min = dataArea.getMinY();
724 max = dataArea.getMaxY();
725 }
726
727 double zzVolume = volumeHeight * (max - min);
728
729 g2.setPaint(Color.gray);
730 Composite originalComposite = g2.getComposite();
731 g2.setComposite(
732 AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.3f)
733 );
734
735 if (horiz) {
736 g2.fill(new Rectangle2D.Double(min, xx - volumeWidth / 2,
737 zzVolume, volumeWidth));
738 }
739 else {
740 g2.fill(new Rectangle2D.Double(xx - volumeWidth / 2,
741 max - zzVolume, volumeWidth, zzVolume));
742 }
743
744 g2.setComposite(originalComposite);
745 }
746
747 if (this.useOutlinePaint) {
748 g2.setPaint(outlinePaint);
749 }
750 else {
751 g2.setPaint(p);
752 }
753
754 double yyMaxOpenClose = Math.max(yyOpen, yyClose);
755 double yyMinOpenClose = Math.min(yyOpen, yyClose);
756 double maxOpenClose = Math.max(yOpen, yClose);
757 double minOpenClose = Math.min(yOpen, yClose);
758
759 // draw the upper shadow
760 if (yHigh > maxOpenClose) {
761 if (horiz) {
762 g2.draw(new Line2D.Double(yyHigh, xx, yyMaxOpenClose, xx));
763 }
764 else {
765 g2.draw(new Line2D.Double(xx, yyHigh, xx, yyMaxOpenClose));
766 }
767 }
768
769 // draw the lower shadow
770 if (yLow < minOpenClose) {
771 if (horiz) {
772 g2.draw(new Line2D.Double(yyLow, xx, yyMinOpenClose, xx));
773 }
774 else {
775 g2.draw(new Line2D.Double(xx, yyLow, xx, yyMinOpenClose));
776 }
777 }
778
779 // draw the body
780 Shape body = null;
781 if (horiz) {
782 body = new Rectangle2D.Double(yyMinOpenClose, xx - stickWidth / 2,
783 yyMaxOpenClose - yyMinOpenClose, stickWidth);
784 }
785 else {
786 body = new Rectangle2D.Double(xx - stickWidth / 2, yyMinOpenClose,
787 stickWidth, yyMaxOpenClose - yyMinOpenClose);
788 }
789 if (yClose > yOpen) {
790 if (this.upPaint != null) {
791 g2.setPaint(this.upPaint);
792 }
793 else {
794 g2.setPaint(p);
795 }
796 g2.fill(body);
797 }
798 else {
799 if (this.downPaint != null) {
800 g2.setPaint(this.downPaint);
801 }
802 else {
803 g2.setPaint(p);
804 }
805 g2.fill(body);
806 }
807 if (this.useOutlinePaint) {
808 g2.setPaint(outlinePaint);
809 }
810 else {
811 g2.setPaint(p);
812 }
813 g2.draw(body);
814
815 // add an entity for the item...
816 if (entities != null) {
817 String tip = null;
818 XYToolTipGenerator generator = getToolTipGenerator(series, item);
819 if (generator != null) {
820 tip = generator.generateToolTip(dataset, series, item);
821 }
822 String url = null;
823 if (getURLGenerator() != null) {
824 url = getURLGenerator().generateURL(dataset, series, item);
825 }
826 XYItemEntity entity = new XYItemEntity(body, dataset, series, item,
827 tip, url);
828 entities.add(entity);
829 }
830
831 }
832
833 /**
834 * Tests this renderer for equality with another object.
835 *
836 * @param obj the object (<code>null</code> permitted).
837 *
838 * @return <code>true</code> or <code>false</code>.
839 */
840 public boolean equals(Object obj) {
841 if (obj == this) {
842 return true;
843 }
844 if (!(obj instanceof CandlestickRenderer)) {
845 return false;
846 }
847 CandlestickRenderer that = (CandlestickRenderer) obj;
848 if (this.candleWidth != that.candleWidth) {
849 return false;
850 }
851 if (!PaintUtilities.equal(this.upPaint, that.upPaint)) {
852 return false;
853 }
854 if (!PaintUtilities.equal(this.downPaint, that.downPaint)) {
855 return false;
856 }
857 if (this.drawVolume != that.drawVolume) {
858 return false;
859 }
860 if (this.maxCandleWidthInMilliseconds
861 != that.maxCandleWidthInMilliseconds) {
862 return false;
863 }
864 if (this.autoWidthMethod != that.autoWidthMethod) {
865 return false;
866 }
867 if (this.autoWidthFactor != that.autoWidthFactor) {
868 return false;
869 }
870 if (this.autoWidthGap != that.autoWidthGap) {
871 return false;
872 }
873 if (this.useOutlinePaint != that.useOutlinePaint) {
874 return false;
875 }
876 return super.equals(obj);
877 }
878
879 /**
880 * Returns a clone of the renderer.
881 *
882 * @return A clone.
883 *
884 * @throws CloneNotSupportedException if the renderer cannot be cloned.
885 */
886 public Object clone() throws CloneNotSupportedException {
887 return super.clone();
888 }
889
890 /**
891 * Provides serialization support.
892 *
893 * @param stream the output stream.
894 *
895 * @throws IOException if there is an I/O error.
896 */
897 private void writeObject(ObjectOutputStream stream) throws IOException {
898 stream.defaultWriteObject();
899 SerialUtilities.writePaint(this.upPaint, stream);
900 SerialUtilities.writePaint(this.downPaint, stream);
901 }
902
903 /**
904 * Provides serialization support.
905 *
906 * @param stream the input stream.
907 *
908 * @throws IOException if there is an I/O error.
909 * @throws ClassNotFoundException if there is a classpath problem.
910 */
911 private void readObject(ObjectInputStream stream)
912 throws IOException, ClassNotFoundException {
913 stream.defaultReadObject();
914 this.upPaint = SerialUtilities.readPaint(stream);
915 this.downPaint = SerialUtilities.readPaint(stream);
916 }
917
918 }