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