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