    001/* ===========================================================
    002 * JFreeChart : a free chart library for the Java(tm) platform
    003 * ===========================================================
    004 *
    005 * (C) Copyright 2000-2011, 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 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates.
    025 * Other names may be trademarks of their respective owners.]
    026 *
    027 * ---------------------------
    028 * StackedXYAreaRenderer2.java
    029 * ---------------------------
    030 * (C) Copyright 2004-2008, 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 * Changes:
    037 * --------
    038 * 30-Apr-2004 : Version 1 (DG);
    039 * 15-Jul-2004 : Switched getX() with getXValue() and getY() with
    040 *               getYValue() (DG);
    041 * 10-Sep-2004 : Removed getRangeType() method (DG);
    042 * 06-Jan-2004 : Renamed getRangeExtent() --> findRangeBounds (DG);
    043 * 28-Mar-2005 : Use getXValue() and getYValue() from dataset (DG);
    044 * 03-Oct-2005 : Add entity generation to drawItem() method (DG);
    045 * ------------- JFREECHART 1.0.x ---------------------------------------------
    046 * 22-Aug-2006 : Handle null and empty datasets correctly in the
    047 *               findRangeBounds() method (DG);
    048 * 22-Sep-2006 : Added a flag to allow rounding of x-coordinates (after
    049 *               translation to Java2D space) in order to avoid the striping
    050 *               that can result from anti-aliasing (thanks to Doug
    051 *               Clayton) (DG);
    052 * 30-Nov-2006 : Added accessor methods for the roundXCoordinates flag (DG);
    053 * 02-Jun-2008 : Fixed bug with PlotOrientation.HORIZONTAL (DG);
    054 *
    055 */
    056
    057package org.jfree.chart.renderer.xy;
    058
    059import java.awt.Graphics2D;
    060import java.awt.Paint;
    061import java.awt.Shape;
    062import java.awt.geom.GeneralPath;
    063import java.awt.geom.Rectangle2D;
    064import java.io.Serializable;
    065
    066import org.jfree.chart.axis.ValueAxis;
    067import org.jfree.chart.entity.EntityCollection;
    068import org.jfree.chart.event.RendererChangeEvent;
    069import org.jfree.chart.labels.XYToolTipGenerator;
    070import org.jfree.chart.plot.CrosshairState;
    071import org.jfree.chart.plot.PlotOrientation;
    072import org.jfree.chart.plot.PlotRenderingInfo;
    073import org.jfree.chart.plot.XYPlot;
    074import org.jfree.chart.urls.XYURLGenerator;
    075import org.jfree.data.Range;
    076import org.jfree.data.xy.TableXYDataset;
    077import org.jfree.data.xy.XYDataset;
    078import org.jfree.ui.RectangleEdge;
    079import org.jfree.util.PublicCloneable;
    080
    081/**
    082 * A stacked area renderer for the {@link XYPlot} class.
    083 * The example shown here is generated by the
    084 * <code>StackedXYAreaChartDemo2.java</code> program included in the
    085 * JFreeChart demo collection:
    086 * <br><br>
    087 * <img src="../../../../../images/StackedXYAreaRenderer2Sample.png"
    088 * alt="StackedXYAreaRenderer2Sample.png" />
    089 */
    090public class StackedXYAreaRenderer2 extends XYAreaRenderer2
    091        implements Cloneable, PublicCloneable, Serializable {
    092
    093    /** For serialization. */
    094    private static final long serialVersionUID = 7752676509764539182L;
    095
    096    /**
    097     * This flag controls whether or not the x-coordinates (in Java2D space)
    098     * are rounded to integers.  When set to true, this can avoid the vertical
    099     * striping that anti-aliasing can generate.  However, the rounding may not
    100     * be appropriate for output in high resolution formats (for example,
    101     * vector graphics formats such as SVG and PDF).
    102     *
    103     * @since 1.0.3
    104     */
    105    private boolean roundXCoordinates;
    106
    107    /**
    108     * Creates a new renderer.
    109     */
    110    public StackedXYAreaRenderer2() {
    111        this(null, null);
    112    }
    113
    114    /**
    115     * Constructs a new renderer.
    116     *
    117     * @param labelGenerator  the tool tip generator to use.  <code>null</code>
    118     *                        is none.
    119     * @param urlGenerator  the URL generator (<code>null</code> permitted).
    120     */
    121    public StackedXYAreaRenderer2(XYToolTipGenerator labelGenerator,
    122                                  XYURLGenerator urlGenerator) {
    123        super(labelGenerator, urlGenerator);
    124        this.roundXCoordinates = true;
    125    }
    126
    127    /**
    128     * Returns the flag that controls whether or not the x-coordinates (in
    129     * Java2D space) are rounded to integer values.
    130     *
    131     * @return The flag.
    132     *
    133     * @since 1.0.4
    134     *
    135     * @see #setRoundXCoordinates(boolean)
    136     */
    137    public boolean getRoundXCoordinates() {
    138        return this.roundXCoordinates;
    139    }
    140
    141    /**
    142     * Sets the flag that controls whether or not the x-coordinates (in
    143     * Java2D space) are rounded to integer values, and sends a
    144     * {@link RendererChangeEvent} to all registered listeners.
    145     *
    146     * @param round  the new flag value.
    147     *
    148     * @since 1.0.4
    149     *
    150     * @see #getRoundXCoordinates()
    151     */
    152    public void setRoundXCoordinates(boolean round) {
    153        this.roundXCoordinates = round;
    154        fireChangeEvent();
    155    }
    156
    157    /**
    158     * Returns the range of values the renderer requires to display all the
    159     * items from the specified dataset.
    160     *
    161     * @param dataset  the dataset (<code>null</code> permitted).
    162     *
    163     * @return The range (or <code>null</code> if the dataset is
    164     *         <code>null</code> or empty).
    165     */
    166    public Range findRangeBounds(XYDataset dataset) {
    167        if (dataset == null) {
    168            return null;
    169        }
    170        double min = Double.POSITIVE_INFINITY;
    171        double max = Double.NEGATIVE_INFINITY;
    172        TableXYDataset d = (TableXYDataset) dataset;
    173        int itemCount = d.getItemCount();
    174        for (int i = 0; i < itemCount; i++) {
    175            double[] stackValues = getStackValues((TableXYDataset) dataset,
    176                    d.getSeriesCount(), i);
    177            min = Math.min(min, stackValues[0]);
    178            max = Math.max(max, stackValues[1]);
    179        }
    180        if (min == Double.POSITIVE_INFINITY) {
    181            return null;
    182        }
    183        return new Range(min, max);
    184    }
    185
    186    /**
    187     * Returns the number of passes required by the renderer.
    188     *
    189     * @return 1.
    190     */
    191    public int getPassCount() {
    192        return 1;
    193    }
    194
    195    /**
    196     * Draws the visual representation of a single data item.
    197     *
    198     * @param g2  the graphics device.
    199     * @param state  the renderer state.
    200     * @param dataArea  the area within which the data is being drawn.
    201     * @param info  collects information about the drawing.
    202     * @param plot  the plot (can be used to obtain standard color information
    203     *              etc).
    204     * @param domainAxis  the domain axis.
    205     * @param rangeAxis  the range axis.
    206     * @param dataset  the dataset.
    207     * @param series  the series index (zero-based).
    208     * @param item  the item index (zero-based).
    209     * @param crosshairState  information about crosshairs on a plot.
    210     * @param pass  the pass index.
    211     */
    212    public void drawItem(Graphics2D g2,
    213                         XYItemRendererState state,
    214                         Rectangle2D dataArea,
    215                         PlotRenderingInfo info,
    216                         XYPlot plot,
    217                         ValueAxis domainAxis,
    218                         ValueAxis rangeAxis,
    219                         XYDataset dataset,
    220                         int series,
    221                         int item,
    222                         CrosshairState crosshairState,
    223                         int pass) {
    224
    225        // setup for collecting optional entity info...
    226        Shape entityArea = null;
    227        EntityCollection entities = null;
    228        if (info != null) {
    229            entities = info.getOwner().getEntityCollection();
    230        }
    231
    232        TableXYDataset tdataset = (TableXYDataset) dataset;
    233        PlotOrientation orientation = plot.getOrientation();
    234
    235        // get the data point...
    236        double x1 = dataset.getXValue(series, item);
    237        double y1 = dataset.getYValue(series, item);
    238        if (Double.isNaN(y1)) {
    239            y1 = 0.0;
    240        }
    241        double[] stack1 = getStackValues(tdataset, series, item);
    242
    243        // get the previous point and the next point so we can calculate a
    244        // "hot spot" for the area (used by the chart entity)...
    245        double x0 = dataset.getXValue(series, Math.max(item - 1, 0));
    246        double y0 = dataset.getYValue(series, Math.max(item - 1, 0));
    247        if (Double.isNaN(y0)) {
    248            y0 = 0.0;
    249        }
    250        double[] stack0 = getStackValues(tdataset, series, Math.max(item - 1,
    251                0));
    252
    253        int itemCount = dataset.getItemCount(series);
    254        double x2 = dataset.getXValue(series, Math.min(item + 1,
    255                itemCount - 1));
    256        double y2 = dataset.getYValue(series, Math.min(item + 1,
    257                itemCount - 1));
    258        if (Double.isNaN(y2)) {
    259            y2 = 0.0;
    260        }
    261        double[] stack2 = getStackValues(tdataset, series, Math.min(item + 1,
    262                itemCount - 1));
    263
    264        double xleft = (x0 + x1) / 2.0;
    265        double xright = (x1 + x2) / 2.0;
    266        double[] stackLeft = averageStackValues(stack0, stack1);
    267        double[] stackRight = averageStackValues(stack1, stack2);
    268        double[] adjStackLeft = adjustedStackValues(stack0, stack1);
    269        double[] adjStackRight = adjustedStackValues(stack1, stack2);
    270
    271        RectangleEdge edge0 = plot.getDomainAxisEdge();
    272
    273        float transX1 = (float) domainAxis.valueToJava2D(x1, dataArea, edge0);
    274        float transXLeft = (float) domainAxis.valueToJava2D(xleft, dataArea,
    275                edge0);
    276        float transXRight = (float) domainAxis.valueToJava2D(xright, dataArea,
    277                edge0);
    278
    279        if (this.roundXCoordinates) {
    280            transX1 = Math.round(transX1);
    281            transXLeft = Math.round(transXLeft);
    282            transXRight = Math.round(transXRight);
    283        }
    284        float transY1;
    285
    286        RectangleEdge edge1 = plot.getRangeAxisEdge();
    287
    288        GeneralPath left = new GeneralPath();
    289        GeneralPath right = new GeneralPath();
    290        if (y1 >= 0.0) {  // handle positive value
    291            transY1 = (float) rangeAxis.valueToJava2D(y1 + stack1[1], dataArea,
    292                    edge1);
    293            float transStack1 = (float) rangeAxis.valueToJava2D(stack1[1],
    294                    dataArea, edge1);
    295            float transStackLeft = (float) rangeAxis.valueToJava2D(
    296                    adjStackLeft[1], dataArea, edge1);
    297
    298            // LEFT POLYGON
    299            if (y0 >= 0.0) {
    300                double yleft = (y0 + y1) / 2.0 + stackLeft[1];
    301                float transYLeft
    302                    = (float) rangeAxis.valueToJava2D(yleft, dataArea, edge1);
    303                if (orientation == PlotOrientation.VERTICAL) {
    304                    left.moveTo(transX1, transY1);
    305                    left.lineTo(transX1, transStack1);
    306                    left.lineTo(transXLeft, transStackLeft);
    307                    left.lineTo(transXLeft, transYLeft);
    308                }
    309                else {
    310                    left.moveTo(transY1, transX1);
    311                    left.lineTo(transStack1, transX1);
    312                    left.lineTo(transStackLeft, transXLeft);
    313                    left.lineTo(transYLeft, transXLeft);
    314                }
    315                left.closePath();
    316            }
    317            else {
    318                if (orientation == PlotOrientation.VERTICAL) {
    319                    left.moveTo(transX1, transStack1);
    320                    left.lineTo(transX1, transY1);
    321                    left.lineTo(transXLeft, transStackLeft);
    322                }
    323                else {
    324                    left.moveTo(transStack1, transX1);
    325                    left.lineTo(transY1, transX1);
    326                    left.lineTo(transStackLeft, transXLeft);
    327                }
    328                left.closePath();
    329            }
    330
    331            float transStackRight = (float) rangeAxis.valueToJava2D(
    332                    adjStackRight[1], dataArea, edge1);
    333            // RIGHT POLYGON
    334            if (y2 >= 0.0) {
    335                double yright = (y1 + y2) / 2.0 + stackRight[1];
    336                float transYRight
    337                    = (float) rangeAxis.valueToJava2D(yright, dataArea, edge1);
    338                if (orientation == PlotOrientation.VERTICAL) {
    339                    right.moveTo(transX1, transStack1);
    340                    right.lineTo(transX1, transY1);
    341                    right.lineTo(transXRight, transYRight);
    342                    right.lineTo(transXRight, transStackRight);
    343                }
    344                else {
    345                    right.moveTo(transStack1, transX1);
    346                    right.lineTo(transY1, transX1);
    347                    right.lineTo(transYRight, transXRight);
    348                    right.lineTo(transStackRight, transXRight);
    349                }
    350                right.closePath();
    351            }
    352            else {
    353                if (orientation == PlotOrientation.VERTICAL) {
    354                    right.moveTo(transX1, transStack1);
    355                    right.lineTo(transX1, transY1);
    356                    right.lineTo(transXRight, transStackRight);
    357                }
    358                else {
    359                    right.moveTo(transStack1, transX1);
    360                    right.lineTo(transY1, transX1);
    361                    right.lineTo(transStackRight, transXRight);
    362                }
    363                right.closePath();
    364            }
    365        }
    366        else {  // handle negative value
    367            transY1 = (float) rangeAxis.valueToJava2D(y1 + stack1[0], dataArea,
    368                    edge1);
    369            float transStack1 = (float) rangeAxis.valueToJava2D(stack1[0],
    370                    dataArea, edge1);
    371            float transStackLeft = (float) rangeAxis.valueToJava2D(
    372                    adjStackLeft[0], dataArea, edge1);
    373
    374            // LEFT POLYGON
    375            if (y0 >= 0.0) {
    376                if (orientation == PlotOrientation.VERTICAL) {
    377                    left.moveTo(transX1, transStack1);
    378                    left.lineTo(transX1, transY1);
    379                    left.lineTo(transXLeft, transStackLeft);
    380                }
    381                else {
    382                    left.moveTo(transStack1, transX1);
    383                    left.lineTo(transY1, transX1);
    384                    left.lineTo(transStackLeft, transXLeft);
    385                }
    386                left.clone();
    387            }
    388            else {
    389                double yleft = (y0 + y1) / 2.0 + stackLeft[0];
    390                float transYLeft = (float) rangeAxis.valueToJava2D(yleft,
    391                        dataArea, edge1);
    392                if (orientation == PlotOrientation.VERTICAL) {
    393                    left.moveTo(transX1, transY1);
    394                    left.lineTo(transX1, transStack1);
    395                    left.lineTo(transXLeft, transStackLeft);
    396                    left.lineTo(transXLeft, transYLeft);
    397                }
    398                else {
    399                    left.moveTo(transY1, transX1);
    400                    left.lineTo(transStack1, transX1);
    401                    left.lineTo(transStackLeft, transXLeft);
    402                    left.lineTo(transYLeft, transXLeft);
    403                }
    404                left.closePath();
    405            }
    406            float transStackRight = (float) rangeAxis.valueToJava2D(
    407                    adjStackRight[0], dataArea, edge1);
    408
    409            // RIGHT POLYGON
    410            if (y2 >= 0.0) {
    411                if (orientation == PlotOrientation.VERTICAL) {
    412                    right.moveTo(transX1, transStack1);
    413                    right.lineTo(transX1, transY1);
    414                    right.lineTo(transXRight, transStackRight);
    415                }
    416                else {
    417                    right.moveTo(transStack1, transX1);
    418                    right.lineTo(transY1, transX1);
    419                    right.lineTo(transStackRight, transXRight);
    420                }
    421                right.closePath();
    422            }
    423            else {
    424                double yright = (y1 + y2) / 2.0 + stackRight[0];
    425                float transYRight = (float) rangeAxis.valueToJava2D(yright,
    426                        dataArea, edge1);
    427                if (orientation == PlotOrientation.VERTICAL) {
    428                    right.moveTo(transX1, transStack1);
    429                    right.lineTo(transX1, transY1);
    430                    right.lineTo(transXRight, transYRight);
    431                    right.lineTo(transXRight, transStackRight);
    432                }
    433                else {
    434                    right.moveTo(transStack1, transX1);
    435                    right.lineTo(transY1, transX1);
    436                    right.lineTo(transYRight, transXRight);
    437                    right.lineTo(transStackRight, transXRight);
    438                }
    439                right.closePath();
    440            }
    441        }
    442
    443        //  Get series Paint and Stroke
    444        Paint itemPaint = getItemPaint(series, item);
    445        if (pass == 0) {
    446            g2.setPaint(itemPaint);
    447            g2.fill(left);
    448            g2.fill(right);
    449        }
    450
    451        // add an entity for the item...
    452        if (entities != null) {
    453            GeneralPath gp = new GeneralPath(left);
    454            gp.append(right, false);
    455            entityArea = gp;
    456            addEntity(entities, entityArea, dataset, series, item,
    457                    transX1, transY1);
    458        }
    459
    460    }
    461
    462    /**
    463     * Calculates the stacked values (one positive and one negative) of all
    464     * series up to, but not including, <code>series</code> for the specified
    465     * item. It returns [0.0, 0.0] if <code>series</code> is the first series.
    466     *
    467     * @param dataset  the dataset (<code>null</code> not permitted).
    468     * @param series  the series index.
    469     * @param index  the item index.
    470     *
    471     * @return An array containing the cumulative negative and positive values
    472     *     for all series values up to but excluding <code>series</code>
    473     *     for <code>index</code>.
    474     */
    475    private double[] getStackValues(TableXYDataset dataset,
    476                                    int series, int index) {
    477        double[] result = new double[2];
    478        for (int i = 0; i < series; i++) {
    479            double v = dataset.getYValue(i, index);
    480            if (!Double.isNaN(v)) {
    481                if (v >= 0.0) {
    482                    result[1] += v;
    483                }
    484                else {
    485                    result[0] += v;
    486                }
    487            }
    488        }
    489        return result;
    490    }
    491
    492    /**
    493     * Returns a pair of "stack" values calculated as the mean of the two
    494     * specified stack value pairs.
    495     *
    496     * @param stack1  the first stack pair.
    497     * @param stack2  the second stack pair.
    498     *
    499     * @return A pair of average stack values.
    500     */
    501    private double[] averageStackValues(double[] stack1, double[] stack2) {
    502        double[] result = new double[2];
    503        result[0] = (stack1[0] + stack2[0]) / 2.0;
    504        result[1] = (stack1[1] + stack2[1]) / 2.0;
    505        return result;
    506    }
    507
    508    /**
    509     * Calculates adjusted stack values from the supplied values.  The value is
    510     * the mean of the supplied values, unless either of the supplied values
    511     * is zero, in which case the adjusted value is zero also.
    512     *
    513     * @param stack1  the first stack pair.
    514     * @param stack2  the second stack pair.
    515     *
    516     * @return A pair of average stack values.
    517     */
    518    private double[] adjustedStackValues(double[] stack1, double[] stack2) {
    519        double[] result = new double[2];
    520        if (stack1[0] == 0.0 || stack2[0] == 0.0) {
    521            result[0] = 0.0;
    522        }
    523        else {
    524            result[0] = (stack1[0] + stack2[0]) / 2.0;
    525        }
    526        if (stack1[1] == 0.0 || stack2[1] == 0.0) {
    527            result[1] = 0.0;
    528        }
    529        else {
    530            result[1] = (stack1[1] + stack2[1]) / 2.0;
    531        }
    532        return result;
    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 StackedXYAreaRenderer2)) {
    547            return false;
    548        }
    549        StackedXYAreaRenderer2 that = (StackedXYAreaRenderer2) obj;
    550        if (this.roundXCoordinates != that.roundXCoordinates) {
    551            return false;
    552        }
    553        return super.equals(obj);
    554    }
    555
    556    /**
    557     * Returns a clone of the renderer.
    558     *
    559     * @return A clone.
    560     *
    561     * @throws CloneNotSupportedException  if the renderer cannot be cloned.
    562     */
    563    public Object clone() throws CloneNotSupportedException {
    564        return super.clone();
    565    }
    566
    567}