    001/* ===========================================================
    002 * JFreeChart : a free chart library for the Java(tm) platform
    003 * ===========================================================
    004 *
    005 * (C) Copyright 2000-2011, 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-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 * 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    public Range findRangeBounds(XYDataset dataset) {
    293        if (dataset != null) {
    294            return DatasetUtilities.findRangeBounds(dataset, true);
    295        }
    296        else {
    297            return null;
    298        }
    299    }
    300
    301    /**
    302     * Draws the visual representation of a single data item.
    303     *
    304     * @param g2  the graphics device.
    305     * @param state  the renderer state.
    306     * @param dataArea  the area within which the plot is being drawn.
    307     * @param info  collects information about the drawing.
    308     * @param plot  the plot (can be used to obtain standard color
    309     *              information etc).
    310     * @param domainAxis  the domain axis.
    311     * @param rangeAxis  the range axis.
    312     * @param dataset  the dataset.
    313     * @param series  the series index (zero-based).
    314     * @param item  the item index (zero-based).
    315     * @param crosshairState  crosshair information for the plot
    316     *                        (<code>null</code> permitted).
    317     * @param pass  the pass index.
    318     */
    319    public void drawItem(Graphics2D g2,
    320                         XYItemRendererState state,
    321                         Rectangle2D dataArea,
    322                         PlotRenderingInfo info,
    323                         XYPlot plot,
    324                         ValueAxis domainAxis,
    325                         ValueAxis rangeAxis,
    326                         XYDataset dataset,
    327                         int series,
    328                         int item,
    329                         CrosshairState crosshairState,
    330                         int pass) {
    331
    332        double x = dataset.getXValue(series, item);
    333        if (!domainAxis.getRange().contains(x)) {
    334            return;    // the x value is not within the axis range
    335        }
    336        double xx = domainAxis.valueToJava2D(x, dataArea,
    337                plot.getDomainAxisEdge());
    338
    339        // setup for collecting optional entity info...
    340        Shape entityArea = null;
    341        EntityCollection entities = null;
    342        if (info != null) {
    343            entities = info.getOwner().getEntityCollection();
    344        }
    345
    346        PlotOrientation orientation = plot.getOrientation();
    347        RectangleEdge location = plot.getRangeAxisEdge();
    348
    349        Paint itemPaint = getItemPaint(series, item);
    350        Stroke itemStroke = getItemStroke(series, item);
    351        g2.setPaint(itemPaint);
    352        g2.setStroke(itemStroke);
    353
    354        if (dataset instanceof OHLCDataset) {
    355            OHLCDataset hld = (OHLCDataset) dataset;
    356
    357            double yHigh = hld.getHighValue(series, item);
    358            double yLow = hld.getLowValue(series, item);
    359            if (!Double.isNaN(yHigh) && !Double.isNaN(yLow)) {
    360                double yyHigh = rangeAxis.valueToJava2D(yHigh, dataArea,
    361                        location);
    362                double yyLow = rangeAxis.valueToJava2D(yLow, dataArea,
    363                        location);
    364                if (orientation == PlotOrientation.HORIZONTAL) {
    365                    g2.draw(new Line2D.Double(yyLow, xx, yyHigh, xx));
    366                    entityArea = new Rectangle2D.Double(Math.min(yyLow, yyHigh),
    367                            xx - 1.0, Math.abs(yyHigh - yyLow), 2.0);
    368                }
    369                else if (orientation == PlotOrientation.VERTICAL) {
    370                    g2.draw(new Line2D.Double(xx, yyLow, xx, yyHigh));
    371                    entityArea = new Rectangle2D.Double(xx - 1.0,
    372                            Math.min(yyLow, yyHigh), 2.0,
    373                            Math.abs(yyHigh - yyLow));
    374                }
    375            }
    376
    377            double delta = getTickLength();
    378            if (domainAxis.isInverted()) {
    379                delta = -delta;
    380            }
    381            if (getDrawOpenTicks()) {
    382                double yOpen = hld.getOpenValue(series, item);
    383                if (!Double.isNaN(yOpen)) {
    384                    double yyOpen = rangeAxis.valueToJava2D(yOpen, dataArea,
    385                            location);
    386                    if (this.openTickPaint != null) {
    387                        g2.setPaint(this.openTickPaint);
    388                    }
    389                    else {
    390                        g2.setPaint(itemPaint);
    391                    }
    392                    if (orientation == PlotOrientation.HORIZONTAL) {
    393                        g2.draw(new Line2D.Double(yyOpen, xx + delta, yyOpen,
    394                                xx));
    395                    }
    396                    else if (orientation == PlotOrientation.VERTICAL) {
    397                        g2.draw(new Line2D.Double(xx - delta, yyOpen, xx,
    398                                yyOpen));
    399                    }
    400                }
    401            }
    402
    403            if (getDrawCloseTicks()) {
    404                double yClose = hld.getCloseValue(series, item);
    405                if (!Double.isNaN(yClose)) {
    406                    double yyClose = rangeAxis.valueToJava2D(
    407                        yClose, dataArea, location);
    408                    if (this.closeTickPaint != null) {
    409                        g2.setPaint(this.closeTickPaint);
    410                    }
    411                    else {
    412                        g2.setPaint(itemPaint);
    413                    }
    414                    if (orientation == PlotOrientation.HORIZONTAL) {
    415                        g2.draw(new Line2D.Double(yyClose, xx, yyClose,
    416                                xx - delta));
    417                    }
    418                    else if (orientation == PlotOrientation.VERTICAL) {
    419                        g2.draw(new Line2D.Double(xx, yyClose, xx + delta,
    420                                yyClose));
    421                    }
    422                }
    423            }
    424
    425        }
    426        else {
    427            // not a HighLowDataset, so just draw a line connecting this point
    428            // with the previous point...
    429            if (item > 0) {
    430                double x0 = dataset.getXValue(series, item - 1);
    431                double y0 = dataset.getYValue(series, item - 1);
    432                double y = dataset.getYValue(series, item);
    433                if (Double.isNaN(x0) || Double.isNaN(y0) || Double.isNaN(y)) {
    434                    return;
    435                }
    436                double xx0 = domainAxis.valueToJava2D(x0, dataArea,
    437                        plot.getDomainAxisEdge());
    438                double yy0 = rangeAxis.valueToJava2D(y0, dataArea, location);
    439                double yy = rangeAxis.valueToJava2D(y, dataArea, location);
    440                if (orientation == PlotOrientation.HORIZONTAL) {
    441                    g2.draw(new Line2D.Double(yy0, xx0, yy, xx));
    442                }
    443                else if (orientation == PlotOrientation.VERTICAL) {
    444                    g2.draw(new Line2D.Double(xx0, yy0, xx, yy));
    445                }
    446            }
    447        }
    448
    449        if (entities != null) {
    450            addEntity(entities, entityArea, dataset, series, item, 0.0, 0.0);
    451        }
    452
    453    }
    454
    455    /**
    456     * Returns a clone of the renderer.
    457     *
    458     * @return A clone.
    459     *
    460     * @throws CloneNotSupportedException  if the renderer cannot be cloned.
    461     */
    462    public Object clone() throws CloneNotSupportedException {
    463        return super.clone();
    464    }
    465
    466    /**
    467     * Tests this renderer for equality with an arbitrary object.
    468     *
    469     * @param obj  the object (<code>null</code> permitted).
    470     *
    471     * @return A boolean.
    472     */
    473    public boolean equals(Object obj) {
    474        if (this == obj) {
    475            return true;
    476        }
    477        if (!(obj instanceof HighLowRenderer)) {
    478            return false;
    479        }
    480        HighLowRenderer that = (HighLowRenderer) obj;
    481        if (this.drawOpenTicks != that.drawOpenTicks) {
    482            return false;
    483        }
    484        if (this.drawCloseTicks != that.drawCloseTicks) {
    485            return false;
    486        }
    487        if (!PaintUtilities.equal(this.openTickPaint, that.openTickPaint)) {
    488            return false;
    489        }
    490        if (!PaintUtilities.equal(this.closeTickPaint, that.closeTickPaint)) {
    491            return false;
    492        }
    493        if (this.tickLength != that.tickLength) {
    494            return false;
    495        }
    496        if (!super.equals(obj)) {
    497            return false;
    498        }
    499        return true;
    500    }
    501
    502    /**
    503     * Provides serialization support.
    504     *
    505     * @param stream  the input stream.
    506     *
    507     * @throws IOException  if there is an I/O error.
    508     * @throws ClassNotFoundException  if there is a classpath problem.
    509     */
    510    private void readObject(ObjectInputStream stream)
    511            throws IOException, ClassNotFoundException {
    512        stream.defaultReadObject();
    513        this.openTickPaint = SerialUtilities.readPaint(stream);
    514        this.closeTickPaint = SerialUtilities.readPaint(stream);
    515    }
    516
    517    /**
    518     * Provides serialization support.
    519     *
    520     * @param stream  the output stream.
    521     *
    522     * @throws IOException  if there is an I/O error.
    523     */
    524    private void writeObject(ObjectOutputStream stream) throws IOException {
    525        stream.defaultWriteObject();
    526        SerialUtilities.writePaint(this.openTickPaint, stream);
    527        SerialUtilities.writePaint(this.closeTickPaint, stream);
    528    }
    529
    530}