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