    001    /* ===========================================================
    002     * JFreeChart : a free chart library for the Java(tm) platform
    003     * ===========================================================
    004     *
    005     * (C) Copyright 2000-2006, 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-2006, 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     * $Id: HighLowRenderer.java,v 1.5.2.3 2006/07/06 10:03:34 mungady Exp $
    037     *
    038     * Changes
    039     * -------
    040     * 13-Dec-2001 : Version 1 (DG);
    041     * 23-Jan-2002 : Added DrawInfo parameter to drawItem() method (DG);
    042     * 28-Mar-2002 : Added a property change listener mechanism so that renderers
    043     *               no longer need to be immutable (DG);
    044     * 09-Apr-2002 : Removed translatedRangeZero from the drawItem() method, and
    045     *               changed the return type of the drawItem method to void,
    046     *               reflecting a change in the XYItemRenderer interface.  Added
    047     *               tooltip code to drawItem() method (DG);
    048     * 05-Aug-2002 : Small modification to drawItem method to support URLs for
    049     *               HTML image maps (RA);
    050     * 25-Mar-2003 : Implemented Serializable (DG);
    051     * 01-May-2003 : Modified drawItem() method signature (DG);
    052     * 30-Jul-2003 : Modified entity constructor (CZ);
    053     * 31-Jul-2003 : Deprecated constructor (DG);
    054     * 20-Aug-2003 : Implemented Cloneable and PublicCloneable (DG);
    055     * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG);
    056     * 29-Jan-2004 : Fixed bug (882392) when rendering with
    057     *               PlotOrientation.HORIZONTAL (DG);
    058     * 25-Feb-2004 : Replaced CrosshairInfo with CrosshairState.  Renamed
    059     *               XYToolTipGenerator --> XYItemLabelGenerator (DG);
    060     * 15-Jul-2004 : Switched getX() with getXValue() and getY() with
    061     *               getYValue() (DG);
    062     * 01-Nov-2005 : Added optional openTickPaint and closeTickPaint settings (DG);
    063     * ------------- JFREECHART 1.0.0 ---------------------------------------------
    064     * 06-Jul-2006 : Replace dataset methods getX() --> getXValue() (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.xy.OHLCDataset;
    091    import org.jfree.data.xy.XYDataset;
    092    import org.jfree.io.SerialUtilities;
    093    import org.jfree.ui.RectangleEdge;
    094    import org.jfree.util.PaintUtilities;
    095    import org.jfree.util.PublicCloneable;
    096
    097    /**
    098     * A renderer that draws high/low/open/close markers on an {@link XYPlot}
    099     * (requires a {@link OHLCDataset}).  This renderer does not include code to
    100     * calculate the crosshair point for the plot.
    101     */
    102    public class HighLowRenderer extends AbstractXYItemRenderer
    103                                 implements XYItemRenderer,
    104                                            Cloneable,
    105                                            PublicCloneable,
    106                                            Serializable {
    107
    108        /** For serialization. */
    109        private static final long serialVersionUID = -8135673815876552516L;
    110
    111        /** A flag that controls whether the open ticks are drawn. */
    112        private boolean drawOpenTicks;
    113
    114        /** A flag that controls whether the close ticks are drawn. */
    115        private boolean drawCloseTicks;
    116
    117        /**
    118         * The paint used for the open ticks (if <code>null</code>, the series
    119         * paint is used instead).
    120         */
    121        private transient Paint openTickPaint;
    122
    123        /**
    124         * The paint used for the close ticks (if <code>null</code>, the series
    125         * paint is used instead).
    126         */
    127        private transient Paint closeTickPaint;
    128
    129        /**
    130         * The default constructor.
    131         */
    132        public HighLowRenderer() {
    133            super();
    134            this.drawOpenTicks = true;
    135            this.drawCloseTicks = true;
    136        }
    137
    138        /**
    139         * Returns the flag that controls whether open ticks are drawn.
    140         *
    141         * @return A boolean.
    142         */
    143        public boolean getDrawOpenTicks() {
    144            return this.drawOpenTicks;
    145        }
    146
    147        /**
    148         * Sets the flag that controls whether open ticks are drawn, and sends a
    149         * {@link RendererChangeEvent} to all registered listeners.
    150         *
    151         * @param draw  the flag.
    152         */
    153        public void setDrawOpenTicks(boolean draw) {
    154            this.drawOpenTicks = draw;
    155            notifyListeners(new RendererChangeEvent(this));
    156        }
    157
    158        /**
    159         * Returns the flag that controls whether close ticks are drawn.
    160         *
    161         * @return A boolean.
    162         */
    163        public boolean getDrawCloseTicks() {
    164            return this.drawCloseTicks;
    165        }
    166
    167        /**
    168         * Sets the flag that controls whether close ticks are drawn, and sends a
    169         * {@link RendererChangeEvent} to all registered listeners.
    170         *
    171         * @param draw  the flag.
    172         */
    173        public void setDrawCloseTicks(boolean draw) {
    174            this.drawCloseTicks = draw;
    175            notifyListeners(new RendererChangeEvent(this));
    176        }
    177
    178        /**
    179         * Returns the paint used to draw the ticks for the open values.
    180         *
    181         * @return The paint used to draw the ticks for the open values (possibly
    182         *         <code>null</code>).
    183         */
    184        public Paint getOpenTickPaint() {
    185            return this.openTickPaint;
    186        }
    187
    188        /**
    189         * Sets the paint used to draw the ticks for the open values and sends a
    190         * {@link RendererChangeEvent} to all registered listeners.  If you set
    191         * this to <code>null</code> (the default), the series paint is used
    192         * instead.
    193         *
    194         * @param paint  the paint (<code>null</code> permitted).
    195         */
    196        public void setOpenTickPaint(Paint paint) {
    197            this.openTickPaint = paint;
    198            notifyListeners(new RendererChangeEvent(this));
    199        }
    200
    201        /**
    202         * Returns the paint used to draw the ticks for the close values.
    203         *
    204         * @return The paint used to draw the ticks for the close values (possibly
    205         *         <code>null</code>).
    206         */
    207        public Paint getCloseTickPaint() {
    208            return this.closeTickPaint;
    209        }
    210
    211        /**
    212         * Sets the paint used to draw the ticks for the close values and sends a
    213         * {@link RendererChangeEvent} to all registered listeners.  If you set
    214         * this to <code>null</code> (the default), the series paint is used
    215         * instead.
    216         *
    217         * @param paint  the paint (<code>null</code> permitted).
    218         */
    219        public void setCloseTickPaint(Paint paint) {
    220            this.closeTickPaint = paint;
    221            notifyListeners(new RendererChangeEvent(this));
    222        }
    223
    224        /**
    225         * Draws the visual representation of a single data item.
    226         *
    227         * @param g2  the graphics device.
    228         * @param state  the renderer state.
    229         * @param dataArea  the area within which the plot is being drawn.
    230         * @param info  collects information about the drawing.
    231         * @param plot  the plot (can be used to obtain standard color
    232         *              information etc).
    233         * @param domainAxis  the domain axis.
    234         * @param rangeAxis  the range axis.
    235         * @param dataset  the dataset.
    236         * @param series  the series index (zero-based).
    237         * @param item  the item index (zero-based).
    238         * @param crosshairState  crosshair information for the plot
    239         *                        (<code>null</code> permitted).
    240         * @param pass  the pass index.
    241         */
    242        public void drawItem(Graphics2D g2,
    243                             XYItemRendererState state,
    244                             Rectangle2D dataArea,
    245                             PlotRenderingInfo info,
    246                             XYPlot plot,
    247                             ValueAxis domainAxis,
    248                             ValueAxis rangeAxis,
    249                             XYDataset dataset,
    250                             int series,
    251                             int item,
    252                             CrosshairState crosshairState,
    253                             int pass) {
    254
    255            double x = dataset.getXValue(series, item);
    256            if (!domainAxis.getRange().contains(x)) {
    257                return;    // the x value is not within the axis range
    258            }
    259            double xx = domainAxis.valueToJava2D(x, dataArea,
    260                    plot.getDomainAxisEdge());
    261
    262            // setup for collecting optional entity info...
    263            Shape entityArea = null;
    264            EntityCollection entities = null;
    265            if (info != null) {
    266                entities = info.getOwner().getEntityCollection();
    267            }
    268
    269            PlotOrientation orientation = plot.getOrientation();
    270            RectangleEdge location = plot.getRangeAxisEdge();
    271
    272            Paint itemPaint = getItemPaint(series, item);
    273            Stroke itemStroke = getItemStroke(series, item);
    274            g2.setPaint(itemPaint);
    275            g2.setStroke(itemStroke);
    276
    277            if (dataset instanceof OHLCDataset) {
    278                OHLCDataset hld = (OHLCDataset) dataset;
    279
    280                double yHigh = hld.getHighValue(series, item);
    281                double yLow = hld.getLowValue(series, item);
    282                if (!Double.isNaN(yHigh) && !Double.isNaN(yLow)) {
    283                    double yyHigh = rangeAxis.valueToJava2D(yHigh, dataArea,
    284                            location);
    285                    double yyLow = rangeAxis.valueToJava2D(yLow, dataArea,
    286                            location);
    287                    if (orientation == PlotOrientation.HORIZONTAL) {
    288                        g2.draw(new Line2D.Double(yyLow, xx, yyHigh, xx));
    289                        entityArea = new Rectangle2D.Double(Math.min(yyLow, yyHigh),
    290                                xx - 1.0, Math.abs(yyHigh - yyLow), 2.0);
    291                    }
    292                    else if (orientation == PlotOrientation.VERTICAL) {
    293                        g2.draw(new Line2D.Double(xx, yyLow, xx, yyHigh));
    294                        entityArea = new Rectangle2D.Double(xx - 1.0,
    295                                Math.min(yyLow, yyHigh), 2.0,
    296                                Math.abs(yyHigh - yyLow));
    297                    }
    298                }
    299
    300                double delta = 2.0;
    301                if (domainAxis.isInverted()) {
    302                    delta = -delta;
    303                }
    304                if (getDrawOpenTicks()) {
    305                    double yOpen = hld.getOpenValue(series, item);
    306                    if (!Double.isNaN(yOpen)) {
    307                        double yyOpen = rangeAxis.valueToJava2D(yOpen, dataArea,
    308                                location);
    309                        if (this.openTickPaint != null) {
    310                            g2.setPaint(this.openTickPaint);
    311                        }
    312                        else {
    313                            g2.setPaint(itemPaint);
    314                        }
    315                        if (orientation == PlotOrientation.HORIZONTAL) {
    316                            g2.draw(new Line2D.Double(yyOpen, xx + delta, yyOpen,
    317                                    xx));
    318                        }
    319                        else if (orientation == PlotOrientation.VERTICAL) {
    320                            g2.draw(new Line2D.Double(xx - delta, yyOpen, xx,
    321                                    yyOpen));
    322                        }
    323                    }
    324                }
    325
    326                if (getDrawCloseTicks()) {
    327                    double yClose = hld.getCloseValue(series, item);
    328                    if (!Double.isNaN(yClose)) {
    329                        double yyClose = rangeAxis.valueToJava2D(
    330                            yClose, dataArea, location);
    331                        if (this.closeTickPaint != null) {
    332                            g2.setPaint(this.closeTickPaint);
    333                        }
    334                        else {
    335                            g2.setPaint(itemPaint);
    336                        }
    337                        if (orientation == PlotOrientation.HORIZONTAL) {
    338                            g2.draw(new Line2D.Double(yyClose, xx, yyClose,
    339                                    xx - delta));
    340                        }
    341                        else if (orientation == PlotOrientation.VERTICAL) {
    342                            g2.draw(new Line2D.Double(xx, yyClose, xx + delta,
    343                                    yyClose));
    344                        }
    345                    }
    346                }
    347
    348            }
    349            else {
    350                // not a HighLowDataset, so just draw a line connecting this point
    351                // with the previous point...
    352                if (item > 0) {
    353                    double x0 = dataset.getXValue(series, item - 1);
    354                    double y0 = dataset.getYValue(series, item - 1);
    355                    double y = dataset.getYValue(series, item);
    356                    if (Double.isNaN(x0) || Double.isNaN(y0) || Double.isNaN(y)) {
    357                        return;
    358                    }
    359                    double xx0 = domainAxis.valueToJava2D(x0, dataArea,
    360                            plot.getDomainAxisEdge());
    361                    double yy0 = rangeAxis.valueToJava2D(y0, dataArea, location);
    362                    double yy = rangeAxis.valueToJava2D(y, dataArea, location);
    363                    if (orientation == PlotOrientation.HORIZONTAL) {
    364                        g2.draw(new Line2D.Double(yy0, xx0, yy, xx));
    365                    }
    366                    else if (orientation == PlotOrientation.VERTICAL) {
    367                        g2.draw(new Line2D.Double(xx0, yy0, xx, yy));
    368                    }
    369                }
    370            }
    371
    372            // add an entity for the item...
    373            if (entities != null) {
    374                String tip = null;
    375                XYToolTipGenerator generator = getToolTipGenerator(series, item);
    376                if (generator != null) {
    377                    tip = generator.generateToolTip(dataset, series, item);
    378                }
    379                String url = null;
    380                if (getURLGenerator() != null) {
    381                    url = getURLGenerator().generateURL(dataset, series, item);
    382                }
    383                XYItemEntity entity = new XYItemEntity(entityArea, dataset,
    384                        series, item, tip, url);
    385                entities.add(entity);
    386            }
    387
    388        }
    389
    390        /**
    391         * Returns a clone of the renderer.
    392         *
    393         * @return A clone.
    394         *
    395         * @throws CloneNotSupportedException  if the renderer cannot be cloned.
    396         */
    397        public Object clone() throws CloneNotSupportedException {
    398            return super.clone();
    399        }
    400
    401        /**
    402         * Tests this renderer for equality with an arbitrary object.
    403         *
    404         * @param obj  the object (<code>null</code> permitted).
    405         *
    406         * @return A boolean.
    407         */
    408        public boolean equals(Object obj) {
    409            if (this == obj) {
    410                return true;
    411            }
    412            if (!(obj instanceof HighLowRenderer)) {
    413                return false;
    414            }
    415            HighLowRenderer that = (HighLowRenderer) obj;
    416            if (this.drawOpenTicks != that.drawOpenTicks) {
    417                return false;
    418            }
    419            if (this.drawCloseTicks != that.drawCloseTicks) {
    420                return false;
    421            }
    422            if (!PaintUtilities.equal(this.openTickPaint, that.openTickPaint)) {
    423                return false;
    424            }
    425            if (!PaintUtilities.equal(this.closeTickPaint, that.closeTickPaint)) {
    426                return false;
    427            }
    428            if (!super.equals(obj)) {
    429                return false;
    430            }
    431            return true;
    432        }
    433
    434        /**
    435         * Provides serialization support.
    436         *
    437         * @param stream  the input stream.
    438         *
    439         * @throws IOException  if there is an I/O error.
    440         * @throws ClassNotFoundException  if there is a classpath problem.
    441         */
    442        private void readObject(ObjectInputStream stream)
    443                throws IOException, ClassNotFoundException {
    444            stream.defaultReadObject();
    445            this.openTickPaint = SerialUtilities.readPaint(stream);
    446            this.closeTickPaint = SerialUtilities.readPaint(stream);
    447        }
    448
    449        /**
    450         * Provides serialization support.
    451         *
    452         * @param stream  the output stream.
    453         *
    454         * @throws IOException  if there is an I/O error.
    455         */
    456        private void writeObject(ObjectOutputStream stream) throws IOException {
    457            stream.defaultWriteObject();
    458            SerialUtilities.writePaint(this.openTickPaint, stream);
    459            SerialUtilities.writePaint(this.closeTickPaint, stream);
    460        }
    461
    462    }