    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     * 25-Sep-2008 : Check for non-null entity collection (DG);
    066     *
    067     */
    068
    069    package org.jfree.chart.renderer.xy;
    070
    071    import java.awt.Graphics2D;
    072    import java.awt.Paint;
    073    import java.awt.Shape;
    074    import java.awt.Stroke;
    075    import java.awt.geom.Line2D;
    076    import java.awt.geom.Rectangle2D;
    077    import java.io.IOException;
    078    import java.io.ObjectInputStream;
    079    import java.io.ObjectOutputStream;
    080    import java.io.Serializable;
    081
    082    import org.jfree.chart.axis.ValueAxis;
    083    import org.jfree.chart.entity.EntityCollection;
    084    import org.jfree.chart.event.RendererChangeEvent;
    085    import org.jfree.chart.plot.CrosshairState;
    086    import org.jfree.chart.plot.PlotOrientation;
    087    import org.jfree.chart.plot.PlotRenderingInfo;
    088    import org.jfree.chart.plot.XYPlot;
    089    import org.jfree.data.Range;
    090    import org.jfree.data.general.DatasetUtilities;
    091    import org.jfree.data.xy.OHLCDataset;
    092    import org.jfree.data.xy.XYDataset;
    093    import org.jfree.io.SerialUtilities;
    094    import org.jfree.ui.RectangleEdge;
    095    import org.jfree.util.PaintUtilities;
    096    import 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     */
    110    public 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    }