    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 * DeviationRenderer.java
    029 * ----------------------
    030 * (C) Copyright 2007-2009, by Object Refinery Limited and Contributors.
    031 *
    032 * Original Author:  David Gilbert (for Object Refinery Limited);
    033 * Contributor(s):   -;
    034 *
    035 * Changes
    036 * -------
    037 * 21-Feb-2007 : Version 1 (DG);
    038 * 04-May-2007 : Set processVisibleItemsOnly flag to false (DG);
    039 * 11-Apr-2008 : New override for findRangeBounds() (DG);
    040 * 27-Mar-2009 : Updated findRangeBounds() to call new inherited method (DG);
    041 *
    042 */
    043
    044package org.jfree.chart.renderer.xy;
    045
    046import java.awt.AlphaComposite;
    047import java.awt.Composite;
    048import java.awt.Graphics2D;
    049import java.awt.geom.GeneralPath;
    050import java.awt.geom.Rectangle2D;
    051import java.util.List;
    052
    053import org.jfree.chart.axis.ValueAxis;
    054import org.jfree.chart.entity.EntityCollection;
    055import org.jfree.chart.event.RendererChangeEvent;
    056import org.jfree.chart.plot.CrosshairState;
    057import org.jfree.chart.plot.PlotOrientation;
    058import org.jfree.chart.plot.PlotRenderingInfo;
    059import org.jfree.chart.plot.XYPlot;
    060import org.jfree.data.Range;
    061import org.jfree.data.general.DatasetUtilities;
    062import org.jfree.data.xy.IntervalXYDataset;
    063import org.jfree.data.xy.XYDataset;
    064import org.jfree.ui.RectangleEdge;
    065
    066/**
    067 * A specialised subclass of the {@link XYLineAndShapeRenderer} that requires
    068 * an {@link IntervalXYDataset} and represents the y-interval by shading an
    069 * area behind the y-values on the chart.
    070 * The example shown here is generated by the
    071 * <code>DeviationRendererDemo1.java</code> program included in the
    072 * JFreeChart demo collection:
    073 * <br><br>
    074 * <img src="../../../../../images/DeviationRendererSample.png"
    075 * alt="DeviationRendererSample.png" />
    076 *
    077 * @since 1.0.5
    078 */
    079public class DeviationRenderer extends XYLineAndShapeRenderer {
    080
    081    /**
    082     * A state object that is passed to each call to <code>drawItem</code>.
    083     */
    084    public static class State extends XYLineAndShapeRenderer.State {
    085
    086        /**
    087         * A list of coordinates for the upper y-values in the current series
    088         * (after translation into Java2D space).
    089         */
    090        public List upperCoordinates;
    091
    092        /**
    093         * A list of coordinates for the lower y-values in the current series
    094         * (after translation into Java2D space).
    095         */
    096        public List lowerCoordinates;
    097
    098        /**
    099         * Creates a new state instance.
    100         *
    101         * @param info  the plot rendering info.
    102         */
    103        public State(PlotRenderingInfo info) {
    104            super(info);
    105            this.lowerCoordinates = new java.util.ArrayList();
    106            this.upperCoordinates = new java.util.ArrayList();
    107        }
    108
    109    }
    110
    111    /** The alpha transparency for the interval shading. */
    112    private float alpha;
    113
    114    /**
    115     * Creates a new renderer that displays lines and shapes for the data
    116     * items, as well as the shaded area for the y-interval.
    117     */
    118    public DeviationRenderer() {
    119        this(true, true);
    120    }
    121
    122    /**
    123     * Creates a new renderer.
    124     *
    125     * @param lines  show lines between data items?
    126     * @param shapes  show a shape for each data item?
    127     */
    128    public DeviationRenderer(boolean lines, boolean shapes) {
    129        super(lines, shapes);
    130        super.setDrawSeriesLineAsPath(true);
    131        this.alpha = 0.5f;
    132    }
    133
    134    /**
    135     * Returns the alpha transparency for the background shading.
    136     *
    137     * @return The alpha transparency.
    138     *
    139     * @see #setAlpha(float)
    140     */
    141    public float getAlpha() {
    142        return this.alpha;
    143    }
    144
    145    /**
    146     * Sets the alpha transparency for the background shading, and sends a
    147     * {@link RendererChangeEvent} to all registered listeners.
    148     *
    149     * @param alpha   the alpha (in the range 0.0f to 1.0f).
    150     *
    151     * @see #getAlpha()
    152     */
    153    public void setAlpha(float alpha) {
    154        if (alpha < 0.0f || alpha > 1.0f) {
    155            throw new IllegalArgumentException(
    156                    "Requires 'alpha' in the range 0.0 to 1.0.");
    157        }
    158        this.alpha = alpha;
    159        fireChangeEvent();
    160    }
    161
    162    /**
    163     * This method is overridden so that this flag cannot be changed---it is
    164     * set to <code>true</code> for this renderer.
    165     *
    166     * @param flag  ignored.
    167     */
    168    public void setDrawSeriesLineAsPath(boolean flag) {
    169        // ignore
    170    }
    171
    172    /**
    173     * Returns the range of values the renderer requires to display all the
    174     * items from the specified dataset.
    175     *
    176     * @param dataset  the dataset (<code>null</code> permitted).
    177     *
    178     * @return The range (<code>null</code> if the dataset is <code>null</code>
    179     *         or empty).
    180     */
    181    public Range findRangeBounds(XYDataset dataset) {
    182        return findRangeBounds(dataset, true);
    183    }
    184
    185    /**
    186     * Initialises and returns a state object that can be passed to each
    187     * invocation of the {@link #drawItem} method.
    188     *
    189     * @param g2  the graphics target.
    190     * @param dataArea  the data area.
    191     * @param plot  the plot.
    192     * @param dataset  the dataset.
    193     * @param info  the plot rendering info.
    194     *
    195     * @return A newly initialised state object.
    196     */
    197    public XYItemRendererState initialise(Graphics2D g2, Rectangle2D dataArea,
    198            XYPlot plot, XYDataset dataset, PlotRenderingInfo info) {
    199        State state = new State(info);
    200        state.seriesPath = new GeneralPath();
    201        state.setProcessVisibleItemsOnly(false);
    202        return state;
    203    }
    204
    205    /**
    206     * Returns the number of passes (through the dataset) used by this
    207     * renderer.
    208     *
    209     * @return <code>3</code>.
    210     */
    211    public int getPassCount() {
    212        return 3;
    213    }
    214
    215    /**
    216     * Returns <code>true</code> if this is the pass where the shapes are
    217     * drawn.
    218     *
    219     * @param pass  the pass index.
    220     *
    221     * @return A boolean.
    222     *
    223     * @see #isLinePass(int)
    224     */
    225    protected boolean isItemPass(int pass) {
    226        return (pass == 2);
    227    }
    228
    229    /**
    230     * Returns <code>true</code> if this is the pass where the lines are
    231     * drawn.
    232     *
    233     * @param pass  the pass index.
    234     *
    235     * @return A boolean.
    236     *
    237     * @see #isItemPass(int)
    238     */
    239    protected boolean isLinePass(int pass) {
    240        return (pass == 1);
    241    }
    242
    243    /**
    244     * Draws the visual representation of a single data item.
    245     *
    246     * @param g2  the graphics device.
    247     * @param state  the renderer state.
    248     * @param dataArea  the area within which the data is being drawn.
    249     * @param info  collects information about the drawing.
    250     * @param plot  the plot (can be used to obtain standard color
    251     *              information etc).
    252     * @param domainAxis  the domain axis.
    253     * @param rangeAxis  the range axis.
    254     * @param dataset  the dataset.
    255     * @param series  the series index (zero-based).
    256     * @param item  the item index (zero-based).
    257     * @param crosshairState  crosshair information for the plot
    258     *                        (<code>null</code> permitted).
    259     * @param pass  the pass index.
    260     */
    261    public void drawItem(Graphics2D g2,
    262                         XYItemRendererState state,
    263                         Rectangle2D dataArea,
    264                         PlotRenderingInfo info,
    265                         XYPlot plot,
    266                         ValueAxis domainAxis,
    267                         ValueAxis rangeAxis,
    268                         XYDataset dataset,
    269                         int series,
    270                         int item,
    271                         CrosshairState crosshairState,
    272                         int pass) {
    273
    274        // do nothing if item is not visible
    275        if (!getItemVisible(series, item)) {
    276            return;
    277        }
    278
    279        // first pass draws the shading
    280        if (pass == 0) {
    281            IntervalXYDataset intervalDataset = (IntervalXYDataset) dataset;
    282            State drState = (State) state;
    283
    284            double x = intervalDataset.getXValue(series, item);
    285            double yLow = intervalDataset.getStartYValue(series, item);
    286            double yHigh  = intervalDataset.getEndYValue(series, item);
    287
    288            RectangleEdge xAxisLocation = plot.getDomainAxisEdge();
    289            RectangleEdge yAxisLocation = plot.getRangeAxisEdge();
    290
    291            double xx = domainAxis.valueToJava2D(x, dataArea, xAxisLocation);
    292            double yyLow = rangeAxis.valueToJava2D(yLow, dataArea,
    293                    yAxisLocation);
    294            double yyHigh = rangeAxis.valueToJava2D(yHigh, dataArea,
    295                    yAxisLocation);
    296
    297            PlotOrientation orientation = plot.getOrientation();
    298            if (orientation == PlotOrientation.HORIZONTAL) {
    299                drState.lowerCoordinates.add(new double[] {yyLow, xx});
    300                drState.upperCoordinates.add(new double[] {yyHigh, xx});
    301            }
    302            else if (orientation == PlotOrientation.VERTICAL) {
    303                drState.lowerCoordinates.add(new double[] {xx, yyLow});
    304                drState.upperCoordinates.add(new double[] {xx, yyHigh});
    305            }
    306
    307            if (item == (dataset.getItemCount(series) - 1)) {
    308                // last item in series, draw the lot...
    309                // set up the alpha-transparency...
    310                Composite originalComposite = g2.getComposite();
    311                g2.setComposite(AlphaComposite.getInstance(
    312                        AlphaComposite.SRC_OVER, this.alpha));
    313                g2.setPaint(getItemFillPaint(series, item));
    314                GeneralPath area = new GeneralPath();
    315                double[] coords = (double[]) drState.lowerCoordinates.get(0);
    316                area.moveTo((float) coords[0], (float) coords[1]);
    317                for (int i = 1; i < drState.lowerCoordinates.size(); i++) {
    318                    coords = (double[]) drState.lowerCoordinates.get(i);
    319                    area.lineTo((float) coords[0], (float) coords[1]);
    320                }
    321                int count = drState.upperCoordinates.size();
    322                coords = (double[]) drState.upperCoordinates.get(count - 1);
    323                area.lineTo((float) coords[0], (float) coords[1]);
    324                for (int i = count - 2; i >= 0; i--) {
    325                    coords = (double[]) drState.upperCoordinates.get(i);
    326                    area.lineTo((float) coords[0], (float) coords[1]);
    327                }
    328                area.closePath();
    329                g2.fill(area);
    330                g2.setComposite(originalComposite);
    331
    332                drState.lowerCoordinates.clear();
    333                drState.upperCoordinates.clear();
    334            }
    335        }
    336        if (isLinePass(pass)) {
    337
    338            // the following code handles the line for the y-values...it's
    339            // all done by code in the super class
    340            if (item == 0) {
    341                State s = (State) state;
    342                s.seriesPath.reset();
    343                s.setLastPointGood(false);
    344            }
    345
    346            if (getItemLineVisible(series, item)) {
    347                drawPrimaryLineAsPath(state, g2, plot, dataset, pass,
    348                        series, item, domainAxis, rangeAxis, dataArea);
    349            }
    350        }
    351
    352        // second pass adds shapes where the items are ..
    353        else if (isItemPass(pass)) {
    354
    355            // setup for collecting optional entity info...
    356            EntityCollection entities = null;
    357            if (info != null) {
    358                entities = info.getOwner().getEntityCollection();
    359            }
    360
    361            drawSecondaryPass(g2, plot, dataset, pass, series, item,
    362                    domainAxis, dataArea, rangeAxis, crosshairState, entities);
    363        }
    364    }
    365
    366    /**
    367     * Tests this renderer for equality with an arbitrary object.
    368     *
    369     * @param obj  the object (<code>null</code> permitted).
    370     *
    371     * @return A boolean.
    372     */
    373    public boolean equals(Object obj) {
    374        if (obj == this) {
    375            return true;
    376        }
    377        if (!(obj instanceof DeviationRenderer)) {
    378            return false;
    379        }
    380        DeviationRenderer that = (DeviationRenderer) obj;
    381        if (this.alpha != that.alpha) {
    382            return false;
    383        }
    384        return super.equals(obj);
    385    }
    386
    387}