    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     * StackedXYAreaRenderer2.java
    029     * ---------------------------
    030     * (C) Copyright 2004, 2005, by Object Refinery Limited and Contributors.
    031     *
    032     * Original Author:  David Gilbert (for Object Refinery Limited), based on
    033     *                   the StackedXYAreaRenderer class by Richard Atkinson;
    034     * Contributor(s):   -;
    035     *
    036     * $Id: StackedXYAreaRenderer2.java,v 1.6.2.2 2005/10/25 20:56:21 mungady Exp $
    037     *
    038     * Changes:
    039     * --------
    040     * 30-Apr-2004 : Version 1 (DG);
    041     * 15-Jul-2004 : Switched getX() with getXValue() and getY() with
    042     *               getYValue() (DG);
    043     * 10-Sep-2004 : Removed getRangeType() method (DG);
    044     * 06-Jan-2004 : Renamed getRangeExtent() --> findRangeBounds (DG);
    045     * 28-Mar-2005 : Use getXValue() and getYValue() from dataset (DG);
    046     * 03-Oct-2005 : Add entity generation to drawItem() method (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.Shape;
    055    import java.awt.geom.GeneralPath;
    056    import java.awt.geom.Rectangle2D;
    057    import java.io.Serializable;
    058
    059    import org.jfree.chart.axis.ValueAxis;
    060    import org.jfree.chart.entity.EntityCollection;
    061    import org.jfree.chart.labels.XYToolTipGenerator;
    062    import org.jfree.chart.plot.CrosshairState;
    063    import org.jfree.chart.plot.PlotRenderingInfo;
    064    import org.jfree.chart.plot.XYPlot;
    065    import org.jfree.chart.urls.XYURLGenerator;
    066    import org.jfree.data.Range;
    067    import org.jfree.data.xy.TableXYDataset;
    068    import org.jfree.data.xy.XYDataset;
    069    import org.jfree.ui.RectangleEdge;
    070    import org.jfree.util.PublicCloneable;
    071
    072    /**
    073     * A stacked area renderer for the {@link XYPlot} class.
    074     */
    075    public class StackedXYAreaRenderer2 extends XYAreaRenderer2
    076                                        implements Cloneable,
    077                                                   PublicCloneable,
    078                                                   Serializable {
    079
    080        /** For serialization. */
    081        private static final long serialVersionUID = 7752676509764539182L;
    082
    083        /**
    084         * Creates a new renderer.
    085         */
    086        public StackedXYAreaRenderer2() {
    087            this(null, null);
    088        }
    089
    090        /**
    091         * Constructs a new renderer.
    092         *
    093         * @param labelGenerator  the tool tip generator to use.  <code>null</code>
    094         *                        is none.
    095         * @param urlGenerator  the URL generator (<code>null</code> permitted).
    096         */
    097        public StackedXYAreaRenderer2(XYToolTipGenerator labelGenerator,
    098                                      XYURLGenerator urlGenerator) {
    099            super(labelGenerator, urlGenerator);
    100        }
    101
    102        /**
    103         * Returns the range of values the renderer requires to display all the
    104         * items from the specified dataset.
    105         *
    106         * @param dataset  the dataset (<code>null</code> permitted).
    107         *
    108         * @return The range (or <code>null</code> if the dataset is
    109         *         <code>null</code> or empty).
    110         */
    111        public Range findRangeBounds(XYDataset dataset) {
    112            double min = Double.POSITIVE_INFINITY;
    113            double max = Double.NEGATIVE_INFINITY;
    114            TableXYDataset d = (TableXYDataset) dataset;
    115            int itemCount = d.getItemCount();
    116            for (int i = 0; i < itemCount; i++) {
    117                double[] stackValues = getStackValues(
    118                    (TableXYDataset) dataset, d.getSeriesCount(), i
    119                );
    120                min = Math.min(min, stackValues[0]);
    121                max = Math.max(max, stackValues[1]);
    122            }
    123            return new Range(min, max);
    124        }
    125
    126        /**
    127         * Returns the number of passes required by the renderer.
    128         *
    129         * @return 1.
    130         */
    131        public int getPassCount() {
    132            return 1;
    133        }
    134
    135        /**
    136         * Draws the visual representation of a single data item.
    137         *
    138         * @param g2  the graphics device.
    139         * @param state  the renderer state.
    140         * @param dataArea  the area within which the data is being drawn.
    141         * @param info  collects information about the drawing.
    142         * @param plot  the plot (can be used to obtain standard color information
    143         *              etc).
    144         * @param domainAxis  the domain axis.
    145         * @param rangeAxis  the range axis.
    146         * @param dataset  the dataset.
    147         * @param series  the series index (zero-based).
    148         * @param item  the item index (zero-based).
    149         * @param crosshairState  information about crosshairs on a plot.
    150         * @param pass  the pass index.
    151         */
    152        public void drawItem(Graphics2D g2,
    153                             XYItemRendererState state,
    154                             Rectangle2D dataArea,
    155                             PlotRenderingInfo info,
    156                             XYPlot plot,
    157                             ValueAxis domainAxis,
    158                             ValueAxis rangeAxis,
    159                             XYDataset dataset,
    160                             int series,
    161                             int item,
    162                             CrosshairState crosshairState,
    163                             int pass) {
    164
    165            // setup for collecting optional entity info...
    166            Shape entityArea = null;
    167            EntityCollection entities = null;
    168            if (info != null) {
    169                entities = info.getOwner().getEntityCollection();
    170            }
    171
    172            TableXYDataset tdataset = (TableXYDataset) dataset;
    173
    174            // get the data point...
    175            double x1 = dataset.getXValue(series, item);
    176            double y1 = dataset.getYValue(series, item);
    177            if (Double.isNaN(y1)) {
    178                y1 = 0.0;
    179            }
    180            double[] stack1 = getStackValues(tdataset, series, item);
    181
    182            // get the previous point and the next point so we can calculate a
    183            // "hot spot" for the area (used by the chart entity)...
    184            double x0 = dataset.getXValue(series, Math.max(item - 1, 0));
    185            double y0 = dataset.getYValue(series, Math.max(item - 1, 0));
    186            if (Double.isNaN(y0)) {
    187                y0 = 0.0;
    188            }
    189            double[] stack0 = getStackValues(
    190                tdataset, series, Math.max(item - 1, 0)
    191            );
    192
    193            int itemCount = dataset.getItemCount(series);
    194            double x2 = dataset.getXValue(
    195                series, Math.min(item + 1, itemCount - 1)
    196            );
    197            double y2 = dataset.getYValue(
    198                series, Math.min(item + 1, itemCount - 1)
    199            );
    200            if (Double.isNaN(y2)) {
    201                y2 = 0.0;
    202            }
    203            double[] stack2 = getStackValues(
    204                tdataset, series, Math.min(item + 1, itemCount - 1)
    205            );
    206
    207            double xleft = (x0 + x1) / 2.0;
    208            double xright = (x1 + x2) / 2.0;
    209            double[] stackLeft = averageStackValues(stack0, stack1);
    210            double[] stackRight = averageStackValues(stack1, stack2);
    211            double[] adjStackLeft = adjustedStackValues(stack0, stack1);
    212            double[] adjStackRight = adjustedStackValues(stack1, stack2);
    213
    214            RectangleEdge edge0 = plot.getDomainAxisEdge();
    215            float transX1 = (float) domainAxis.valueToJava2D(x1, dataArea, edge0);
    216            float transXLeft
    217                = (float) domainAxis.valueToJava2D(xleft, dataArea, edge0);
    218            float transXRight
    219                = (float) domainAxis.valueToJava2D(xright, dataArea, edge0);
    220            float transY1;
    221
    222            RectangleEdge edge1 = plot.getRangeAxisEdge();
    223
    224            GeneralPath left = new GeneralPath();
    225            GeneralPath right = new GeneralPath();
    226            if (y1 >= 0.0) {  // handle positive value
    227                transY1 = (float) rangeAxis.valueToJava2D(y1 + stack1[1], dataArea,
    228                        edge1);
    229                float transStack1 = (float) rangeAxis.valueToJava2D(
    230                    stack1[1], dataArea, edge1
    231                );
    232                float transStackLeft = (float) rangeAxis.valueToJava2D(
    233                    adjStackLeft[1], dataArea, edge1
    234                );
    235
    236                // LEFT POLYGON
    237                if (y0 >= 0.0) {
    238                    double yleft = (y0 + y1) / 2.0 + stackLeft[1];
    239                    float transYLeft
    240                        = (float) rangeAxis.valueToJava2D(yleft, dataArea, edge1);
    241                    left.moveTo(transX1, transY1);
    242                    left.lineTo(transX1, transStack1);
    243                    left.lineTo(transXLeft, transStackLeft);
    244                    left.lineTo(transXLeft, transYLeft);
    245                    left.closePath();
    246                }
    247                else {
    248                    left.moveTo(transX1, transStack1);
    249                    left.lineTo(transX1, transY1);
    250                    left.lineTo(transXLeft, transStackLeft);
    251                    left.closePath();
    252                }
    253
    254                float transStackRight = (float) rangeAxis.valueToJava2D(
    255                    adjStackRight[1], dataArea, edge1
    256                );
    257                // RIGHT POLYGON
    258                if (y2 >= 0.0) {
    259                    double yright = (y1 + y2) / 2.0 + stackRight[1];
    260                    float transYRight
    261                        = (float) rangeAxis.valueToJava2D(yright, dataArea, edge1);
    262                    right.moveTo(transX1, transStack1);
    263                    right.lineTo(transX1, transY1);
    264                    right.lineTo(transXRight, transYRight);
    265                    right.lineTo(transXRight, transStackRight);
    266                    right.closePath();
    267                }
    268                else {
    269                    right.moveTo(transX1, transStack1);
    270                    right.lineTo(transX1, transY1);
    271                    right.lineTo(transXRight, transStackRight);
    272                    right.closePath();
    273                }
    274            }
    275            else {  // handle negative value
    276                transY1 = (float) rangeAxis.valueToJava2D(y1 + stack1[0], dataArea,
    277                        edge1);
    278                float transStack1 = (float) rangeAxis.valueToJava2D(
    279                    stack1[0], dataArea, edge1
    280                );
    281                float transStackLeft = (float) rangeAxis.valueToJava2D(
    282                    adjStackLeft[0], dataArea, edge1
    283                );
    284
    285                // LEFT POLYGON
    286                if (y0 >= 0.0) {
    287                    left.moveTo(transX1, transStack1);
    288                    left.lineTo(transX1, transY1);
    289                    left.lineTo(transXLeft, transStackLeft);
    290                    left.clone();
    291                }
    292                else {
    293                    double yleft = (y0 + y1) / 2.0 + stackLeft[0];
    294                    float transYLeft = (float) rangeAxis.valueToJava2D(
    295                        yleft, dataArea, edge1
    296                    );
    297                    left.moveTo(transX1, transY1);
    298                    left.lineTo(transX1, transStack1);
    299                    left.lineTo(transXLeft, transStackLeft);
    300                    left.lineTo(transXLeft, transYLeft);
    301                    left.closePath();
    302                }
    303                float transStackRight = (float) rangeAxis.valueToJava2D(
    304                    adjStackRight[0], dataArea, edge1
    305                );
    306
    307                // RIGHT POLYGON
    308                if (y2 >= 0.0) {
    309                    right.moveTo(transX1, transStack1);
    310                    right.lineTo(transX1, transY1);
    311                    right.lineTo(transXRight, transStackRight);
    312                    right.closePath();
    313                }
    314                else {
    315                    double yright = (y1 + y2) / 2.0 + stackRight[0];
    316                    float transYRight = (float) rangeAxis.valueToJava2D(
    317                        yright, dataArea, edge1
    318                    );
    319                    right.moveTo(transX1, transStack1);
    320                    right.lineTo(transX1, transY1);
    321                    right.lineTo(transXRight, transYRight);
    322                    right.lineTo(transXRight, transStackRight);
    323                    right.closePath();
    324                }
    325            }
    326
    327            //  Get series Paint and Stroke
    328            Paint itemPaint = getItemPaint(series, item);
    329            if (pass == 0) {
    330                g2.setPaint(itemPaint);
    331                g2.fill(left);
    332                g2.fill(right);
    333            }
    334
    335            // add an entity for the item...
    336            if (entities != null) {
    337                GeneralPath gp = new GeneralPath(left);
    338                gp.append(right, false);
    339                entityArea = gp;
    340                addEntity(entities, entityArea, dataset, series, item,
    341                        transX1, transY1);
    342            }
    343
    344        }
    345
    346        /**
    347         * Calculates the stacked value of the all series up to, but not including
    348         * <code>series</code> for the specified item. It returns 0.0 if
    349         * <code>series</code> is the first series, i.e. 0.
    350         *
    351         * @param dataset  the dataset.
    352         * @param series  the series.
    353         * @param index  the index.
    354         *
    355         * @return The cumulative value for all series' values up to but excluding
    356         *         <code>series</code> for <code>index</code>.
    357         */
    358        private double[] getStackValues(TableXYDataset dataset,
    359                                        int series, int index) {
    360            double[] result = new double[2];
    361            for (int i = 0; i < series; i++) {
    362                double v = dataset.getYValue(i, index);
    363                if (!Double.isNaN(v)) {
    364                    if (v >= 0.0) {
    365                        result[1] += v;
    366                    }
    367                    else {
    368                        result[0] += v;
    369                    }
    370                }
    371            }
    372            return result;
    373        }
    374
    375        /**
    376         * Returns a pair of "stack" values calculated from the two specified pairs.
    377         *
    378         * @param stack1  the first stack pair.
    379         * @param stack2  the second stack pair.
    380         *
    381         * @return A pair of average stack values.
    382         */
    383        private double[] averageStackValues(double[] stack1, double[] stack2) {
    384            double[] result = new double[2];
    385            result[0] = (stack1[0] + stack2[0]) / 2.0;
    386            result[1] = (stack1[1] + stack2[1]) / 2.0;
    387            return result;
    388        }
    389
    390        /**
    391         * Returns a pair of "stack" values calculated from the two specified pairs.
    392         *
    393         * @param stack1  the first stack pair.
    394         * @param stack2  the second stack pair.
    395         *
    396         * @return A pair of average stack values.
    397         */
    398        private double[] adjustedStackValues(double[] stack1, double[] stack2) {
    399            double[] result = new double[2];
    400            if (stack1[0] == 0.0 || stack2[0] == 0.0) {
    401                result[0] = 0.0;
    402            }
    403            else {
    404                result[0] = (stack1[0] + stack2[0]) / 2.0;
    405            }
    406            if (stack1[1] == 0.0 || stack2[1] == 0.0) {
    407                result[1] = 0.0;
    408            }
    409            else {
    410                result[1] = (stack1[1] + stack2[1]) / 2.0;
    411            }
    412            return result;
    413        }
    414
    415        /**
    416         * Returns a clone of the renderer.
    417         *
    418         * @return A clone.
    419         *
    420         * @throws CloneNotSupportedException  if the renderer cannot be cloned.
    421         */
    422        public Object clone() throws CloneNotSupportedException {
    423            return super.clone();
    424        }
    425
    426    }