001/* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2014, 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 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. 
025 * Other names may be trademarks of their respective owners.]
026 *
027 * --------------------
028 * HighLowRenderer.java
029 * --------------------
030 * (C) Copyright 2001-2014, 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 * 25-Sep-2008 : Check for non-null entity collection (DG);
066 *
067 */
068
069package org.jfree.chart.renderer.xy;
070
071import java.awt.Graphics2D;
072import java.awt.Paint;
073import java.awt.Shape;
074import java.awt.Stroke;
075import java.awt.geom.Line2D;
076import java.awt.geom.Rectangle2D;
077import java.io.IOException;
078import java.io.ObjectInputStream;
079import java.io.ObjectOutputStream;
080import java.io.Serializable;
081
082import org.jfree.chart.axis.ValueAxis;
083import org.jfree.chart.entity.EntityCollection;
084import org.jfree.chart.event.RendererChangeEvent;
085import org.jfree.chart.plot.CrosshairState;
086import org.jfree.chart.plot.PlotOrientation;
087import org.jfree.chart.plot.PlotRenderingInfo;
088import org.jfree.chart.plot.XYPlot;
089import org.jfree.data.Range;
090import org.jfree.data.general.DatasetUtilities;
091import org.jfree.data.xy.OHLCDataset;
092import org.jfree.data.xy.XYDataset;
093import org.jfree.io.SerialUtilities;
094import org.jfree.ui.RectangleEdge;
095import org.jfree.util.PaintUtilities;
096import org.jfree.util.PublicCloneable;
097
098/**
099 * A renderer that draws high/low/open/close markers on an {@link XYPlot}
100 * (requires a {@link OHLCDataset}).  This renderer does not include code to
101 * calculate the crosshair point for the plot.
102 *
103 * The example shown here is generated by the
104 * <code>HighLowChartDemo1.java</code> program included in the JFreeChart Demo
105 * Collection:
106 * <br><br>
107 * <img src="../../../../../images/HighLowRendererSample.png"
108 * alt="HighLowRendererSample.png">
109 */
110public class HighLowRenderer extends AbstractXYItemRenderer
111        implements XYItemRenderer, Cloneable, PublicCloneable, Serializable {
112
113    /** For serialization. */
114    private static final long serialVersionUID = -8135673815876552516L;
115
116    /** A flag that controls whether the open ticks are drawn. */
117    private boolean drawOpenTicks;
118
119    /** A flag that controls whether the close ticks are drawn. */
120    private boolean drawCloseTicks;
121
122    /**
123     * The paint used for the open ticks (if <code>null</code>, the series
124     * paint is used instead).
125     */
126    private transient Paint openTickPaint;
127
128    /**
129     * The paint used for the close ticks (if <code>null</code>, the series
130     * paint is used instead).
131     */
132    private transient Paint closeTickPaint;
133
134    /**
135     * The tick length (in Java2D units).
136     *
137     * @since 1.0.10
138     */
139    private double tickLength;
140
141    /**
142     * The default constructor.
143     */
144    public HighLowRenderer() {
145        super();
146        this.drawOpenTicks = true;
147        this.drawCloseTicks = true;
148        this.tickLength = 2.0;
149    }
150
151    /**
152     * Returns the flag that controls whether open ticks are drawn.
153     *
154     * @return A boolean.
155     *
156     * @see #getDrawCloseTicks()
157     * @see #setDrawOpenTicks(boolean)
158     */
159    public boolean getDrawOpenTicks() {
160        return this.drawOpenTicks;
161    }
162
163    /**
164     * Sets the flag that controls whether open ticks are drawn, and sends a
165     * {@link RendererChangeEvent} to all registered listeners.
166     *
167     * @param draw  the flag.
168     *
169     * @see #getDrawOpenTicks()
170     */
171    public void setDrawOpenTicks(boolean draw) {
172        this.drawOpenTicks = draw;
173        fireChangeEvent();
174    }
175
176    /**
177     * Returns the flag that controls whether close ticks are drawn.
178     *
179     * @return A boolean.
180     *
181     * @see #getDrawOpenTicks()
182     * @see #setDrawCloseTicks(boolean)
183     */
184    public boolean getDrawCloseTicks() {
185        return this.drawCloseTicks;
186    }
187
188    /**
189     * Sets the flag that controls whether close ticks are drawn, and sends a
190     * {@link RendererChangeEvent} to all registered listeners.
191     *
192     * @param draw  the flag.
193     *
194     * @see #getDrawCloseTicks()
195     */
196    public void setDrawCloseTicks(boolean draw) {
197        this.drawCloseTicks = draw;
198        fireChangeEvent();
199    }
200
201    /**
202     * Returns the paint used to draw the ticks for the open values.
203     *
204     * @return The paint used to draw the ticks for the open values (possibly
205     *         <code>null</code>).
206     *
207     * @see #setOpenTickPaint(Paint)
208     */
209    public Paint getOpenTickPaint() {
210        return this.openTickPaint;
211    }
212
213    /**
214     * Sets the paint used to draw the ticks for the open values and sends a
215     * {@link RendererChangeEvent} to all registered listeners.  If you set
216     * this to <code>null</code> (the default), the series paint is used
217     * instead.
218     *
219     * @param paint  the paint (<code>null</code> permitted).
220     *
221     * @see #getOpenTickPaint()
222     */
223    public void setOpenTickPaint(Paint paint) {
224        this.openTickPaint = paint;
225        fireChangeEvent();
226    }
227
228    /**
229     * Returns the paint used to draw the ticks for the close values.
230     *
231     * @return The paint used to draw the ticks for the close values (possibly
232     *         <code>null</code>).
233     *
234     * @see #setCloseTickPaint(Paint)
235     */
236    public Paint getCloseTickPaint() {
237        return this.closeTickPaint;
238    }
239
240    /**
241     * Sets the paint used to draw the ticks for the close values and sends a
242     * {@link RendererChangeEvent} to all registered listeners.  If you set
243     * this to <code>null</code> (the default), the series paint is used
244     * instead.
245     *
246     * @param paint  the paint (<code>null</code> permitted).
247     *
248     * @see #getCloseTickPaint()
249     */
250    public void setCloseTickPaint(Paint paint) {
251        this.closeTickPaint = paint;
252        fireChangeEvent();
253    }
254
255    /**
256     * Returns the tick length (in Java2D units).
257     *
258     * @return The tick length.
259     *
260     * @since 1.0.10
261     *
262     * @see #setTickLength(double)
263     */
264    public double getTickLength() {
265        return this.tickLength;
266    }
267
268    /**
269     * Sets the tick length (in Java2D units) and sends a
270     * {@link RendererChangeEvent} to all registered listeners.
271     *
272     * @param length  the length.
273     *
274     * @since 1.0.10
275     *
276     * @see #getTickLength()
277     */
278    public void setTickLength(double length) {
279        this.tickLength = length;
280        fireChangeEvent();
281    }
282
283    /**
284     * Returns the range of values the renderer requires to display all the
285     * items from the specified dataset.
286     *
287     * @param dataset  the dataset (<code>null</code> permitted).
288     *
289     * @return The range (<code>null</code> if the dataset is <code>null</code>
290     *         or empty).
291     */
292    @Override
293    public Range findRangeBounds(XYDataset dataset) {
294        if (dataset != null) {
295            return DatasetUtilities.findRangeBounds(dataset, true);
296        }
297        else {
298            return null;
299        }
300    }
301
302    /**
303     * Draws the visual representation of a single data item.
304     *
305     * @param g2  the graphics device.
306     * @param state  the renderer state.
307     * @param dataArea  the area within which the plot is being drawn.
308     * @param info  collects information about the drawing.
309     * @param plot  the plot (can be used to obtain standard color
310     *              information etc).
311     * @param domainAxis  the domain axis.
312     * @param rangeAxis  the range axis.
313     * @param dataset  the dataset.
314     * @param series  the series index (zero-based).
315     * @param item  the item index (zero-based).
316     * @param crosshairState  crosshair information for the plot
317     *                        (<code>null</code> permitted).
318     * @param pass  the pass index.
319     */
320    @Override
321    public void drawItem(Graphics2D g2, XYItemRendererState state,
322            Rectangle2D dataArea, PlotRenderingInfo info, XYPlot plot,
323            ValueAxis domainAxis, ValueAxis rangeAxis, XYDataset dataset,
324            int series, int item, CrosshairState crosshairState, 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        if (entities != null) {
444            addEntity(entities, entityArea, dataset, series, item, 0.0, 0.0);
445        }
446
447    }
448
449    /**
450     * Returns a clone of the renderer.
451     *
452     * @return A clone.
453     *
454     * @throws CloneNotSupportedException  if the renderer cannot be cloned.
455     */
456    @Override
457    public Object clone() throws CloneNotSupportedException {
458        return super.clone();
459    }
460
461    /**
462     * Tests this renderer for equality with an arbitrary object.
463     *
464     * @param obj  the object (<code>null</code> permitted).
465     *
466     * @return A boolean.
467     */
468    @Override
469    public boolean equals(Object obj) {
470        if (this == obj) {
471            return true;
472        }
473        if (!(obj instanceof HighLowRenderer)) {
474            return false;
475        }
476        HighLowRenderer that = (HighLowRenderer) obj;
477        if (this.drawOpenTicks != that.drawOpenTicks) {
478            return false;
479        }
480        if (this.drawCloseTicks != that.drawCloseTicks) {
481            return false;
482        }
483        if (!PaintUtilities.equal(this.openTickPaint, that.openTickPaint)) {
484            return false;
485        }
486        if (!PaintUtilities.equal(this.closeTickPaint, that.closeTickPaint)) {
487            return false;
488        }
489        if (this.tickLength != that.tickLength) {
490            return false;
491        }
492        if (!super.equals(obj)) {
493            return false;
494        }
495        return true;
496    }
497
498    /**
499     * Provides serialization support.
500     *
501     * @param stream  the input stream.
502     *
503     * @throws IOException  if there is an I/O error.
504     * @throws ClassNotFoundException  if there is a classpath problem.
505     */
506    private void readObject(ObjectInputStream stream)
507            throws IOException, ClassNotFoundException {
508        stream.defaultReadObject();
509        this.openTickPaint = SerialUtilities.readPaint(stream);
510        this.closeTickPaint = SerialUtilities.readPaint(stream);
511    }
512
513    /**
514     * Provides serialization support.
515     *
516     * @param stream  the output stream.
517     *
518     * @throws IOException  if there is an I/O error.
519     */
520    private void writeObject(ObjectOutputStream stream) throws IOException {
521        stream.defaultWriteObject();
522        SerialUtilities.writePaint(this.openTickPaint, stream);
523        SerialUtilities.writePaint(this.closeTickPaint, stream);
524    }
525
526}