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