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    }