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.entity.XYItemEntity;
084 import org.jfree.chart.event.RendererChangeEvent;
085 import org.jfree.chart.labels.XYToolTipGenerator;
086 import org.jfree.chart.plot.CrosshairState;
087 import org.jfree.chart.plot.PlotOrientation;
088 import org.jfree.chart.plot.PlotRenderingInfo;
089 import org.jfree.chart.plot.XYPlot;
090 import org.jfree.data.Range;
091 import org.jfree.data.general.DatasetUtilities;
092 import org.jfree.data.xy.OHLCDataset;
093 import org.jfree.data.xy.XYDataset;
094 import org.jfree.io.SerialUtilities;
095 import org.jfree.ui.RectangleEdge;
096 import org.jfree.util.PaintUtilities;
097 import org.jfree.util.PublicCloneable;
098
099 /**
100 * A renderer that draws high/low/open/close markers on an {@link XYPlot}
101 * (requires a {@link OHLCDataset}). This renderer does not include code to
102 * calculate the crosshair point for the plot.
103 */
104 public class HighLowRenderer extends AbstractXYItemRenderer
105 implements XYItemRenderer, Cloneable, PublicCloneable, Serializable {
106
107 /** For serialization. */
108 private static final long serialVersionUID = -8135673815876552516L;
109
110 /** A flag that controls whether the open ticks are drawn. */
111 private boolean drawOpenTicks;
112
113 /** A flag that controls whether the close ticks are drawn. */
114 private boolean drawCloseTicks;
115
116 /**
117 * The paint used for the open ticks (if <code>null</code>, the series
118 * paint is used instead).
119 */
120 private transient Paint openTickPaint;
121
122 /**
123 * The paint used for the close ticks (if <code>null</code>, the series
124 * paint is used instead).
125 */
126 private transient Paint closeTickPaint;
127
128 /**
129 * The tick length (in Java2D units).
130 *
131 * @since 1.0.10
132 */
133 private double tickLength;
134
135 /**
136 * The default constructor.
137 */
138 public HighLowRenderer() {
139 super();
140 this.drawOpenTicks = true;
141 this.drawCloseTicks = true;
142 this.tickLength = 2.0;
143 }
144
145 /**
146 * Returns the flag that controls whether open ticks are drawn.
147 *
148 * @return A boolean.
149 *
150 * @see #getDrawCloseTicks()
151 * @see #setDrawOpenTicks(boolean)
152 */
153 public boolean getDrawOpenTicks() {
154 return this.drawOpenTicks;
155 }
156
157 /**
158 * Sets the flag that controls whether open ticks are drawn, and sends a
159 * {@link RendererChangeEvent} to all registered listeners.
160 *
161 * @param draw the flag.
162 *
163 * @see #getDrawOpenTicks()
164 */
165 public void setDrawOpenTicks(boolean draw) {
166 this.drawOpenTicks = draw;
167 fireChangeEvent();
168 }
169
170 /**
171 * Returns the flag that controls whether close ticks are drawn.
172 *
173 * @return A boolean.
174 *
175 * @see #getDrawOpenTicks()
176 * @see #setDrawCloseTicks(boolean)
177 */
178 public boolean getDrawCloseTicks() {
179 return this.drawCloseTicks;
180 }
181
182 /**
183 * Sets the flag that controls whether close ticks are drawn, and sends a
184 * {@link RendererChangeEvent} to all registered listeners.
185 *
186 * @param draw the flag.
187 *
188 * @see #getDrawCloseTicks()
189 */
190 public void setDrawCloseTicks(boolean draw) {
191 this.drawCloseTicks = draw;
192 fireChangeEvent();
193 }
194
195 /**
196 * Returns the paint used to draw the ticks for the open values.
197 *
198 * @return The paint used to draw the ticks for the open values (possibly
199 * <code>null</code>).
200 *
201 * @see #setOpenTickPaint(Paint)
202 */
203 public Paint getOpenTickPaint() {
204 return this.openTickPaint;
205 }
206
207 /**
208 * Sets the paint used to draw the ticks for the open values and sends a
209 * {@link RendererChangeEvent} to all registered listeners. If you set
210 * this to <code>null</code> (the default), the series paint is used
211 * instead.
212 *
213 * @param paint the paint (<code>null</code> permitted).
214 *
215 * @see #getOpenTickPaint()
216 */
217 public void setOpenTickPaint(Paint paint) {
218 this.openTickPaint = paint;
219 fireChangeEvent();
220 }
221
222 /**
223 * Returns the paint used to draw the ticks for the close values.
224 *
225 * @return The paint used to draw the ticks for the close values (possibly
226 * <code>null</code>).
227 *
228 * @see #setCloseTickPaint(Paint)
229 */
230 public Paint getCloseTickPaint() {
231 return this.closeTickPaint;
232 }
233
234 /**
235 * Sets the paint used to draw the ticks for the close values and sends a
236 * {@link RendererChangeEvent} to all registered listeners. If you set
237 * this to <code>null</code> (the default), the series paint is used
238 * instead.
239 *
240 * @param paint the paint (<code>null</code> permitted).
241 *
242 * @see #getCloseTickPaint()
243 */
244 public void setCloseTickPaint(Paint paint) {
245 this.closeTickPaint = paint;
246 fireChangeEvent();
247 }
248
249 /**
250 * Returns the tick length (in Java2D units).
251 *
252 * @return The tick length.
253 *
254 * @since 1.0.10
255 *
256 * @see #setTickLength(double)
257 */
258 public double getTickLength() {
259 return this.tickLength;
260 }
261
262 /**
263 * Sets the tick length (in Java2D units) and sends a
264 * {@link RendererChangeEvent} to all registered listeners.
265 *
266 * @param length the length.
267 *
268 * @since 1.0.10
269 *
270 * @see #getTickLength()
271 */
272 public void setTickLength(double length) {
273 this.tickLength = length;
274 fireChangeEvent();
275 }
276
277 /**
278 * Returns the range of values the renderer requires to display all the
279 * items from the specified dataset.
280 *
281 * @param dataset the dataset (<code>null</code> permitted).
282 *
283 * @return The range (<code>null</code> if the dataset is <code>null</code>
284 * or empty).
285 */
286 public Range findRangeBounds(XYDataset dataset) {
287 if (dataset != null) {
288 return DatasetUtilities.findRangeBounds(dataset, true);
289 }
290 else {
291 return null;
292 }
293 }
294
295 /**
296 * Draws the visual representation of a single data item.
297 *
298 * @param g2 the graphics device.
299 * @param state the renderer state.
300 * @param dataArea the area within which the plot is being drawn.
301 * @param info collects information about the drawing.
302 * @param plot the plot (can be used to obtain standard color
303 * information etc).
304 * @param domainAxis the domain axis.
305 * @param rangeAxis the range axis.
306 * @param dataset the dataset.
307 * @param series the series index (zero-based).
308 * @param item the item index (zero-based).
309 * @param crosshairState crosshair information for the plot
310 * (<code>null</code> permitted).
311 * @param pass the pass index.
312 */
313 public void drawItem(Graphics2D g2,
314 XYItemRendererState state,
315 Rectangle2D dataArea,
316 PlotRenderingInfo info,
317 XYPlot plot,
318 ValueAxis domainAxis,
319 ValueAxis rangeAxis,
320 XYDataset dataset,
321 int series,
322 int item,
323 CrosshairState crosshairState,
324 int pass) {
325
326 double x = dataset.getXValue(series, item);
327 if (!domainAxis.getRange().contains(x)) {
328 return; // the x value is not within the axis range
329 }
330 double xx = domainAxis.valueToJava2D(x, dataArea,
331 plot.getDomainAxisEdge());
332
333 // setup for collecting optional entity info...
334 Shape entityArea = null;
335 EntityCollection entities = null;
336 if (info != null) {
337 entities = info.getOwner().getEntityCollection();
338 }
339
340 PlotOrientation orientation = plot.getOrientation();
341 RectangleEdge location = plot.getRangeAxisEdge();
342
343 Paint itemPaint = getItemPaint(series, item);
344 Stroke itemStroke = getItemStroke(series, item);
345 g2.setPaint(itemPaint);
346 g2.setStroke(itemStroke);
347
348 if (dataset instanceof OHLCDataset) {
349 OHLCDataset hld = (OHLCDataset) dataset;
350
351 double yHigh = hld.getHighValue(series, item);
352 double yLow = hld.getLowValue(series, item);
353 if (!Double.isNaN(yHigh) && !Double.isNaN(yLow)) {
354 double yyHigh = rangeAxis.valueToJava2D(yHigh, dataArea,
355 location);
356 double yyLow = rangeAxis.valueToJava2D(yLow, dataArea,
357 location);
358 if (orientation == PlotOrientation.HORIZONTAL) {
359 g2.draw(new Line2D.Double(yyLow, xx, yyHigh, xx));
360 entityArea = new Rectangle2D.Double(Math.min(yyLow, yyHigh),
361 xx - 1.0, Math.abs(yyHigh - yyLow), 2.0);
362 }
363 else if (orientation == PlotOrientation.VERTICAL) {
364 g2.draw(new Line2D.Double(xx, yyLow, xx, yyHigh));
365 entityArea = new Rectangle2D.Double(xx - 1.0,
366 Math.min(yyLow, yyHigh), 2.0,
367 Math.abs(yyHigh - yyLow));
368 }
369 }
370
371 double delta = getTickLength();
372 if (domainAxis.isInverted()) {
373 delta = -delta;
374 }
375 if (getDrawOpenTicks()) {
376 double yOpen = hld.getOpenValue(series, item);
377 if (!Double.isNaN(yOpen)) {
378 double yyOpen = rangeAxis.valueToJava2D(yOpen, dataArea,
379 location);
380 if (this.openTickPaint != null) {
381 g2.setPaint(this.openTickPaint);
382 }
383 else {
384 g2.setPaint(itemPaint);
385 }
386 if (orientation == PlotOrientation.HORIZONTAL) {
387 g2.draw(new Line2D.Double(yyOpen, xx + delta, yyOpen,
388 xx));
389 }
390 else if (orientation == PlotOrientation.VERTICAL) {
391 g2.draw(new Line2D.Double(xx - delta, yyOpen, xx,
392 yyOpen));
393 }
394 }
395 }
396
397 if (getDrawCloseTicks()) {
398 double yClose = hld.getCloseValue(series, item);
399 if (!Double.isNaN(yClose)) {
400 double yyClose = rangeAxis.valueToJava2D(
401 yClose, dataArea, location);
402 if (this.closeTickPaint != null) {
403 g2.setPaint(this.closeTickPaint);
404 }
405 else {
406 g2.setPaint(itemPaint);
407 }
408 if (orientation == PlotOrientation.HORIZONTAL) {
409 g2.draw(new Line2D.Double(yyClose, xx, yyClose,
410 xx - delta));
411 }
412 else if (orientation == PlotOrientation.VERTICAL) {
413 g2.draw(new Line2D.Double(xx, yyClose, xx + delta,
414 yyClose));
415 }
416 }
417 }
418
419 }
420 else {
421 // not a HighLowDataset, so just draw a line connecting this point
422 // with the previous point...
423 if (item > 0) {
424 double x0 = dataset.getXValue(series, item - 1);
425 double y0 = dataset.getYValue(series, item - 1);
426 double y = dataset.getYValue(series, item);
427 if (Double.isNaN(x0) || Double.isNaN(y0) || Double.isNaN(y)) {
428 return;
429 }
430 double xx0 = domainAxis.valueToJava2D(x0, dataArea,
431 plot.getDomainAxisEdge());
432 double yy0 = rangeAxis.valueToJava2D(y0, dataArea, location);
433 double yy = rangeAxis.valueToJava2D(y, dataArea, location);
434 if (orientation == PlotOrientation.HORIZONTAL) {
435 g2.draw(new Line2D.Double(yy0, xx0, yy, xx));
436 }
437 else if (orientation == PlotOrientation.VERTICAL) {
438 g2.draw(new Line2D.Double(xx0, yy0, xx, yy));
439 }
440 }
441 }
442
443 // add an entity for the item...
444 if (entities != null) {
445 String tip = null;
446 XYToolTipGenerator generator = getToolTipGenerator(series, item);
447 if (generator != null) {
448 tip = generator.generateToolTip(dataset, series, item);
449 }
450 String url = null;
451 if (getURLGenerator() != null) {
452 url = getURLGenerator().generateURL(dataset, series, item);
453 }
454 XYItemEntity entity = new XYItemEntity(entityArea, dataset,
455 series, item, tip, url);
456 entities.add(entity);
457 }
458
459 }
460
461 /**
462 * Returns a clone of the renderer.
463 *
464 * @return A clone.
465 *
466 * @throws CloneNotSupportedException if the renderer cannot be cloned.
467 */
468 public Object clone() throws CloneNotSupportedException {
469 return super.clone();
470 }
471
472 /**
473 * Tests this renderer for equality with an arbitrary object.
474 *
475 * @param obj the object (<code>null</code> permitted).
476 *
477 * @return A boolean.
478 */
479 public boolean equals(Object obj) {
480 if (this == obj) {
481 return true;
482 }
483 if (!(obj instanceof HighLowRenderer)) {
484 return false;
485 }
486 HighLowRenderer that = (HighLowRenderer) obj;
487 if (this.drawOpenTicks != that.drawOpenTicks) {
488 return false;
489 }
490 if (this.drawCloseTicks != that.drawCloseTicks) {
491 return false;
492 }
493 if (!PaintUtilities.equal(this.openTickPaint, that.openTickPaint)) {
494 return false;
495 }
496 if (!PaintUtilities.equal(this.closeTickPaint, that.closeTickPaint)) {
497 return false;
498 }
499 if (this.tickLength != that.tickLength) {
500 return false;
501 }
502 if (!super.equals(obj)) {
503 return false;
504 }
505 return true;
506 }
507
508 /**
509 * Provides serialization support.
510 *
511 * @param stream the input stream.
512 *
513 * @throws IOException if there is an I/O error.
514 * @throws ClassNotFoundException if there is a classpath problem.
515 */
516 private void readObject(ObjectInputStream stream)
517 throws IOException, ClassNotFoundException {
518 stream.defaultReadObject();
519 this.openTickPaint = SerialUtilities.readPaint(stream);
520 this.closeTickPaint = SerialUtilities.readPaint(stream);
521 }
522
523 /**
524 * Provides serialization support.
525 *
526 * @param stream the output stream.
527 *
528 * @throws IOException if there is an I/O error.
529 */
530 private void writeObject(ObjectOutputStream stream) throws IOException {
531 stream.defaultWriteObject();
532 SerialUtilities.writePaint(this.openTickPaint, stream);
533 SerialUtilities.writePaint(this.closeTickPaint, stream);
534 }
535
536 }