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