    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    }