    001    /* ===========================================================
    002     * JFreeChart : a free chart library for the Java(tm) platform
    003     * ===========================================================
    004     *
    005     * (C) Copyright 2000-2009, 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-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
    044    package org.jfree.chart.renderer.xy;
    045
    046    import java.awt.AlphaComposite;
    047    import java.awt.Composite;
    048    import java.awt.Graphics2D;
    049    import java.awt.geom.GeneralPath;
    050    import java.awt.geom.Rectangle2D;
    051    import java.util.List;
    052
    053    import org.jfree.chart.axis.ValueAxis;
    054    import org.jfree.chart.entity.EntityCollection;
    055    import org.jfree.chart.event.RendererChangeEvent;
    056    import org.jfree.chart.plot.CrosshairState;
    057    import org.jfree.chart.plot.PlotOrientation;
    058    import org.jfree.chart.plot.PlotRenderingInfo;
    059    import org.jfree.chart.plot.XYPlot;
    060    import org.jfree.data.Range;
    061    import org.jfree.data.general.DatasetUtilities;
    062    import org.jfree.data.xy.IntervalXYDataset;
    063    import org.jfree.data.xy.XYDataset;
    064    import 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     */
    079    public 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    }