001 /* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2008, 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 * HighLowRenderer.java
029 * --------------------
030 * (C) Copyright 2001-2008, by Object Refinery Limited.
031 *
032 * Original Author: David Gilbert (for Object Refinery Limited);
033 * Contributor(s): Richard Atkinson;
034 * Christian W. Zuckschwerdt;
035 *
036 * Changes
037 * -------
038 * 13-Dec-2001 : Version 1 (DG);
039 * 23-Jan-2002 : Added DrawInfo parameter to drawItem() method (DG);
040 * 28-Mar-2002 : Added a property change listener mechanism so that renderers
041 * no longer need to be immutable (DG);
042 * 09-Apr-2002 : Removed translatedRangeZero from the drawItem() method, and
043 * changed the return type of the drawItem method to void,
044 * reflecting a change in the XYItemRenderer interface. Added
045 * tooltip code to drawItem() method (DG);
046 * 05-Aug-2002 : Small modification to drawItem method to support URLs for
047 * HTML image maps (RA);
048 * 25-Mar-2003 : Implemented Serializable (DG);
049 * 01-May-2003 : Modified drawItem() method signature (DG);
050 * 30-Jul-2003 : Modified entity constructor (CZ);
051 * 31-Jul-2003 : Deprecated constructor (DG);
052 * 20-Aug-2003 : Implemented Cloneable and PublicCloneable (DG);
053 * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG);
054 * 29-Jan-2004 : Fixed bug (882392) when rendering with
055 * PlotOrientation.HORIZONTAL (DG);
056 * 25-Feb-2004 : Replaced CrosshairInfo with CrosshairState. Renamed
057 * XYToolTipGenerator --> XYItemLabelGenerator (DG);
058 * 15-Jul-2004 : Switched getX() with getXValue() and getY() with
059 * getYValue() (DG);
060 * 01-Nov-2005 : Added optional openTickPaint and closeTickPaint settings (DG);
061 * ------------- JFREECHART 1.0.0 ---------------------------------------------
062 * 06-Jul-2006 : Replace dataset methods getX() --> getXValue() (DG);
063 * 08-Apr-2008 : Added findRangeBounds() override (DG);
064 * 29-Apr-2008 : Added tickLength field (DG);
065 *
066 */
067
068 package org.jfree.chart.renderer.xy;
069
070 import java.awt.Graphics2D;
071 import java.awt.Paint;
072 import java.awt.Shape;
073 import java.awt.Stroke;
074 import java.awt.geom.Line2D;
075 import java.awt.geom.Rectangle2D;
076 import java.io.IOException;
077 import java.io.ObjectInputStream;
078 import java.io.ObjectOutputStream;
079 import java.io.Serializable;
080
081 import org.jfree.chart.axis.ValueAxis;
082 import org.jfree.chart.entity.EntityCollection;
083 import org.jfree.chart.event.RendererChangeEvent;
084 import org.jfree.chart.plot.CrosshairState;
085 import org.jfree.chart.plot.PlotOrientation;
086 import org.jfree.chart.plot.PlotRenderingInfo;
087 import org.jfree.chart.plot.XYPlot;
088 import org.jfree.data.Range;
089 import org.jfree.data.general.DatasetUtilities;
090 import org.jfree.data.xy.OHLCDataset;
091 import org.jfree.data.xy.XYDataset;
092 import org.jfree.io.SerialUtilities;
093 import org.jfree.ui.RectangleEdge;
094 import org.jfree.util.PaintUtilities;
095 import org.jfree.util.PublicCloneable;
096
097 /**
098 * A renderer that draws high/low/open/close markers on an {@link XYPlot}
099 * (requires a {@link OHLCDataset}). This renderer does not include code to
100 * calculate the crosshair point for the plot.
101 */
102 public class HighLowRenderer extends AbstractXYItemRenderer
103 implements XYItemRenderer, Cloneable, PublicCloneable, Serializable {
104
105 /** For serialization. */
106 private static final long serialVersionUID = -8135673815876552516L;
107
108 /** A flag that controls whether the open ticks are drawn. */
109 private boolean drawOpenTicks;
110
111 /** A flag that controls whether the close ticks are drawn. */
112 private boolean drawCloseTicks;
113
114 /**
115 * The paint used for the open ticks (if <code>null</code>, the series
116 * paint is used instead).
117 */
118 private transient Paint openTickPaint;
119
120 /**
121 * The paint used for the close ticks (if <code>null</code>, the series
122 * paint is used instead).
123 */
124 private transient Paint closeTickPaint;
125
126 /**
127 * The tick length (in Java2D units).
128 *
129 * @since 1.0.10
130 */
131 private double tickLength;
132
133 /**
134 * The default constructor.
135 */
136 public HighLowRenderer() {
137 super();
138 this.drawOpenTicks = true;
139 this.drawCloseTicks = true;
140 this.tickLength = 2.0;
141 }
142
143 /**
144 * Returns the flag that controls whether open ticks are drawn.
145 *
146 * @return A boolean.
147 *
148 * @see #getDrawCloseTicks()
149 * @see #setDrawOpenTicks(boolean)
150 */
151 public boolean getDrawOpenTicks() {
152 return this.drawOpenTicks;
153 }
154
155 /**
156 * Sets the flag that controls whether open ticks are drawn, and sends a
157 * {@link RendererChangeEvent} to all registered listeners.
158 *
159 * @param draw the flag.
160 *
161 * @see #getDrawOpenTicks()
162 */
163 public void setDrawOpenTicks(boolean draw) {
164 this.drawOpenTicks = draw;
165 fireChangeEvent();
166 }
167
168 /**
169 * Returns the flag that controls whether close ticks are drawn.
170 *
171 * @return A boolean.
172 *
173 * @see #getDrawOpenTicks()
174 * @see #setDrawCloseTicks(boolean)
175 */
176 public boolean getDrawCloseTicks() {
177 return this.drawCloseTicks;
178 }
179
180 /**
181 * Sets the flag that controls whether close ticks are drawn, and sends a
182 * {@link RendererChangeEvent} to all registered listeners.
183 *
184 * @param draw the flag.
185 *
186 * @see #getDrawCloseTicks()
187 */
188 public void setDrawCloseTicks(boolean draw) {
189 this.drawCloseTicks = draw;
190 fireChangeEvent();
191 }
192
193 /**
194 * Returns the paint used to draw the ticks for the open values.
195 *
196 * @return The paint used to draw the ticks for the open values (possibly
197 * <code>null</code>).
198 *
199 * @see #setOpenTickPaint(Paint)
200 */
201 public Paint getOpenTickPaint() {
202 return this.openTickPaint;
203 }
204
205 /**
206 * Sets the paint used to draw the ticks for the open values and sends a
207 * {@link RendererChangeEvent} to all registered listeners. If you set
208 * this to <code>null</code> (the default), the series paint is used
209 * instead.
210 *
211 * @param paint the paint (<code>null</code> permitted).
212 *
213 * @see #getOpenTickPaint()
214 */
215 public void setOpenTickPaint(Paint paint) {
216 this.openTickPaint = paint;
217 fireChangeEvent();
218 }
219
220 /**
221 * Returns the paint used to draw the ticks for the close values.
222 *
223 * @return The paint used to draw the ticks for the close values (possibly
224 * <code>null</code>).
225 *
226 * @see #setCloseTickPaint(Paint)
227 */
228 public Paint getCloseTickPaint() {
229 return this.closeTickPaint;
230 }
231
232 /**
233 * Sets the paint used to draw the ticks for the close values and sends a
234 * {@link RendererChangeEvent} to all registered listeners. If you set
235 * this to <code>null</code> (the default), the series paint is used
236 * instead.
237 *
238 * @param paint the paint (<code>null</code> permitted).
239 *
240 * @see #getCloseTickPaint()
241 */
242 public void setCloseTickPaint(Paint paint) {
243 this.closeTickPaint = paint;
244 fireChangeEvent();
245 }
246
247 /**
248 * Returns the tick length (in Java2D units).
249 *
250 * @return The tick length.
251 *
252 * @since 1.0.10
253 *
254 * @see #setTickLength(double)
255 */
256 public double getTickLength() {
257 return this.tickLength;
258 }
259
260 /**
261 * Sets the tick length (in Java2D units) and sends a
262 * {@link RendererChangeEvent} to all registered listeners.
263 *
264 * @param length the length.
265 *
266 * @since 1.0.10
267 *
268 * @see #getTickLength()
269 */
270 public void setTickLength(double length) {
271 this.tickLength = length;
272 fireChangeEvent();
273 }
274
275 /**
276 * Returns the range of values the renderer requires to display all the
277 * items from the specified dataset.
278 *
279 * @param dataset the dataset (<code>null</code> permitted).
280 *
281 * @return The range (<code>null</code> if the dataset is <code>null</code>
282 * or empty).
283 */
284 public Range findRangeBounds(XYDataset dataset) {
285 if (dataset != null) {
286 return DatasetUtilities.findRangeBounds(dataset, true);
287 }
288 else {
289 return null;
290 }
291 }
292
293 /**
294 * Draws the visual representation of a single data item.
295 *
296 * @param g2 the graphics device.
297 * @param state the renderer state.
298 * @param dataArea the area within which the plot is being drawn.
299 * @param info collects information about the drawing.
300 * @param plot the plot (can be used to obtain standard color
301 * information etc).
302 * @param domainAxis the domain axis.
303 * @param rangeAxis the range axis.
304 * @param dataset the dataset.
305 * @param series the series index (zero-based).
306 * @param item the item index (zero-based).
307 * @param crosshairState crosshair information for the plot
308 * (<code>null</code> permitted).
309 * @param pass the pass index.
310 */
311 public void drawItem(Graphics2D g2,
312 XYItemRendererState state,
313 Rectangle2D dataArea,
314 PlotRenderingInfo info,
315 XYPlot plot,
316 ValueAxis domainAxis,
317 ValueAxis rangeAxis,
318 XYDataset dataset,
319 int series,
320 int item,
321 CrosshairState crosshairState,
322 int pass) {
323
324 double x = dataset.getXValue(series, item);
325 if (!domainAxis.getRange().contains(x)) {
326 return; // the x value is not within the axis range
327 }
328 double xx = domainAxis.valueToJava2D(x, dataArea,
329 plot.getDomainAxisEdge());
330
331 // setup for collecting optional entity info...
332 Shape entityArea = null;
333 EntityCollection entities = null;
334 if (info != null) {
335 entities = info.getOwner().getEntityCollection();
336 }
337
338 PlotOrientation orientation = plot.getOrientation();
339 RectangleEdge location = plot.getRangeAxisEdge();
340
341 Paint itemPaint = getItemPaint(series, item);
342 Stroke itemStroke = getItemStroke(series, item);
343 g2.setPaint(itemPaint);
344 g2.setStroke(itemStroke);
345
346 if (dataset instanceof OHLCDataset) {
347 OHLCDataset hld = (OHLCDataset) dataset;
348
349 double yHigh = hld.getHighValue(series, item);
350 double yLow = hld.getLowValue(series, item);
351 if (!Double.isNaN(yHigh) && !Double.isNaN(yLow)) {
352 double yyHigh = rangeAxis.valueToJava2D(yHigh, dataArea,
353 location);
354 double yyLow = rangeAxis.valueToJava2D(yLow, dataArea,
355 location);
356 if (orientation == PlotOrientation.HORIZONTAL) {
357 g2.draw(new Line2D.Double(yyLow, xx, yyHigh, xx));
358 entityArea = new Rectangle2D.Double(Math.min(yyLow, yyHigh),
359 xx - 1.0, Math.abs(yyHigh - yyLow), 2.0);
360 }
361 else if (orientation == PlotOrientation.VERTICAL) {
362 g2.draw(new Line2D.Double(xx, yyLow, xx, yyHigh));
363 entityArea = new Rectangle2D.Double(xx - 1.0,
364 Math.min(yyLow, yyHigh), 2.0,
365 Math.abs(yyHigh - yyLow));
366 }
367 }
368
369 double delta = getTickLength();
370 if (domainAxis.isInverted()) {
371 delta = -delta;
372 }
373 if (getDrawOpenTicks()) {
374 double yOpen = hld.getOpenValue(series, item);
375 if (!Double.isNaN(yOpen)) {
376 double yyOpen = rangeAxis.valueToJava2D(yOpen, dataArea,
377 location);
378 if (this.openTickPaint != null) {
379 g2.setPaint(this.openTickPaint);
380 }
381 else {
382 g2.setPaint(itemPaint);
383 }
384 if (orientation == PlotOrientation.HORIZONTAL) {
385 g2.draw(new Line2D.Double(yyOpen, xx + delta, yyOpen,
386 xx));
387 }
388 else if (orientation == PlotOrientation.VERTICAL) {
389 g2.draw(new Line2D.Double(xx - delta, yyOpen, xx,
390 yyOpen));
391 }
392 }
393 }
394
395 if (getDrawCloseTicks()) {
396 double yClose = hld.getCloseValue(series, item);
397 if (!Double.isNaN(yClose)) {
398 double yyClose = rangeAxis.valueToJava2D(
399 yClose, dataArea, location);
400 if (this.closeTickPaint != null) {
401 g2.setPaint(this.closeTickPaint);
402 }
403 else {
404 g2.setPaint(itemPaint);
405 }
406 if (orientation == PlotOrientation.HORIZONTAL) {
407 g2.draw(new Line2D.Double(yyClose, xx, yyClose,
408 xx - delta));
409 }
410 else if (orientation == PlotOrientation.VERTICAL) {
411 g2.draw(new Line2D.Double(xx, yyClose, xx + delta,
412 yyClose));
413 }
414 }
415 }
416
417 }
418 else {
419 // not a HighLowDataset, so just draw a line connecting this point
420 // with the previous point...
421 if (item > 0) {
422 double x0 = dataset.getXValue(series, item - 1);
423 double y0 = dataset.getYValue(series, item - 1);
424 double y = dataset.getYValue(series, item);
425 if (Double.isNaN(x0) || Double.isNaN(y0) || Double.isNaN(y)) {
426 return;
427 }
428 double xx0 = domainAxis.valueToJava2D(x0, dataArea,
429 plot.getDomainAxisEdge());
430 double yy0 = rangeAxis.valueToJava2D(y0, dataArea, location);
431 double yy = rangeAxis.valueToJava2D(y, dataArea, location);
432 if (orientation == PlotOrientation.HORIZONTAL) {
433 g2.draw(new Line2D.Double(yy0, xx0, yy, xx));
434 }
435 else if (orientation == PlotOrientation.VERTICAL) {
436 g2.draw(new Line2D.Double(xx0, yy0, xx, yy));
437 }
438 }
439 }
440
441 addEntity(entities, entityArea, dataset, series, item, 0.0, 0.0);
442
443 }
444
445 /**
446 * Returns a clone of the renderer.
447 *
448 * @return A clone.
449 *
450 * @throws CloneNotSupportedException if the renderer cannot be cloned.
451 */
452 public Object clone() throws CloneNotSupportedException {
453 return super.clone();
454 }
455
456 /**
457 * Tests this renderer for equality with an arbitrary object.
458 *
459 * @param obj the object (<code>null</code> permitted).
460 *
461 * @return A boolean.
462 */
463 public boolean equals(Object obj) {
464 if (this == obj) {
465 return true;
466 }
467 if (!(obj instanceof HighLowRenderer)) {
468 return false;
469 }
470 HighLowRenderer that = (HighLowRenderer) obj;
471 if (this.drawOpenTicks != that.drawOpenTicks) {
472 return false;
473 }
474 if (this.drawCloseTicks != that.drawCloseTicks) {
475 return false;
476 }
477 if (!PaintUtilities.equal(this.openTickPaint, that.openTickPaint)) {
478 return false;
479 }
480 if (!PaintUtilities.equal(this.closeTickPaint, that.closeTickPaint)) {
481 return false;
482 }
483 if (this.tickLength != that.tickLength) {
484 return false;
485 }
486 if (!super.equals(obj)) {
487 return false;
488 }
489 return true;
490 }
491
492 /**
493 * Provides serialization support.
494 *
495 * @param stream the input stream.
496 *
497 * @throws IOException if there is an I/O error.
498 * @throws ClassNotFoundException if there is a classpath problem.
499 */
500 private void readObject(ObjectInputStream stream)
501 throws IOException, ClassNotFoundException {
502 stream.defaultReadObject();
503 this.openTickPaint = SerialUtilities.readPaint(stream);
504 this.closeTickPaint = SerialUtilities.readPaint(stream);
505 }
506
507 /**
508 * Provides serialization support.
509 *
510 * @param stream the output stream.
511 *
512 * @throws IOException if there is an I/O error.
513 */
514 private void writeObject(ObjectOutputStream stream) throws IOException {
515 stream.defaultWriteObject();
516 SerialUtilities.writePaint(this.openTickPaint, stream);
517 SerialUtilities.writePaint(this.closeTickPaint, stream);
518 }
519
520 }