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