    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     * $Id: DeviationRenderer.java,v 1.1.2.2 2007/03/15 12:48:56 mungady Exp $
    036     *
    037     * Changes
    038     * -------
    039     * 21-Feb-2007 : Version 1 (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.xy.IntervalXYDataset;
    060    import org.jfree.data.xy.XYDataset;
    061    import org.jfree.ui.RectangleEdge;
    062
    063    /**
    064     * A specialised subclass of the {@link XYLineAndShapeRenderer} that requires
    065     * an {@link IntervalXYDataset} and represents the y-interval by shading an
    066     * area behind the y-values on the chart.
    067     *
    068     * @since 1.0.5
    069     */
    070    public class DeviationRenderer extends XYLineAndShapeRenderer {
    071
    072        /**
    073         * A state object that is passed to each call to <code>drawItem</code>.
    074         */
    075        public static class State extends XYLineAndShapeRenderer.State {
    076
    077            /**
    078             * A list of coordinates for the upper y-values in the current series
    079             * (after translation into Java2D space).
    080             */
    081            public List upperCoordinates;
    082
    083            /**
    084             * A list of coordinates for the lower y-values in the current series
    085             * (after translation into Java2D space).
    086             */
    087            public List lowerCoordinates;
    088
    089            /**
    090             * Creates a new state instance.
    091             *
    092             * @param info  the plot rendering info.
    093             */
    094            public State(PlotRenderingInfo info) {
    095                super(info);
    096                this.lowerCoordinates = new java.util.ArrayList();
    097                this.upperCoordinates = new java.util.ArrayList();
    098            }
    099
    100        }
    101
    102        /** The alpha transparency for the interval shading. */
    103        private float alpha;
    104
    105        /**
    106         * Creates a new renderer that displays lines and shapes for the data
    107         * items, as well as the shaded area for the y-interval.
    108         */
    109        public DeviationRenderer() {
    110            this(true, true);
    111        }
    112
    113        /**
    114         * Creates a new renderer.
    115         *
    116         * @param lines  show lines between data items?
    117         * @param shapes  show a shape for each data item?
    118         */
    119        public DeviationRenderer(boolean lines, boolean shapes) {
    120            super(lines, shapes);
    121            super.setDrawSeriesLineAsPath(true);
    122            this.alpha = 0.5f;
    123        }
    124
    125        /**
    126         * Returns the alpha transparency for the background shading.
    127         *
    128         * @return The alpha transparency.
    129         *
    130         * @see #setAlpha(float)
    131         */
    132        public float getAlpha() {
    133            return this.alpha;
    134        }
    135
    136        /**
    137         * Sets the alpha transparency for the background shading, and sends a
    138         * {@link RendererChangeEvent} to all registered listeners.
    139         *
    140         * @param alpha   the alpha (in the range 0.0f to 1.0f).
    141         *
    142         * @see #getAlpha()
    143         */
    144        public void setAlpha(float alpha) {
    145            if (alpha < 0.0f || alpha > 1.0f) {
    146                throw new IllegalArgumentException(
    147                        "Requires 'alpha' in the range 0.0 to 1.0.");
    148            }
    149            this.alpha = alpha;
    150            notifyListeners(new RendererChangeEvent(this));
    151        }
    152
    153        /**
    154         * This method is overridden so that this flag cannot be changed---it is
    155         * set to <code>true</code> for this renderer.
    156         *
    157         * @param flag  ignored.
    158         */
    159        public void setDrawSeriesLineAsPath(boolean flag) {
    160            // ignore
    161        }
    162
    163        /**
    164         * Initialises and returns a state object that can be passed to each
    165         * invocation of the {@link #drawItem} method.
    166         *
    167         * @param g2  the graphics target.
    168         * @param dataArea  the data area.
    169         * @param plot  the plot.
    170         * @param dataset  the dataset.
    171         * @param info  the plot rendering info.
    172         *
    173         * @return A newly initialised state object.
    174         */
    175        public XYItemRendererState initialise(Graphics2D g2, Rectangle2D dataArea,
    176                XYPlot plot, XYDataset dataset, PlotRenderingInfo info) {
    177            State state = new State(info);
    178            state.seriesPath = new GeneralPath();
    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    }