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    }