    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     * XYStepAreaRenderer.java
    029     * -----------------------
    030     * (C) Copyright 2003-2008, by Matthias Rose and Contributors.
    031     *
    032     * Original Author:  Matthias Rose (based on XYAreaRenderer.java);
    033     * Contributor(s):   David Gilbert (for Object Refinery Limited);
    034     *
    035     * Changes:
    036     * --------
    037     * 07-Oct-2003 : Version 1, contributed by Matthias Rose (DG);
    038     * 10-Feb-2004 : Added some getter and setter methods (DG);
    039     * 25-Feb-2004 : Replaced CrosshairInfo with CrosshairState.  Renamed
    040     *               XYToolTipGenerator --> XYItemLabelGenerator (DG);
    041     * 15-Jul-2004 : Switched getX() with getXValue() and getY() with
    042     *               getYValue() (DG);
    043     * 11-Nov-2004 : Now uses ShapeUtilities to translate shapes (DG);
    044     * 06-Jul-2005 : Renamed get/setPlotShapes() --> get/setShapesVisible() (DG);
    045     * ------------- JFREECHART 1.0.x ---------------------------------------------
    046     * 06-Jul-2006 : Modified to call dataset methods that return double
    047     *               primitives only (DG);
    048     * 06-Feb-2007 : Fixed bug 1086307, crosshairs with multiple axes (DG);
    049     * 14-Feb-2007 : Added equals() method override (DG);
    050     * 04-May-2007 : Set processVisibleItemsOnly flag to false (DG);
    051     * 14-May-2008 : Call addEntity() from within drawItem() (DG);
    052     *
    053     */
    054
    055    package org.jfree.chart.renderer.xy;
    056
    057    import java.awt.Graphics2D;
    058    import java.awt.Paint;
    059    import java.awt.Polygon;
    060    import java.awt.Shape;
    061    import java.awt.Stroke;
    062    import java.awt.geom.Rectangle2D;
    063    import java.io.Serializable;
    064
    065    import org.jfree.chart.axis.ValueAxis;
    066    import org.jfree.chart.entity.EntityCollection;
    067    import org.jfree.chart.event.RendererChangeEvent;
    068    import org.jfree.chart.labels.XYToolTipGenerator;
    069    import org.jfree.chart.plot.CrosshairState;
    070    import org.jfree.chart.plot.PlotOrientation;
    071    import org.jfree.chart.plot.PlotRenderingInfo;
    072    import org.jfree.chart.plot.XYPlot;
    073    import org.jfree.chart.urls.XYURLGenerator;
    074    import org.jfree.data.xy.XYDataset;
    075    import org.jfree.util.PublicCloneable;
    076    import org.jfree.util.ShapeUtilities;
    077
    078    /**
    079     * A step chart renderer that fills the area between the step and the x-axis.
    080     * The example shown here is generated by the
    081     * <code>XYStepAreaRendererDemo1.java</code> program included in the JFreeChart
    082     * demo collection:
    083     * <br><br>
    084     * <img src="../../../../../images/XYStepAreaRendererSample.png"
    085     * alt="XYStepAreaRendererSample.png" />
    086     */
    087    public class XYStepAreaRenderer extends AbstractXYItemRenderer
    088            implements XYItemRenderer, Cloneable, PublicCloneable, Serializable {
    089
    090        /** For serialization. */
    091        private static final long serialVersionUID = -7311560779702649635L;
    092
    093        /** Useful constant for specifying the type of rendering (shapes only). */
    094        public static final int SHAPES = 1;
    095
    096        /** Useful constant for specifying the type of rendering (area only). */
    097        public static final int AREA = 2;
    098
    099        /**
    100         * Useful constant for specifying the type of rendering (area and shapes).
    101         */
    102        public static final int AREA_AND_SHAPES = 3;
    103
    104        /** A flag indicating whether or not shapes are drawn at each XY point. */
    105        private boolean shapesVisible;
    106
    107        /** A flag that controls whether or not shapes are filled for ALL series. */
    108        private boolean shapesFilled;
    109
    110        /** A flag indicating whether or not Area are drawn at each XY point. */
    111        private boolean plotArea;
    112
    113        /** A flag that controls whether or not the outline is shown. */
    114        private boolean showOutline;
    115
    116        /** Area of the complete series */
    117        protected transient Polygon pArea = null;
    118
    119        /**
    120         * The value on the range axis which defines the 'lower' border of the
    121         * area.
    122         */
    123        private double rangeBase;
    124
    125        /**
    126         * Constructs a new renderer.
    127         */
    128        public XYStepAreaRenderer() {
    129            this(AREA);
    130        }
    131
    132        /**
    133         * Constructs a new renderer.
    134         *
    135         * @param type  the type of the renderer.
    136         */
    137        public XYStepAreaRenderer(int type) {
    138            this(type, null, null);
    139        }
    140
    141        /**
    142         * Constructs a new renderer.
    143         * <p>
    144         * To specify the type of renderer, use one of the constants:
    145         * AREA, SHAPES or AREA_AND_SHAPES.
    146         *
    147         * @param type  the type of renderer.
    148         * @param toolTipGenerator  the tool tip generator to use
    149         *                          (<code>null</code> permitted).
    150         * @param urlGenerator  the URL generator (<code>null</code> permitted).
    151         */
    152        public XYStepAreaRenderer(int type,
    153                                  XYToolTipGenerator toolTipGenerator,
    154                                  XYURLGenerator urlGenerator) {
    155
    156            super();
    157            setBaseToolTipGenerator(toolTipGenerator);
    158            setURLGenerator(urlGenerator);
    159
    160            if (type == AREA) {
    161                this.plotArea = true;
    162            }
    163            else if (type == SHAPES) {
    164                this.shapesVisible = true;
    165            }
    166            else if (type == AREA_AND_SHAPES) {
    167                this.plotArea = true;
    168                this.shapesVisible = true;
    169            }
    170            this.showOutline = false;
    171        }
    172
    173        /**
    174         * Returns a flag that controls whether or not outlines of the areas are
    175         * drawn.
    176         *
    177         * @return The flag.
    178         *
    179         * @see #setOutline(boolean)
    180         */
    181        public boolean isOutline() {
    182            return this.showOutline;
    183        }
    184
    185        /**
    186         * Sets a flag that controls whether or not outlines of the areas are
    187         * drawn, and sends a {@link RendererChangeEvent} to all registered
    188         * listeners.
    189         *
    190         * @param show  the flag.
    191         *
    192         * @see #isOutline()
    193         */
    194        public void setOutline(boolean show) {
    195            this.showOutline = show;
    196            fireChangeEvent();
    197        }
    198
    199        /**
    200         * Returns true if shapes are being plotted by the renderer.
    201         *
    202         * @return <code>true</code> if shapes are being plotted by the renderer.
    203         *
    204         * @see #setShapesVisible(boolean)
    205         */
    206        public boolean getShapesVisible() {
    207            return this.shapesVisible;
    208        }
    209
    210        /**
    211         * Sets the flag that controls whether or not shapes are displayed for each
    212         * data item, and sends a {@link RendererChangeEvent} to all registered
    213         * listeners.
    214         *
    215         * @param flag  the flag.
    216         *
    217         * @see #getShapesVisible()
    218         */
    219        public void setShapesVisible(boolean flag) {
    220            this.shapesVisible = flag;
    221            fireChangeEvent();
    222        }
    223
    224        /**
    225         * Returns the flag that controls whether or not the shapes are filled.
    226         *
    227         * @return A boolean.
    228         *
    229         * @see #setShapesFilled(boolean)
    230         */
    231        public boolean isShapesFilled() {
    232            return this.shapesFilled;
    233        }
    234
    235        /**
    236         * Sets the 'shapes filled' for ALL series and sends a
    237         * {@link RendererChangeEvent} to all registered listeners.
    238         *
    239         * @param filled  the flag.
    240         *
    241         * @see #isShapesFilled()
    242         */
    243        public void setShapesFilled(boolean filled) {
    244            this.shapesFilled = filled;
    245            fireChangeEvent();
    246        }
    247
    248        /**
    249         * Returns true if Area is being plotted by the renderer.
    250         *
    251         * @return <code>true</code> if Area is being plotted by the renderer.
    252         *
    253         * @see #setPlotArea(boolean)
    254         */
    255        public boolean getPlotArea() {
    256            return this.plotArea;
    257        }
    258
    259        /**
    260         * Sets a flag that controls whether or not areas are drawn for each data
    261         * item and sends a {@link RendererChangeEvent} to all registered
    262         * listeners.
    263         *
    264         * @param flag  the flag.
    265         *
    266         * @see #getPlotArea()
    267         */
    268        public void setPlotArea(boolean flag) {
    269            this.plotArea = flag;
    270            fireChangeEvent();
    271        }
    272
    273        /**
    274         * Returns the value on the range axis which defines the 'lower' border of
    275         * the area.
    276         *
    277         * @return <code>double</code> the value on the range axis which defines
    278         *         the 'lower' border of the area.
    279         *
    280         * @see #setRangeBase(double)
    281         */
    282        public double getRangeBase() {
    283            return this.rangeBase;
    284        }
    285
    286        /**
    287         * Sets the value on the range axis which defines the default border of the
    288         * area, and sends a {@link RendererChangeEvent} to all registered
    289         * listeners.  E.g. setRangeBase(Double.NEGATIVE_INFINITY) lets areas always
    290         * reach the lower border of the plotArea.
    291         *
    292         * @param val  the value on the range axis which defines the default border
    293         *             of the area.
    294         *
    295         * @see #getRangeBase()
    296         */
    297        public void setRangeBase(double val) {
    298            this.rangeBase = val;
    299            fireChangeEvent();
    300        }
    301
    302        /**
    303         * Initialises the renderer.  Here we calculate the Java2D y-coordinate for
    304         * zero, since all the bars have their bases fixed at zero.
    305         *
    306         * @param g2  the graphics device.
    307         * @param dataArea  the area inside the axes.
    308         * @param plot  the plot.
    309         * @param data  the data.
    310         * @param info  an optional info collection object to return data back to
    311         *              the caller.
    312         *
    313         * @return The number of passes required by the renderer.
    314         */
    315        public XYItemRendererState initialise(Graphics2D g2,
    316                                              Rectangle2D dataArea,
    317                                              XYPlot plot,
    318                                              XYDataset data,
    319                                              PlotRenderingInfo info) {
    320
    321
    322            XYItemRendererState state = super.initialise(g2, dataArea, plot, data,
    323                    info);
    324            // disable visible items optimisation - it doesn't work for this
    325            // renderer...
    326            state.setProcessVisibleItemsOnly(false);
    327            return state;
    328
    329        }
    330
    331
    332        /**
    333         * Draws the visual representation of a single data item.
    334         *
    335         * @param g2  the graphics device.
    336         * @param state  the renderer state.
    337         * @param dataArea  the area within which the data is being drawn.
    338         * @param info  collects information about the drawing.
    339         * @param plot  the plot (can be used to obtain standard color information
    340         *              etc).
    341         * @param domainAxis  the domain axis.
    342         * @param rangeAxis  the range axis.
    343         * @param dataset  the dataset.
    344         * @param series  the series index (zero-based).
    345         * @param item  the item index (zero-based).
    346         * @param crosshairState  crosshair information for the plot
    347         *                        (<code>null</code> permitted).
    348         * @param pass  the pass index.
    349         */
    350        public void drawItem(Graphics2D g2,
    351                             XYItemRendererState state,
    352                             Rectangle2D dataArea,
    353                             PlotRenderingInfo info,
    354                             XYPlot plot,
    355                             ValueAxis domainAxis,
    356                             ValueAxis rangeAxis,
    357                             XYDataset dataset,
    358                             int series,
    359                             int item,
    360                             CrosshairState crosshairState,
    361                             int pass) {
    362
    363            PlotOrientation orientation = plot.getOrientation();
    364
    365            // Get the item count for the series, so that we can know which is the
    366            // end of the series.
    367            int itemCount = dataset.getItemCount(series);
    368
    369            Paint paint = getItemPaint(series, item);
    370            Stroke seriesStroke = getItemStroke(series, item);
    371            g2.setPaint(paint);
    372            g2.setStroke(seriesStroke);
    373
    374            // get the data point...
    375            double x1 = dataset.getXValue(series, item);
    376            double y1 = dataset.getYValue(series, item);
    377            double x = x1;
    378            double y = Double.isNaN(y1) ? getRangeBase() : y1;
    379            double transX1 = domainAxis.valueToJava2D(x, dataArea,
    380                    plot.getDomainAxisEdge());
    381            double transY1 = rangeAxis.valueToJava2D(y, dataArea,
    382                    plot.getRangeAxisEdge());
    383
    384            // avoid possible sun.dc.pr.PRException: endPath: bad path
    385            transY1 = restrictValueToDataArea(transY1, plot, dataArea);
    386
    387            if (this.pArea == null && !Double.isNaN(y1)) {
    388
    389                // Create a new Area for the series
    390                this.pArea = new Polygon();
    391
    392                // start from Y = rangeBase
    393                double transY2 = rangeAxis.valueToJava2D(getRangeBase(), dataArea,
    394                        plot.getRangeAxisEdge());
    395
    396                // avoid possible sun.dc.pr.PRException: endPath: bad path
    397                transY2 = restrictValueToDataArea(transY2, plot, dataArea);
    398
    399                // The first point is (x, this.baseYValue)
    400                if (orientation == PlotOrientation.VERTICAL) {
    401                    this.pArea.addPoint((int) transX1, (int) transY2);
    402                }
    403                else if (orientation == PlotOrientation.HORIZONTAL) {
    404                    this.pArea.addPoint((int) transY2, (int) transX1);
    405                }
    406            }
    407
    408            double transX0 = 0;
    409            double transY0 = restrictValueToDataArea(getRangeBase(), plot,
    410                    dataArea);
    411
    412            double x0;
    413            double y0;
    414            if (item > 0) {
    415                // get the previous data point...
    416                x0 = dataset.getXValue(series, item - 1);
    417                y0 = Double.isNaN(y1) ? y1 : dataset.getYValue(series, item - 1);
    418
    419                x = x0;
    420                y = Double.isNaN(y0) ? getRangeBase() : y0;
    421                transX0 = domainAxis.valueToJava2D(x, dataArea,
    422                        plot.getDomainAxisEdge());
    423                transY0 = rangeAxis.valueToJava2D(y, dataArea,
    424                        plot.getRangeAxisEdge());
    425
    426                // avoid possible sun.dc.pr.PRException: endPath: bad path
    427                transY0 = restrictValueToDataArea(transY0, plot, dataArea);
    428
    429                if (Double.isNaN(y1)) {
    430                    // NULL value -> insert point on base line
    431                    // instead of 'step point'
    432                    transX1 = transX0;
    433                    transY0 = transY1;
    434                }
    435                if (transY0 != transY1) {
    436                    // not just a horizontal bar but need to perform a 'step'.
    437                    if (orientation == PlotOrientation.VERTICAL) {
    438                        this.pArea.addPoint((int) transX1, (int) transY0);
    439                    }
    440                    else if (orientation == PlotOrientation.HORIZONTAL) {
    441                        this.pArea.addPoint((int) transY0, (int) transX1);
    442                    }
    443                }
    444            }
    445
    446            Shape shape = null;
    447            if (!Double.isNaN(y1)) {
    448                // Add each point to Area (x, y)
    449                if (orientation == PlotOrientation.VERTICAL) {
    450                    this.pArea.addPoint((int) transX1, (int) transY1);
    451                }
    452                else if (orientation == PlotOrientation.HORIZONTAL) {
    453                    this.pArea.addPoint((int) transY1, (int) transX1);
    454                }
    455
    456                if (getShapesVisible()) {
    457                    shape = getItemShape(series, item);
    458                    if (orientation == PlotOrientation.VERTICAL) {
    459                        shape = ShapeUtilities.createTranslatedShape(shape,
    460                                transX1, transY1);
    461                    }
    462                    else if (orientation == PlotOrientation.HORIZONTAL) {
    463                        shape = ShapeUtilities.createTranslatedShape(shape,
    464                                transY1, transX1);
    465                    }
    466                    if (isShapesFilled()) {
    467                        g2.fill(shape);
    468                    }
    469                    else {
    470                        g2.draw(shape);
    471                    }
    472                }
    473                else {
    474                    if (orientation == PlotOrientation.VERTICAL) {
    475                        shape = new Rectangle2D.Double(transX1 - 2, transY1 - 2,
    476                                4.0, 4.0);
    477                    }
    478                    else if (orientation == PlotOrientation.HORIZONTAL) {
    479                        shape = new Rectangle2D.Double(transY1 - 2, transX1 - 2,
    480                                4.0, 4.0);
    481                    }
    482                }
    483            }
    484
    485            // Check if the item is the last item for the series or if it
    486            // is a NULL value and number of items > 0.  We can't draw an area for
    487            // a single point.
    488            if (getPlotArea() && item > 0 && this.pArea != null
    489                              && (item == (itemCount - 1) || Double.isNaN(y1))) {
    490
    491                double transY2 = rangeAxis.valueToJava2D(getRangeBase(), dataArea,
    492                        plot.getRangeAxisEdge());
    493
    494                // avoid possible sun.dc.pr.PRException: endPath: bad path
    495                transY2 = restrictValueToDataArea(transY2, plot, dataArea);
    496
    497                if (orientation == PlotOrientation.VERTICAL) {
    498                    // Add the last point (x,0)
    499                    this.pArea.addPoint((int) transX1, (int) transY2);
    500                }
    501                else if (orientation == PlotOrientation.HORIZONTAL) {
    502                    // Add the last point (x,0)
    503                    this.pArea.addPoint((int) transY2, (int) transX1);
    504                }
    505
    506                // fill the polygon
    507                g2.fill(this.pArea);
    508
    509                // draw an outline around the Area.
    510                if (isOutline()) {
    511                    g2.setStroke(plot.getOutlineStroke());
    512                    g2.setPaint(plot.getOutlinePaint());
    513                    g2.draw(this.pArea);
    514                }
    515
    516                // start new area when needed (see above)
    517                this.pArea = null;
    518            }
    519
    520            // do we need to update the crosshair values?
    521            if (!Double.isNaN(y1)) {
    522                int domainAxisIndex = plot.getDomainAxisIndex(domainAxis);
    523                int rangeAxisIndex = plot.getRangeAxisIndex(rangeAxis);
    524                updateCrosshairValues(crosshairState, x1, y1, domainAxisIndex,
    525                        rangeAxisIndex, transX1, transY1, orientation);
    526            }
    527
    528            // collect entity and tool tip information...
    529            EntityCollection entities = state.getEntityCollection();
    530            if (entities != null) {
    531                addEntity(entities, shape, dataset, series, item, transX1, transY1);
    532            }
    533        }
    534
    535        /**
    536         * Tests this renderer for equality with an arbitrary object.
    537         *
    538         * @param obj  the object (<code>null</code> permitted).
    539         *
    540         * @return A boolean.
    541         */
    542        public boolean equals(Object obj) {
    543            if (obj == this) {
    544                return true;
    545            }
    546            if (!(obj instanceof XYStepAreaRenderer)) {
    547                return false;
    548            }
    549            XYStepAreaRenderer that = (XYStepAreaRenderer) obj;
    550            if (this.showOutline != that.showOutline) {
    551                return false;
    552            }
    553            if (this.shapesVisible != that.shapesVisible) {
    554                return false;
    555            }
    556            if (this.shapesFilled != that.shapesFilled) {
    557                return false;
    558            }
    559            if (this.plotArea != that.plotArea) {
    560                return false;
    561            }
    562            if (this.rangeBase != that.rangeBase) {
    563                return false;
    564            }
    565            return super.equals(obj);
    566        }
    567
    568        /**
    569         * Returns a clone of the renderer.
    570         *
    571         * @return A clone.
    572         *
    573         * @throws CloneNotSupportedException  if the renderer cannot be cloned.
    574         */
    575        public Object clone() throws CloneNotSupportedException {
    576            return super.clone();
    577        }
    578
    579        /**
    580         * Helper method which returns a value if it lies
    581         * inside the visible dataArea and otherwise the corresponding
    582         * coordinate on the border of the dataArea. The PlotOrientation
    583         * is taken into account.
    584         * Useful to avoid possible sun.dc.pr.PRException: endPath: bad path
    585         * which occurs when trying to draw lines/shapes which in large part
    586         * lie outside of the visible dataArea.
    587         *
    588         * @param value the value which shall be
    589         * @param dataArea  the area within which the data is being drawn.
    590         * @param plot  the plot (can be used to obtain standard color
    591         *              information etc).
    592         * @return <code>double</code> value inside the data area.
    593         */
    594        protected static double restrictValueToDataArea(double value,
    595                                                        XYPlot plot,
    596                                                        Rectangle2D dataArea) {
    597            double min = 0;
    598            double max = 0;
    599            if (plot.getOrientation() == PlotOrientation.VERTICAL) {
    600                min = dataArea.getMinY();
    601                max = dataArea.getMaxY();
    602            }
    603            else if (plot.getOrientation() ==  PlotOrientation.HORIZONTAL) {
    604                min = dataArea.getMinX();
    605                max = dataArea.getMaxX();
    606            }
    607            if (value < min) {
    608                value = min;
    609            }
    610            else if (value > max) {
    611                value = max;
    612            }
    613            return value;
    614        }
    615
    616    }