    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     * StackedXYAreaRenderer.java
    029     * --------------------------
    030     * (C) Copyright 2003-2005, by Richard Atkinson and Contributors.
    031     *
    032     * Original Author:  Richard Atkinson;
    033     * Contributor(s):   Christian W. Zuckschwerdt;
    034     *                   David Gilbert (for Object Refinery Limited);
    035     *
    036     * $Id: StackedXYAreaRenderer.java,v 1.12.2.5 2006/01/27 13:52:12 mungady Exp $
    037     *
    038     * Changes:
    039     * --------
    040     * 27-Jul-2003 : Initial version (RA);
    041     * 30-Jul-2003 : Modified entity constructor (CZ);
    042     * 18-Aug-2003 : Now handles null values (RA);
    043     * 20-Aug-2003 : Implemented Cloneable, PublicCloneable and Serializable (DG);
    044     * 22-Sep-2003 : Changed to be a two pass renderer with optional shape Paint
    045     *               and Stroke (RA);
    046     * 07-Oct-2003 : Added renderer state (DG);
    047     * 10-Feb-2004 : Updated state object and changed drawItem() method to make
    048     *               overriding easier (DG);
    049     * 25-Feb-2004 : Replaced CrosshairInfo with CrosshairState.  Renamed
    050     *               XYToolTipGenerator --> XYItemLabelGenerator (DG);
    051     * 15-Jul-2004 : Switched getX() with getXValue() and getY() with
    052     *               getYValue() (DG);
    053     * 10-Sep-2004 : Removed getRangeType() method (DG);
    054     * 11-Nov-2004 : Now uses ShapeUtilities to translate shapes (DG);
    055     * 06-Jan-2005 : Override equals() (DG);
    056     * 07-Jan-2005 : Update for method name changes in DatasetUtilities (DG);
    057     * 28-Mar-2005 : Use getXValue() and getYValue() from dataset (DG);
    058     * 06-Jun-2005 : Fixed null pointer exception, plus problems with equals() and
    059     *               serialization (DG);
    060     *
    061     */
    062
    063    package org.jfree.chart.renderer.xy;
    064
    065    import java.awt.Graphics2D;
    066    import java.awt.Paint;
    067    import java.awt.Point;
    068    import java.awt.Polygon;
    069    import java.awt.Shape;
    070    import java.awt.Stroke;
    071    import java.awt.geom.Line2D;
    072    import java.awt.geom.Rectangle2D;
    073    import java.io.IOException;
    074    import java.io.ObjectInputStream;
    075    import java.io.ObjectOutputStream;
    076    import java.io.Serializable;
    077    import java.util.Stack;
    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.labels.XYToolTipGenerator;
    083    import org.jfree.chart.plot.CrosshairState;
    084    import org.jfree.chart.plot.PlotOrientation;
    085    import org.jfree.chart.plot.PlotRenderingInfo;
    086    import org.jfree.chart.plot.XYPlot;
    087    import org.jfree.chart.urls.XYURLGenerator;
    088    import org.jfree.data.Range;
    089    import org.jfree.data.general.DatasetUtilities;
    090    import org.jfree.data.xy.TableXYDataset;
    091    import org.jfree.data.xy.XYDataset;
    092    import org.jfree.io.SerialUtilities;
    093    import org.jfree.util.ObjectUtilities;
    094    import org.jfree.util.PaintUtilities;
    095    import org.jfree.util.PublicCloneable;
    096    import org.jfree.util.ShapeUtilities;
    097
    098    /**
    099     * A stacked area renderer for the {@link XYPlot} class.
    100     */
    101    public class StackedXYAreaRenderer extends XYAreaRenderer
    102                                       implements Cloneable,
    103                                                  PublicCloneable,
    104                                                  Serializable {
    105
    106        /** For serialization. */
    107        private static final long serialVersionUID = 5217394318178570889L;
    108
    109         /**
    110         * A state object for use by this renderer.
    111         */
    112        static class StackedXYAreaRendererState extends XYItemRendererState {
    113
    114            /** The area for the current series. */
    115            private Polygon seriesArea;
    116
    117            /** The line. */
    118            private Line2D line;
    119
    120            /** The points from the last series. */
    121            private Stack lastSeriesPoints;
    122
    123            /** The points for the current series. */
    124            private Stack currentSeriesPoints;
    125
    126            /**
    127             * Creates a new state for the renderer.
    128             *
    129             * @param info  the plot rendering info.
    130             */
    131            public StackedXYAreaRendererState(PlotRenderingInfo info) {
    132                super(info);
    133                this.seriesArea = null;
    134                this.line = null;
    135                this.lastSeriesPoints = new Stack();
    136                this.currentSeriesPoints = new Stack();
    137            }
    138
    139            /**
    140             * Returns the series area.
    141             *
    142             * @return The series area.
    143             */
    144            public Polygon getSeriesArea() {
    145                return this.seriesArea;
    146            }
    147
    148            /**
    149             * Sets the series area.
    150             *
    151             * @param area  the area.
    152             */
    153            public void setSeriesArea(Polygon area) {
    154                this.seriesArea = area;
    155            }
    156
    157            /**
    158             * Returns the working line.
    159             *
    160             * @return The working line.
    161             */
    162            public Line2D getLine() {
    163                return this.line;
    164            }
    165
    166            /**
    167             * Returns the current series points.
    168             *
    169             * @return The current series points.
    170             */
    171            public Stack getCurrentSeriesPoints() {
    172                return this.currentSeriesPoints;
    173            }
    174
    175            /**
    176             * Sets the current series points.
    177             *
    178             * @param points  the points.
    179             */
    180            public void setCurrentSeriesPoints(Stack points) {
    181                this.currentSeriesPoints = points;
    182            }
    183
    184            /**
    185             * Returns the last series points.
    186             *
    187             * @return The last series points.
    188             */
    189            public Stack getLastSeriesPoints() {
    190                return this.lastSeriesPoints;
    191            }
    192
    193            /**
    194             * Sets the last series points.
    195             *
    196             * @param points  the points.
    197             */
    198            public void setLastSeriesPoints(Stack points) {
    199                this.lastSeriesPoints = points;
    200            }
    201
    202        }
    203
    204        /**
    205         * Custom Paint for drawing all shapes, if null defaults to series shapes
    206         */
    207        private transient Paint shapePaint = null;
    208
    209        /**
    210         * Custom Stroke for drawing all shapes, if null defaults to series
    211         * strokes.
    212         */
    213        private transient Stroke shapeStroke = null;
    214
    215        /**
    216         * Creates a new renderer.
    217         */
    218        public StackedXYAreaRenderer() {
    219            this(AREA);
    220        }
    221        /**
    222         * Constructs a new renderer.
    223         *
    224         * @param type  the type of the renderer.
    225         */
    226        public StackedXYAreaRenderer(int type) {
    227            this(type, null, null);
    228        }
    229
    230        /**
    231         * Constructs a new renderer.  To specify the type of renderer, use one of
    232         * the constants: <code>SHAPES</code>, <code>LINES</code>,
    233         * <code>SHAPES_AND_LINES</code>, <code>AREA</code> or
    234         * <code>AREA_AND_SHAPES</code>.
    235         *
    236         * @param type  the type of renderer.
    237         * @param labelGenerator  the tool tip generator to use (<code>null</code>
    238         *                        is none).
    239         * @param urlGenerator  the URL generator (<code>null</code> permitted).
    240         */
    241        public StackedXYAreaRenderer(int type,
    242                                     XYToolTipGenerator labelGenerator,
    243                                     XYURLGenerator urlGenerator) {
    244
    245            super(type, labelGenerator, urlGenerator);
    246        }
    247
    248        /**
    249         * Returns the paint used for rendering shapes, or <code>null</code> if
    250         * using series paints.
    251         *
    252         * @return The Paint.
    253         */
    254        public Paint getShapePaint() {
    255            return this.shapePaint;
    256        }
    257
    258        /**
    259         * Returns the stroke used for rendering shapes, or <code>null</code> if
    260         * using series strokes.
    261         *
    262         * @return The stroke.
    263         */
    264        public Stroke getShapeStroke() {
    265            return this.shapeStroke;
    266        }
    267
    268        /**
    269         * Sets the paint for rendering shapes.
    270         *
    271         * @param shapePaint  the paint (<code>null</code> permitted).
    272         */
    273        public void setShapePaint(Paint shapePaint) {
    274            this.shapePaint = shapePaint;
    275        }
    276
    277        /**
    278         * Sets the stroke for rendering shapes.
    279         *
    280         * @param shapeStroke  the stroke (<code>null</code> permitted).
    281         */
    282        public void setShapeStroke(Stroke shapeStroke) {
    283            this.shapeStroke = shapeStroke;
    284        }
    285
    286        /**
    287         * Initialises the renderer. This method will be called before the first
    288         * item is rendered, giving the renderer an opportunity to initialise any
    289         * state information it wants to maintain.
    290         *
    291         * @param g2  the graphics device.
    292         * @param dataArea  the area inside the axes.
    293         * @param plot  the plot.
    294         * @param data  the data.
    295         * @param info  an optional info collection object to return data back to
    296         *              the caller.
    297         *
    298         * @return A state object that should be passed to subsequent calls to the
    299         *         drawItem() method.
    300         */
    301        public XYItemRendererState initialise(Graphics2D g2,
    302                                              Rectangle2D dataArea,
    303                                              XYPlot plot,
    304                                              XYDataset data,
    305                                              PlotRenderingInfo info) {
    306
    307            return new StackedXYAreaRendererState(info);
    308
    309        }
    310
    311        /**
    312         * Returns the number of passes required by the renderer.
    313         *
    314         * @return 2.
    315         */
    316        public int getPassCount() {
    317            return 2;
    318        }
    319
    320        /**
    321         * Returns the range of values the renderer requires to display all the
    322         * items from the specified dataset.
    323         *
    324         * @param dataset  the dataset (<code>null</code> permitted).
    325         *
    326         * @return The range ([0.0, 0.0] if the dataset contains no values, and
    327         *         <code>null</code> if the dataset is <code>null</code>).
    328         *
    329         * @throws ClassCastException if <code>dataset</code> is not an instance
    330         *         of {@link TableXYDataset}.
    331         */
    332        public Range findRangeBounds(XYDataset dataset) {
    333            if (dataset != null) {
    334                return DatasetUtilities.findStackedRangeBounds(
    335                    (TableXYDataset) dataset);
    336            }
    337            else {
    338                return null;
    339            }
    340        }
    341
    342        /**
    343         * Draws the visual representation of a single data item.
    344         *
    345         * @param g2  the graphics device.
    346         * @param state  the renderer state.
    347         * @param dataArea  the area within which the data is being drawn.
    348         * @param info  collects information about the drawing.
    349         * @param plot  the plot (can be used to obtain standard color information
    350         *              etc).
    351         * @param domainAxis  the domain axis.
    352         * @param rangeAxis  the range axis.
    353         * @param dataset  the dataset.
    354         * @param series  the series index (zero-based).
    355         * @param item  the item index (zero-based).
    356         * @param crosshairState  information about crosshairs on a plot.
    357         * @param pass  the pass index.
    358         *
    359         * @throws ClassCastException if <code>state</code> is not an instance of
    360         *         <code>StackedXYAreaRendererState</code> or <code>dataset</code>
    361         *         is not an instance of {@link TableXYDataset}.
    362         */
    363        public void drawItem(Graphics2D g2,
    364                             XYItemRendererState state,
    365                             Rectangle2D dataArea,
    366                             PlotRenderingInfo info,
    367                             XYPlot plot,
    368                             ValueAxis domainAxis,
    369                             ValueAxis rangeAxis,
    370                             XYDataset dataset,
    371                             int series,
    372                             int item,
    373                             CrosshairState crosshairState,
    374                             int pass) {
    375
    376            PlotOrientation orientation = plot.getOrientation();
    377            StackedXYAreaRendererState areaState
    378                = (StackedXYAreaRendererState) state;
    379            // Get the item count for the series, so that we can know which is the
    380            // end of the series.
    381            TableXYDataset tdataset = (TableXYDataset) dataset;
    382            int itemCount = tdataset.getItemCount();
    383
    384            // get the data point...
    385            double x1 = dataset.getXValue(series, item);
    386            double y1 = dataset.getYValue(series, item);
    387            boolean nullPoint = false;
    388            if (Double.isNaN(y1)) {
    389                y1 = 0.0;
    390                nullPoint = true;
    391            }
    392
    393            //  Get height adjustment based on stack and translate to Java2D values
    394            double ph1 = getPreviousHeight(tdataset, series, item);
    395            double transX1 = domainAxis.valueToJava2D(x1, dataArea,
    396                    plot.getDomainAxisEdge());
    397            double transY1 = rangeAxis.valueToJava2D(y1 + ph1, dataArea,
    398                    plot.getRangeAxisEdge());
    399
    400            //  Get series Paint and Stroke
    401            Paint seriesPaint = getItemPaint(series, item);
    402            Stroke seriesStroke = getItemStroke(series, item);
    403
    404            if (pass == 0) {
    405                //  On first pass render the areas, line and outlines
    406
    407                if (item == 0) {
    408                    // Create a new Area for the series
    409                    areaState.setSeriesArea(new Polygon());
    410                    areaState.setLastSeriesPoints(
    411                            areaState.getCurrentSeriesPoints());
    412                    areaState.setCurrentSeriesPoints(new Stack());
    413
    414                    // start from previous height (ph1)
    415                    double transY2 = rangeAxis.valueToJava2D(ph1, dataArea,
    416                            plot.getRangeAxisEdge());
    417
    418                    // The first point is (x, 0)
    419                    if (orientation == PlotOrientation.VERTICAL) {
    420                        areaState.getSeriesArea().addPoint((int) transX1,
    421                                (int) transY2);
    422                    }
    423                    else if (orientation == PlotOrientation.HORIZONTAL) {
    424                        areaState.getSeriesArea().addPoint((int) transY2,
    425                                (int) transX1);
    426                    }
    427                }
    428
    429                // Add each point to Area (x, y)
    430                if (orientation == PlotOrientation.VERTICAL) {
    431                    Point point = new Point((int) transX1, (int) transY1);
    432                    areaState.getSeriesArea().addPoint((int) point.getX(),
    433                            (int) point.getY());
    434                    areaState.getCurrentSeriesPoints().push(point);
    435                }
    436                else if (orientation == PlotOrientation.HORIZONTAL) {
    437                    areaState.getSeriesArea().addPoint((int) transY1,
    438                            (int) transX1);
    439                }
    440
    441                if (getPlotLines()) {
    442                    if (item > 0) {
    443                        // get the previous data point...
    444                        double x0 = dataset.getXValue(series, item - 1);
    445                        double y0 = dataset.getYValue(series, item - 1);
    446                        double ph0 = getPreviousHeight(tdataset, series, item - 1);
    447                        double transX0 = domainAxis.valueToJava2D(x0, dataArea,
    448                                plot.getDomainAxisEdge());
    449                        double transY0 = rangeAxis.valueToJava2D(y0 + ph0,
    450                                dataArea, plot.getRangeAxisEdge());
    451
    452                        if (orientation == PlotOrientation.VERTICAL) {
    453                            areaState.getLine().setLine(transX0, transY0, transX1,
    454                                    transY1);
    455                        }
    456                        else if (orientation == PlotOrientation.HORIZONTAL) {
    457                            areaState.getLine().setLine(transY0, transX0, transY1,
    458                                    transX1);
    459                        }
    460                        g2.draw(areaState.getLine());
    461                    }
    462                }
    463
    464                // Check if the item is the last item for the series and number of
    465                // items > 0.  We can't draw an area for a single point.
    466                if (getPlotArea() && item > 0 && item == (itemCount - 1)) {
    467
    468                    double transY2 = rangeAxis.valueToJava2D(ph1, dataArea,
    469                            plot.getRangeAxisEdge());
    470
    471                    if (orientation == PlotOrientation.VERTICAL) {
    472                        // Add the last point (x,0)
    473                        areaState.getSeriesArea().addPoint((int) transX1,
    474                                (int) transY2);
    475                    }
    476                    else if (orientation == PlotOrientation.HORIZONTAL) {
    477                        // Add the last point (x,0)
    478                        areaState.getSeriesArea().addPoint((int) transY2,
    479                                (int) transX1);
    480                    }
    481
    482                    // Add points from last series to complete the base of the
    483                    // polygon
    484                    if (series != 0) {
    485                        Stack points = areaState.getLastSeriesPoints();
    486                        while (!points.empty()) {
    487                            Point point = (Point) points.pop();
    488                            areaState.getSeriesArea().addPoint((int) point.getX(),
    489                                    (int) point.getY());
    490                        }
    491                    }
    492
    493                    //  Fill the polygon
    494                    g2.setPaint(seriesPaint);
    495                    g2.setStroke(seriesStroke);
    496                    g2.fill(areaState.getSeriesArea());
    497
    498                    //  Draw an outline around the Area.
    499                    if (isOutline()) {
    500                        g2.setStroke(getSeriesOutlineStroke(series));
    501                        g2.setPaint(getSeriesOutlinePaint(series));
    502                        g2.draw(areaState.getSeriesArea());
    503                    }
    504                }
    505
    506                updateCrosshairValues(crosshairState, x1, y1, transX1, transY1,
    507                        orientation);
    508
    509            }
    510            else if (pass == 1) {
    511                // On second pass render shapes and collect entity and tooltip
    512                // information
    513
    514                Shape shape = null;
    515                if (getPlotShapes()) {
    516                    shape = getItemShape(series, item);
    517                    if (plot.getOrientation() == PlotOrientation.VERTICAL) {
    518                        shape = ShapeUtilities.createTranslatedShape(shape,
    519                                transX1, transY1);
    520                    }
    521                    else if (plot.getOrientation() == PlotOrientation.HORIZONTAL) {
    522                        shape = ShapeUtilities.createTranslatedShape(shape,
    523                                transY1, transX1);
    524                    }
    525                    if (!nullPoint) {
    526                        if (getShapePaint() != null) {
    527                            g2.setPaint(getShapePaint());
    528                        }
    529                        else {
    530                            g2.setPaint(seriesPaint);
    531                        }
    532                        if (getShapeStroke() != null) {
    533                            g2.setStroke(getShapeStroke());
    534                        }
    535                        else {
    536                            g2.setStroke(seriesStroke);
    537                        }
    538                        g2.draw(shape);
    539                    }
    540                }
    541                else {
    542                    if (plot.getOrientation() == PlotOrientation.VERTICAL) {
    543                        shape = new Rectangle2D.Double(transX1 - 3, transY1 - 3,
    544                                6.0, 6.0);
    545                    }
    546                    else if (plot.getOrientation() == PlotOrientation.HORIZONTAL) {
    547                        shape = new Rectangle2D.Double(transY1 - 3, transX1 - 3,
    548                                6.0, 6.0);
    549                    }
    550                }
    551
    552                // collect entity and tool tip information...
    553                if (state.getInfo() != null) {
    554                    EntityCollection entities = state.getEntityCollection();
    555                    if (entities != null && shape != null && !nullPoint) {
    556                        String tip = null;
    557                        XYToolTipGenerator generator
    558                            = getToolTipGenerator(series, item);
    559                        if (generator != null) {
    560                            tip = generator.generateToolTip(dataset, series, item);
    561                        }
    562                        String url = null;
    563                        if (getURLGenerator() != null) {
    564                            url = getURLGenerator().generateURL(dataset, series,
    565                                    item);
    566                        }
    567                        XYItemEntity entity = new XYItemEntity(shape, dataset,
    568                                series, item, tip, url);
    569                        entities.add(entity);
    570                    }
    571                }
    572
    573            }
    574        }
    575
    576        /**
    577         * Calculates the stacked value of the all series up to, but not including
    578         * <code>series</code> for the specified item. It returns 0.0 if
    579         * <code>series</code> is the first series, i.e. 0.
    580         *
    581         * @param dataset  the dataset.
    582         * @param series  the series.
    583         * @param index  the index.
    584         *
    585         * @return The cumulative value for all series' values up to but excluding
    586         *         <code>series</code> for <code>index</code>.
    587         */
    588        protected double getPreviousHeight(TableXYDataset dataset,
    589                                           int series, int index) {
    590            double result = 0.0;
    591            for (int i = 0; i < series; i++) {
    592                double value = dataset.getYValue(i, index);
    593                if (!Double.isNaN(value)) {
    594                    result += value;
    595                }
    596            }
    597            return result;
    598        }
    599
    600        /**
    601         * Tests the renderer for equality with an arbitrary object.
    602         *
    603         * @param obj  the object (<code>null</code> permitted).
    604         *
    605         * @return A boolean.
    606         */
    607        public boolean equals(Object obj) {
    608            if (obj == this) {
    609                return true;
    610            }
    611            if (!(obj instanceof StackedXYAreaRenderer) || !super.equals(obj)) {
    612                return false;
    613            }
    614            StackedXYAreaRenderer that = (StackedXYAreaRenderer) obj;
    615            if (!PaintUtilities.equal(this.shapePaint, that.shapePaint)) {
    616                return false;
    617            }
    618            if (!ObjectUtilities.equal(this.shapeStroke, that.shapeStroke)) {
    619                return false;
    620            }
    621            return true;
    622        }
    623
    624        /**
    625         * Returns a clone of the renderer.
    626         *
    627         * @return A clone.
    628         *
    629         * @throws CloneNotSupportedException if the renderer cannot be cloned.
    630         */
    631        public Object clone() throws CloneNotSupportedException {
    632            return super.clone();
    633        }
    634
    635        /**
    636         * Provides serialization support.
    637         *
    638         * @param stream  the input stream.
    639         *
    640         * @throws IOException  if there is an I/O error.
    641         * @throws ClassNotFoundException  if there is a classpath problem.
    642         */
    643        private void readObject(ObjectInputStream stream)
    644                throws IOException, ClassNotFoundException {
    645            stream.defaultReadObject();
    646            this.shapePaint = SerialUtilities.readPaint(stream);
    647            this.shapeStroke = SerialUtilities.readStroke(stream);
    648        }
    649
    650        /**
    651         * Provides serialization support.
    652         *
    653         * @param stream  the output stream.
    654         *
    655         * @throws IOException  if there is an I/O error.
    656         */
    657        private void writeObject(ObjectOutputStream stream) throws IOException {
    658            stream.defaultWriteObject();
    659            SerialUtilities.writePaint(this.shapePaint, stream);
    660            SerialUtilities.writeStroke(this.shapeStroke, stream);
    661        }
    662
    663    }