001 /* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2006, 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-2006, 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 * $Id: HighLowRenderer.java,v 1.5.2.3 2006/07/06 10:03:34 mungady Exp $
037 *
038 * Changes
039 * -------
040 * 13-Dec-2001 : Version 1 (DG);
041 * 23-Jan-2002 : Added DrawInfo parameter to drawItem() method (DG);
042 * 28-Mar-2002 : Added a property change listener mechanism so that renderers
043 * no longer need to be immutable (DG);
044 * 09-Apr-2002 : Removed translatedRangeZero from the drawItem() method, and
045 * changed the return type of the drawItem method to void,
046 * reflecting a change in the XYItemRenderer interface. Added
047 * tooltip code to drawItem() method (DG);
048 * 05-Aug-2002 : Small modification to drawItem method to support URLs for
049 * HTML image maps (RA);
050 * 25-Mar-2003 : Implemented Serializable (DG);
051 * 01-May-2003 : Modified drawItem() method signature (DG);
052 * 30-Jul-2003 : Modified entity constructor (CZ);
053 * 31-Jul-2003 : Deprecated constructor (DG);
054 * 20-Aug-2003 : Implemented Cloneable and PublicCloneable (DG);
055 * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG);
056 * 29-Jan-2004 : Fixed bug (882392) when rendering with
057 * PlotOrientation.HORIZONTAL (DG);
058 * 25-Feb-2004 : Replaced CrosshairInfo with CrosshairState. Renamed
059 * XYToolTipGenerator --> XYItemLabelGenerator (DG);
060 * 15-Jul-2004 : Switched getX() with getXValue() and getY() with
061 * getYValue() (DG);
062 * 01-Nov-2005 : Added optional openTickPaint and closeTickPaint settings (DG);
063 * ------------- JFREECHART 1.0.0 ---------------------------------------------
064 * 06-Jul-2006 : Replace dataset methods getX() --> getXValue() (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.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,
104 Cloneable,
105 PublicCloneable,
106 Serializable {
107
108 /** For serialization. */
109 private static final long serialVersionUID = -8135673815876552516L;
110
111 /** A flag that controls whether the open ticks are drawn. */
112 private boolean drawOpenTicks;
113
114 /** A flag that controls whether the close ticks are drawn. */
115 private boolean drawCloseTicks;
116
117 /**
118 * The paint used for the open ticks (if <code>null</code>, the series
119 * paint is used instead).
120 */
121 private transient Paint openTickPaint;
122
123 /**
124 * The paint used for the close ticks (if <code>null</code>, the series
125 * paint is used instead).
126 */
127 private transient Paint closeTickPaint;
128
129 /**
130 * The default constructor.
131 */
132 public HighLowRenderer() {
133 super();
134 this.drawOpenTicks = true;
135 this.drawCloseTicks = true;
136 }
137
138 /**
139 * Returns the flag that controls whether open ticks are drawn.
140 *
141 * @return A boolean.
142 */
143 public boolean getDrawOpenTicks() {
144 return this.drawOpenTicks;
145 }
146
147 /**
148 * Sets the flag that controls whether open ticks are drawn, and sends a
149 * {@link RendererChangeEvent} to all registered listeners.
150 *
151 * @param draw the flag.
152 */
153 public void setDrawOpenTicks(boolean draw) {
154 this.drawOpenTicks = draw;
155 notifyListeners(new RendererChangeEvent(this));
156 }
157
158 /**
159 * Returns the flag that controls whether close ticks are drawn.
160 *
161 * @return A boolean.
162 */
163 public boolean getDrawCloseTicks() {
164 return this.drawCloseTicks;
165 }
166
167 /**
168 * Sets the flag that controls whether close ticks are drawn, and sends a
169 * {@link RendererChangeEvent} to all registered listeners.
170 *
171 * @param draw the flag.
172 */
173 public void setDrawCloseTicks(boolean draw) {
174 this.drawCloseTicks = draw;
175 notifyListeners(new RendererChangeEvent(this));
176 }
177
178 /**
179 * Returns the paint used to draw the ticks for the open values.
180 *
181 * @return The paint used to draw the ticks for the open values (possibly
182 * <code>null</code>).
183 */
184 public Paint getOpenTickPaint() {
185 return this.openTickPaint;
186 }
187
188 /**
189 * Sets the paint used to draw the ticks for the open values and sends a
190 * {@link RendererChangeEvent} to all registered listeners. If you set
191 * this to <code>null</code> (the default), the series paint is used
192 * instead.
193 *
194 * @param paint the paint (<code>null</code> permitted).
195 */
196 public void setOpenTickPaint(Paint paint) {
197 this.openTickPaint = paint;
198 notifyListeners(new RendererChangeEvent(this));
199 }
200
201 /**
202 * Returns the paint used to draw the ticks for the close values.
203 *
204 * @return The paint used to draw the ticks for the close values (possibly
205 * <code>null</code>).
206 */
207 public Paint getCloseTickPaint() {
208 return this.closeTickPaint;
209 }
210
211 /**
212 * Sets the paint used to draw the ticks for the close values and sends a
213 * {@link RendererChangeEvent} to all registered listeners. If you set
214 * this to <code>null</code> (the default), the series paint is used
215 * instead.
216 *
217 * @param paint the paint (<code>null</code> permitted).
218 */
219 public void setCloseTickPaint(Paint paint) {
220 this.closeTickPaint = paint;
221 notifyListeners(new RendererChangeEvent(this));
222 }
223
224 /**
225 * Draws the visual representation of a single data item.
226 *
227 * @param g2 the graphics device.
228 * @param state the renderer state.
229 * @param dataArea the area within which the plot is being drawn.
230 * @param info collects information about the drawing.
231 * @param plot the plot (can be used to obtain standard color
232 * information etc).
233 * @param domainAxis the domain axis.
234 * @param rangeAxis the range axis.
235 * @param dataset the dataset.
236 * @param series the series index (zero-based).
237 * @param item the item index (zero-based).
238 * @param crosshairState crosshair information for the plot
239 * (<code>null</code> permitted).
240 * @param pass the pass index.
241 */
242 public void drawItem(Graphics2D g2,
243 XYItemRendererState state,
244 Rectangle2D dataArea,
245 PlotRenderingInfo info,
246 XYPlot plot,
247 ValueAxis domainAxis,
248 ValueAxis rangeAxis,
249 XYDataset dataset,
250 int series,
251 int item,
252 CrosshairState crosshairState,
253 int pass) {
254
255 double x = dataset.getXValue(series, item);
256 if (!domainAxis.getRange().contains(x)) {
257 return; // the x value is not within the axis range
258 }
259 double xx = domainAxis.valueToJava2D(x, dataArea,
260 plot.getDomainAxisEdge());
261
262 // setup for collecting optional entity info...
263 Shape entityArea = null;
264 EntityCollection entities = null;
265 if (info != null) {
266 entities = info.getOwner().getEntityCollection();
267 }
268
269 PlotOrientation orientation = plot.getOrientation();
270 RectangleEdge location = plot.getRangeAxisEdge();
271
272 Paint itemPaint = getItemPaint(series, item);
273 Stroke itemStroke = getItemStroke(series, item);
274 g2.setPaint(itemPaint);
275 g2.setStroke(itemStroke);
276
277 if (dataset instanceof OHLCDataset) {
278 OHLCDataset hld = (OHLCDataset) dataset;
279
280 double yHigh = hld.getHighValue(series, item);
281 double yLow = hld.getLowValue(series, item);
282 if (!Double.isNaN(yHigh) && !Double.isNaN(yLow)) {
283 double yyHigh = rangeAxis.valueToJava2D(yHigh, dataArea,
284 location);
285 double yyLow = rangeAxis.valueToJava2D(yLow, dataArea,
286 location);
287 if (orientation == PlotOrientation.HORIZONTAL) {
288 g2.draw(new Line2D.Double(yyLow, xx, yyHigh, xx));
289 entityArea = new Rectangle2D.Double(Math.min(yyLow, yyHigh),
290 xx - 1.0, Math.abs(yyHigh - yyLow), 2.0);
291 }
292 else if (orientation == PlotOrientation.VERTICAL) {
293 g2.draw(new Line2D.Double(xx, yyLow, xx, yyHigh));
294 entityArea = new Rectangle2D.Double(xx - 1.0,
295 Math.min(yyLow, yyHigh), 2.0,
296 Math.abs(yyHigh - yyLow));
297 }
298 }
299
300 double delta = 2.0;
301 if (domainAxis.isInverted()) {
302 delta = -delta;
303 }
304 if (getDrawOpenTicks()) {
305 double yOpen = hld.getOpenValue(series, item);
306 if (!Double.isNaN(yOpen)) {
307 double yyOpen = rangeAxis.valueToJava2D(yOpen, dataArea,
308 location);
309 if (this.openTickPaint != null) {
310 g2.setPaint(this.openTickPaint);
311 }
312 else {
313 g2.setPaint(itemPaint);
314 }
315 if (orientation == PlotOrientation.HORIZONTAL) {
316 g2.draw(new Line2D.Double(yyOpen, xx + delta, yyOpen,
317 xx));
318 }
319 else if (orientation == PlotOrientation.VERTICAL) {
320 g2.draw(new Line2D.Double(xx - delta, yyOpen, xx,
321 yyOpen));
322 }
323 }
324 }
325
326 if (getDrawCloseTicks()) {
327 double yClose = hld.getCloseValue(series, item);
328 if (!Double.isNaN(yClose)) {
329 double yyClose = rangeAxis.valueToJava2D(
330 yClose, dataArea, location);
331 if (this.closeTickPaint != null) {
332 g2.setPaint(this.closeTickPaint);
333 }
334 else {
335 g2.setPaint(itemPaint);
336 }
337 if (orientation == PlotOrientation.HORIZONTAL) {
338 g2.draw(new Line2D.Double(yyClose, xx, yyClose,
339 xx - delta));
340 }
341 else if (orientation == PlotOrientation.VERTICAL) {
342 g2.draw(new Line2D.Double(xx, yyClose, xx + delta,
343 yyClose));
344 }
345 }
346 }
347
348 }
349 else {
350 // not a HighLowDataset, so just draw a line connecting this point
351 // with the previous point...
352 if (item > 0) {
353 double x0 = dataset.getXValue(series, item - 1);
354 double y0 = dataset.getYValue(series, item - 1);
355 double y = dataset.getYValue(series, item);
356 if (Double.isNaN(x0) || Double.isNaN(y0) || Double.isNaN(y)) {
357 return;
358 }
359 double xx0 = domainAxis.valueToJava2D(x0, dataArea,
360 plot.getDomainAxisEdge());
361 double yy0 = rangeAxis.valueToJava2D(y0, dataArea, location);
362 double yy = rangeAxis.valueToJava2D(y, dataArea, location);
363 if (orientation == PlotOrientation.HORIZONTAL) {
364 g2.draw(new Line2D.Double(yy0, xx0, yy, xx));
365 }
366 else if (orientation == PlotOrientation.VERTICAL) {
367 g2.draw(new Line2D.Double(xx0, yy0, xx, yy));
368 }
369 }
370 }
371
372 // add an entity for the item...
373 if (entities != null) {
374 String tip = null;
375 XYToolTipGenerator generator = getToolTipGenerator(series, item);
376 if (generator != null) {
377 tip = generator.generateToolTip(dataset, series, item);
378 }
379 String url = null;
380 if (getURLGenerator() != null) {
381 url = getURLGenerator().generateURL(dataset, series, item);
382 }
383 XYItemEntity entity = new XYItemEntity(entityArea, dataset,
384 series, item, tip, url);
385 entities.add(entity);
386 }
387
388 }
389
390 /**
391 * Returns a clone of the renderer.
392 *
393 * @return A clone.
394 *
395 * @throws CloneNotSupportedException if the renderer cannot be cloned.
396 */
397 public Object clone() throws CloneNotSupportedException {
398 return super.clone();
399 }
400
401 /**
402 * Tests this renderer for equality with an arbitrary object.
403 *
404 * @param obj the object (<code>null</code> permitted).
405 *
406 * @return A boolean.
407 */
408 public boolean equals(Object obj) {
409 if (this == obj) {
410 return true;
411 }
412 if (!(obj instanceof HighLowRenderer)) {
413 return false;
414 }
415 HighLowRenderer that = (HighLowRenderer) obj;
416 if (this.drawOpenTicks != that.drawOpenTicks) {
417 return false;
418 }
419 if (this.drawCloseTicks != that.drawCloseTicks) {
420 return false;
421 }
422 if (!PaintUtilities.equal(this.openTickPaint, that.openTickPaint)) {
423 return false;
424 }
425 if (!PaintUtilities.equal(this.closeTickPaint, that.closeTickPaint)) {
426 return false;
427 }
428 if (!super.equals(obj)) {
429 return false;
430 }
431 return true;
432 }
433
434 /**
435 * Provides serialization support.
436 *
437 * @param stream the input stream.
438 *
439 * @throws IOException if there is an I/O error.
440 * @throws ClassNotFoundException if there is a classpath problem.
441 */
442 private void readObject(ObjectInputStream stream)
443 throws IOException, ClassNotFoundException {
444 stream.defaultReadObject();
445 this.openTickPaint = SerialUtilities.readPaint(stream);
446 this.closeTickPaint = SerialUtilities.readPaint(stream);
447 }
448
449 /**
450 * Provides serialization support.
451 *
452 * @param stream the output stream.
453 *
454 * @throws IOException if there is an I/O error.
455 */
456 private void writeObject(ObjectOutputStream stream) throws IOException {
457 stream.defaultWriteObject();
458 SerialUtilities.writePaint(this.openTickPaint, stream);
459 SerialUtilities.writePaint(this.closeTickPaint, stream);
460 }
461
462 }