    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     * XYBoxAndWhiskerRenderer.java
    029     * ----------------------------
    030     * (C) Copyright 2003-2008, by David Browning and Contributors.
    031     *
    032     * Original Author:  David Browning (for Australian Institute of Marine
    033     *                   Science);
    034     * Contributor(s):   David Gilbert (for Object Refinery Limited);
    035     *
    036     * Changes
    037     * -------
    038     * 05-Aug-2003 : Version 1, contributed by David Browning.  Based on code in the
    039     *               CandlestickRenderer class.  Additional modifications by David
    040     *               Gilbert to make the code work with 0.9.10 changes (DG);
    041     * 08-Aug-2003 : Updated some of the Javadoc
    042     *               Allowed BoxAndwhiskerDataset Average value to be null - the
    043     *               average value is an AIMS requirement
    044     *               Allow the outlier and farout coefficients to be set - though
    045     *               at the moment this only affects the calculation of farouts.
    046     *               Added artifactPaint variable and setter/getter
    047     * 12-Aug-2003   Rewrote code to sort out and process outliers to take
    048     *               advantage of changes in DefaultBoxAndWhiskerDataset
    049     *               Added a limit of 10% for width of box should no width be
    050     *               specified...maybe this should be setable???
    051     * 20-Aug-2003 : Implemented Cloneable and PublicCloneable (DG);
    052     * 08-Sep-2003 : Changed ValueAxis API (DG);
    053     * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG);
    054     * 25-Feb-2004 : Replaced CrosshairInfo with CrosshairState (DG);
    055     * 23-Apr-2004 : Added fillBox attribute, extended equals() method and fixed
    056     *               serialization issue (DG);
    057     * 29-Apr-2004 : Fixed problem with drawing upper and lower shadows - bug id
    058     *               944011 (DG);
    059     * 15-Jul-2004 : Switched getX() with getXValue() and getY() with
    060     *               getYValue() (DG);
    061     * 01-Oct-2004 : Renamed 'paint' --> 'boxPaint' to avoid conflict with
    062     *               inherited attribute (DG);
    063     * 10-Jun-2005 : Updated equals() to handle GradientPaint (DG);
    064     * 06-Oct-2005 : Removed setPaint() call in drawItem(), it is causing a
    065     *               loop (DG);
    066     * ------------- JFREECHART 1.0.x ---------------------------------------------
    067     * 02-Feb-2007 : Removed author tags from all over JFreeChart sources (DG);
    068     * 05-Feb-2007 : Added event notifications and fixed drawing for horizontal
    069     *               plot orientation (DG);
    070     * 13-Jun-2007 : Replaced deprecated method call (DG);
    071     * 03-Jan-2008 : Check visibility of average marker before drawing it (DG);
    072     * 27-Mar-2008 : If boxPaint is null, revert to itemPaint (DG);
    073     *
    074     */
    075
    076    package org.jfree.chart.renderer.xy;
    077
    078    import java.awt.Color;
    079    import java.awt.Graphics2D;
    080    import java.awt.Paint;
    081    import java.awt.Shape;
    082    import java.awt.Stroke;
    083    import java.awt.geom.Ellipse2D;
    084    import java.awt.geom.Line2D;
    085    import java.awt.geom.Point2D;
    086    import java.awt.geom.Rectangle2D;
    087    import java.io.IOException;
    088    import java.io.ObjectInputStream;
    089    import java.io.ObjectOutputStream;
    090    import java.io.Serializable;
    091    import java.util.ArrayList;
    092    import java.util.Collections;
    093    import java.util.Iterator;
    094    import java.util.List;
    095
    096    import org.jfree.chart.axis.ValueAxis;
    097    import org.jfree.chart.entity.EntityCollection;
    098    import org.jfree.chart.event.RendererChangeEvent;
    099    import org.jfree.chart.labels.BoxAndWhiskerXYToolTipGenerator;
    100    import org.jfree.chart.plot.CrosshairState;
    101    import org.jfree.chart.plot.PlotOrientation;
    102    import org.jfree.chart.plot.PlotRenderingInfo;
    103    import org.jfree.chart.plot.XYPlot;
    104    import org.jfree.chart.renderer.Outlier;
    105    import org.jfree.chart.renderer.OutlierList;
    106    import org.jfree.chart.renderer.OutlierListCollection;
    107    import org.jfree.data.statistics.BoxAndWhiskerXYDataset;
    108    import org.jfree.data.xy.XYDataset;
    109    import org.jfree.io.SerialUtilities;
    110    import org.jfree.ui.RectangleEdge;
    111    import org.jfree.util.PaintUtilities;
    112    import org.jfree.util.PublicCloneable;
    113
    114    /**
    115     * A renderer that draws box-and-whisker items on an {@link XYPlot}.  This
    116     * renderer requires a {@link BoxAndWhiskerXYDataset}).
    117     * <P>
    118     * This renderer does not include any code to calculate the crosshair point.
    119     */
    120    public class XYBoxAndWhiskerRenderer extends AbstractXYItemRenderer
    121                                         implements XYItemRenderer,
    122                                                    Cloneable,
    123                                                    PublicCloneable,
    124                                                    Serializable {
    125
    126        /** For serialization. */
    127        private static final long serialVersionUID = -8020170108532232324L;
    128
    129        /** The box width. */
    130        private double boxWidth;
    131
    132        /** The paint used to fill the box. */
    133        private transient Paint boxPaint;
    134
    135        /** A flag that controls whether or not the box is filled. */
    136        private boolean fillBox;
    137
    138        /**
    139         * The paint used to draw various artifacts such as outliers, farout
    140         * symbol, average ellipse and median line.
    141         */
    142        private transient Paint artifactPaint = Color.black;
    143
    144        /**
    145         * Creates a new renderer for box and whisker charts.
    146         */
    147        public XYBoxAndWhiskerRenderer() {
    148            this(-1.0);
    149        }
    150
    151        /**
    152         * Creates a new renderer for box and whisker charts.
    153         * <P>
    154         * Use -1 for the box width if you prefer the width to be calculated
    155         * automatically.
    156         *
    157         * @param boxWidth  the box width.
    158         */
    159        public XYBoxAndWhiskerRenderer(double boxWidth) {
    160            super();
    161            this.boxWidth = boxWidth;
    162            this.boxPaint = Color.green;
    163            this.fillBox = true;
    164            setBaseToolTipGenerator(new BoxAndWhiskerXYToolTipGenerator());
    165        }
    166
    167        /**
    168         * Returns the width of each box.
    169         *
    170         * @return The box width.
    171         *
    172         * @see #setBoxWidth(double)
    173         */
    174        public double getBoxWidth() {
    175            return this.boxWidth;
    176        }
    177
    178        /**
    179         * Sets the box width and sends a {@link RendererChangeEvent} to all
    180         * registered listeners.
    181         * <P>
    182         * If you set the width to a negative value, the renderer will calculate
    183         * the box width automatically based on the space available on the chart.
    184         *
    185         * @param width  the width.
    186         *
    187         * @see #getBoxWidth()
    188         */
    189        public void setBoxWidth(double width) {
    190            if (width != this.boxWidth) {
    191                this.boxWidth = width;
    192                fireChangeEvent();
    193            }
    194        }
    195
    196        /**
    197         * Returns the paint used to fill boxes.
    198         *
    199         * @return The paint (possibly <code>null</code>).
    200         *
    201         * @see #setBoxPaint(Paint)
    202         */
    203        public Paint getBoxPaint() {
    204            return this.boxPaint;
    205        }
    206
    207        /**
    208         * Sets the paint used to fill boxes and sends a {@link RendererChangeEvent}
    209         * to all registered listeners.
    210         *
    211         * @param paint  the paint (<code>null</code> permitted).
    212         *
    213         * @see #getBoxPaint()
    214         */
    215        public void setBoxPaint(Paint paint) {
    216            this.boxPaint = paint;
    217            fireChangeEvent();
    218        }
    219
    220        /**
    221         * Returns the flag that controls whether or not the box is filled.
    222         *
    223         * @return A boolean.
    224         *
    225         * @see #setFillBox(boolean)
    226         */
    227        public boolean getFillBox() {
    228            return this.fillBox;
    229        }
    230
    231        /**
    232         * Sets the flag that controls whether or not the box is filled and sends a
    233         * {@link RendererChangeEvent} to all registered listeners.
    234         *
    235         * @param flag  the flag.
    236         *
    237         * @see #setFillBox(boolean)
    238         */
    239        public void setFillBox(boolean flag) {
    240            this.fillBox = flag;
    241            fireChangeEvent();
    242        }
    243
    244        /**
    245         * Returns the paint used to paint the various artifacts such as outliers,
    246         * farout symbol, median line and the averages ellipse.
    247         *
    248         * @return The paint (never <code>null</code>).
    249         *
    250         * @see #setArtifactPaint(Paint)
    251         */
    252        public Paint getArtifactPaint() {
    253            return this.artifactPaint;
    254        }
    255
    256        /**
    257         * Sets the paint used to paint the various artifacts such as outliers,
    258         * farout symbol, median line and the averages ellipse, and sends a
    259         * {@link RendererChangeEvent} to all registered listeners.
    260         *
    261         * @param paint  the paint (<code>null</code> not permitted).
    262         *
    263         * @see #getArtifactPaint()
    264         */
    265        public void setArtifactPaint(Paint paint) {
    266            if (paint == null) {
    267                throw new IllegalArgumentException("Null 'paint' argument.");
    268            }
    269            this.artifactPaint = paint;
    270            fireChangeEvent();
    271        }
    272
    273        /**
    274         * Returns the box paint or, if this is <code>null</code>, the item
    275         * paint.
    276         *
    277         * @param series  the series index.
    278         * @param item  the item index.
    279         *
    280         * @return The paint used to fill the box for the specified item (never
    281         *         <code>null</code>).
    282         *
    283         * @since 1.0.10
    284         */
    285        protected Paint lookupBoxPaint(int series, int item) {
    286            Paint p = getBoxPaint();
    287            if (p != null) {
    288                return p;
    289            }
    290            else {
    291                // TODO: could change this to itemFillPaint().  For backwards
    292                // compatibility, it might require a useFillPaint flag.
    293                return getItemPaint(series, item);
    294            }
    295        }
    296
    297        /**
    298         * Draws the visual representation of a single data item.
    299         *
    300         * @param g2  the graphics device.
    301         * @param state  the renderer state.
    302         * @param dataArea  the area within which the plot is being drawn.
    303         * @param info  collects info about the drawing.
    304         * @param plot  the plot (can be used to obtain standard color
    305         *              information etc).
    306         * @param domainAxis  the domain axis.
    307         * @param rangeAxis  the range axis.
    308         * @param dataset  the dataset (must be an instance of
    309         *                 {@link BoxAndWhiskerXYDataset}).
    310         * @param series  the series index (zero-based).
    311         * @param item  the item index (zero-based).
    312         * @param crosshairState  crosshair information for the plot
    313         *                        (<code>null</code> permitted).
    314         * @param pass  the pass index.
    315         */
    316        public void drawItem(Graphics2D g2,
    317                             XYItemRendererState state,
    318                             Rectangle2D dataArea,
    319                             PlotRenderingInfo info,
    320                             XYPlot plot,
    321                             ValueAxis domainAxis,
    322                             ValueAxis rangeAxis,
    323                             XYDataset dataset,
    324                             int series,
    325                             int item,
    326                             CrosshairState crosshairState,
    327                             int pass) {
    328
    329            PlotOrientation orientation = plot.getOrientation();
    330
    331            if (orientation == PlotOrientation.HORIZONTAL) {
    332                drawHorizontalItem(g2, dataArea, info, plot, domainAxis, rangeAxis,
    333                        dataset, series, item, crosshairState, pass);
    334            }
    335            else if (orientation == PlotOrientation.VERTICAL) {
    336                drawVerticalItem(g2, dataArea, info, plot, domainAxis, rangeAxis,
    337                        dataset, series, item, crosshairState, pass);
    338            }
    339
    340        }
    341
    342        /**
    343         * Draws the visual representation of a single data item.
    344         *
    345         * @param g2  the graphics device.
    346         * @param dataArea  the area within which the plot is being drawn.
    347         * @param info  collects info about the drawing.
    348         * @param plot  the plot (can be used to obtain standard color
    349         *              information etc).
    350         * @param domainAxis  the domain axis.
    351         * @param rangeAxis  the range axis.
    352         * @param dataset  the dataset (must be an instance of
    353         *                 {@link BoxAndWhiskerXYDataset}).
    354         * @param series  the series index (zero-based).
    355         * @param item  the item index (zero-based).
    356         * @param crosshairState  crosshair information for the plot
    357         *                        (<code>null</code> permitted).
    358         * @param pass  the pass index.
    359         */
    360        public void drawHorizontalItem(Graphics2D g2,
    361                                       Rectangle2D dataArea,
    362                                       PlotRenderingInfo info,
    363                                       XYPlot plot,
    364                                       ValueAxis domainAxis,
    365                                       ValueAxis rangeAxis,
    366                                       XYDataset dataset,
    367                                       int series,
    368                                       int item,
    369                                       CrosshairState crosshairState,
    370                                       int pass) {
    371
    372            // setup for collecting optional entity info...
    373            EntityCollection entities = null;
    374            if (info != null) {
    375                entities = info.getOwner().getEntityCollection();
    376            }
    377
    378            BoxAndWhiskerXYDataset boxAndWhiskerData
    379                    = (BoxAndWhiskerXYDataset) dataset;
    380
    381            Number x = boxAndWhiskerData.getX(series, item);
    382            Number yMax = boxAndWhiskerData.getMaxRegularValue(series, item);
    383            Number yMin = boxAndWhiskerData.getMinRegularValue(series, item);
    384            Number yMedian = boxAndWhiskerData.getMedianValue(series, item);
    385            Number yAverage = boxAndWhiskerData.getMeanValue(series, item);
    386            Number yQ1Median = boxAndWhiskerData.getQ1Value(series, item);
    387            Number yQ3Median = boxAndWhiskerData.getQ3Value(series, item);
    388
    389            double xx = domainAxis.valueToJava2D(x.doubleValue(), dataArea,
    390                    plot.getDomainAxisEdge());
    391
    392            RectangleEdge location = plot.getRangeAxisEdge();
    393            double yyMax = rangeAxis.valueToJava2D(yMax.doubleValue(), dataArea,
    394                    location);
    395            double yyMin = rangeAxis.valueToJava2D(yMin.doubleValue(), dataArea,
    396                    location);
    397            double yyMedian = rangeAxis.valueToJava2D(yMedian.doubleValue(),
    398                    dataArea, location);
    399            double yyAverage = 0.0;
    400            if (yAverage != null) {
    401                yyAverage = rangeAxis.valueToJava2D(yAverage.doubleValue(),
    402                        dataArea, location);
    403            }
    404            double yyQ1Median = rangeAxis.valueToJava2D(yQ1Median.doubleValue(),
    405                    dataArea, location);
    406            double yyQ3Median = rangeAxis.valueToJava2D(yQ3Median.doubleValue(),
    407                    dataArea, location);
    408
    409            double exactBoxWidth = getBoxWidth();
    410            double width = exactBoxWidth;
    411            double dataAreaX = dataArea.getHeight();
    412            double maxBoxPercent = 0.1;
    413            double maxBoxWidth = dataAreaX * maxBoxPercent;
    414            if (exactBoxWidth <= 0.0) {
    415                int itemCount = boxAndWhiskerData.getItemCount(series);
    416                exactBoxWidth = dataAreaX / itemCount * 4.5 / 7;
    417                if (exactBoxWidth < 3) {
    418                    width = 3;
    419                }
    420                else if (exactBoxWidth > maxBoxWidth) {
    421                    width = maxBoxWidth;
    422                }
    423                else {
    424                    width = exactBoxWidth;
    425                }
    426            }
    427
    428            g2.setPaint(getItemPaint(series, item));
    429            Stroke s = getItemStroke(series, item);
    430            g2.setStroke(s);
    431
    432            // draw the upper shadow
    433            g2.draw(new Line2D.Double(yyMax, xx, yyQ3Median, xx));
    434            g2.draw(new Line2D.Double(yyMax, xx - width / 2, yyMax,
    435                    xx + width / 2));
    436
    437            // draw the lower shadow
    438            g2.draw(new Line2D.Double(yyMin, xx, yyQ1Median, xx));
    439            g2.draw(new Line2D.Double(yyMin, xx - width / 2, yyMin,
    440                    xx + width / 2));
    441
    442            // draw the body
    443            Shape box = null;
    444            if (yyQ1Median < yyQ3Median) {
    445                box = new Rectangle2D.Double(yyQ1Median, xx - width / 2,
    446                        yyQ3Median - yyQ1Median, width);
    447            }
    448            else {
    449                box = new Rectangle2D.Double(yyQ3Median, xx - width / 2,
    450                        yyQ1Median - yyQ3Median, width);
    451            }
    452            if (this.fillBox) {
    453                g2.setPaint(lookupBoxPaint(series, item));
    454                g2.fill(box);
    455            }
    456            g2.setStroke(getItemOutlineStroke(series, item));
    457            g2.setPaint(getItemOutlinePaint(series, item));
    458            g2.draw(box);
    459
    460            // draw median
    461            g2.setPaint(getArtifactPaint());
    462            g2.draw(new Line2D.Double(yyMedian,
    463                    xx - width / 2, yyMedian, xx + width / 2));
    464
    465            // draw average - SPECIAL AIMS REQUIREMENT
    466            if (yAverage != null) {
    467                double aRadius = width / 4;
    468                // here we check that the average marker will in fact be visible
    469                // before drawing it...
    470                if ((yyAverage > (dataArea.getMinX() - aRadius))
    471                        && (yyAverage < (dataArea.getMaxX() + aRadius))) {
    472                    Ellipse2D.Double avgEllipse = new Ellipse2D.Double(
    473                            yyAverage - aRadius, xx - aRadius, aRadius * 2,
    474                            aRadius * 2);
    475                    g2.fill(avgEllipse);
    476                    g2.draw(avgEllipse);
    477                }
    478            }
    479
    480            // FIXME: draw outliers
    481
    482            // add an entity for the item...
    483            if (entities != null && box.intersects(dataArea)) {
    484                addEntity(entities, box, dataset, series, item, yyAverage, xx);
    485            }
    486
    487        }
    488
    489        /**
    490         * Draws the visual representation of a single data item.
    491         *
    492         * @param g2  the graphics device.
    493         * @param dataArea  the area within which the plot is being drawn.
    494         * @param info  collects info about the drawing.
    495         * @param plot  the plot (can be used to obtain standard color
    496         *              information etc).
    497         * @param domainAxis  the domain axis.
    498         * @param rangeAxis  the range axis.
    499         * @param dataset  the dataset (must be an instance of
    500         *                 {@link BoxAndWhiskerXYDataset}).
    501         * @param series  the series index (zero-based).
    502         * @param item  the item index (zero-based).
    503         * @param crosshairState  crosshair information for the plot
    504         *                        (<code>null</code> permitted).
    505         * @param pass  the pass index.
    506         */
    507        public void drawVerticalItem(Graphics2D g2,
    508                                     Rectangle2D dataArea,
    509                                     PlotRenderingInfo info,
    510                                     XYPlot plot,
    511                                     ValueAxis domainAxis,
    512                                     ValueAxis rangeAxis,
    513                                     XYDataset dataset,
    514                                     int series,
    515                                     int item,
    516                                     CrosshairState crosshairState,
    517                                     int pass) {
    518
    519            // setup for collecting optional entity info...
    520            EntityCollection entities = null;
    521            if (info != null) {
    522                entities = info.getOwner().getEntityCollection();
    523            }
    524
    525            BoxAndWhiskerXYDataset boxAndWhiskerData
    526                = (BoxAndWhiskerXYDataset) dataset;
    527
    528            Number x = boxAndWhiskerData.getX(series, item);
    529            Number yMax = boxAndWhiskerData.getMaxRegularValue(series, item);
    530            Number yMin = boxAndWhiskerData.getMinRegularValue(series, item);
    531            Number yMedian = boxAndWhiskerData.getMedianValue(series, item);
    532            Number yAverage = boxAndWhiskerData.getMeanValue(series, item);
    533            Number yQ1Median = boxAndWhiskerData.getQ1Value(series, item);
    534            Number yQ3Median = boxAndWhiskerData.getQ3Value(series, item);
    535            List yOutliers = boxAndWhiskerData.getOutliers(series, item);
    536
    537            double xx = domainAxis.valueToJava2D(x.doubleValue(), dataArea,
    538                    plot.getDomainAxisEdge());
    539
    540            RectangleEdge location = plot.getRangeAxisEdge();
    541            double yyMax = rangeAxis.valueToJava2D(yMax.doubleValue(), dataArea,
    542                    location);
    543            double yyMin = rangeAxis.valueToJava2D(yMin.doubleValue(), dataArea,
    544                    location);
    545            double yyMedian = rangeAxis.valueToJava2D(yMedian.doubleValue(),
    546                    dataArea, location);
    547            double yyAverage = 0.0;
    548            if (yAverage != null) {
    549                yyAverage = rangeAxis.valueToJava2D(yAverage.doubleValue(),
    550                        dataArea, location);
    551            }
    552            double yyQ1Median = rangeAxis.valueToJava2D(yQ1Median.doubleValue(),
    553                    dataArea, location);
    554            double yyQ3Median = rangeAxis.valueToJava2D(yQ3Median.doubleValue(),
    555                    dataArea, location);
    556            double yyOutlier;
    557
    558
    559            double exactBoxWidth = getBoxWidth();
    560            double width = exactBoxWidth;
    561            double dataAreaX = dataArea.getMaxX() - dataArea.getMinX();
    562            double maxBoxPercent = 0.1;
    563            double maxBoxWidth = dataAreaX * maxBoxPercent;
    564            if (exactBoxWidth <= 0.0) {
    565                int itemCount = boxAndWhiskerData.getItemCount(series);
    566                exactBoxWidth = dataAreaX / itemCount * 4.5 / 7;
    567                if (exactBoxWidth < 3) {
    568                    width = 3;
    569                }
    570                else if (exactBoxWidth > maxBoxWidth) {
    571                    width = maxBoxWidth;
    572                }
    573                else {
    574                    width = exactBoxWidth;
    575                }
    576            }
    577
    578            g2.setPaint(getItemPaint(series, item));
    579            Stroke s = getItemStroke(series, item);
    580            g2.setStroke(s);
    581
    582            // draw the upper shadow
    583            g2.draw(new Line2D.Double(xx, yyMax, xx, yyQ3Median));
    584            g2.draw(new Line2D.Double(xx - width / 2, yyMax, xx + width / 2,
    585                    yyMax));
    586
    587            // draw the lower shadow
    588            g2.draw(new Line2D.Double(xx, yyMin, xx, yyQ1Median));
    589            g2.draw(new Line2D.Double(xx - width / 2, yyMin, xx + width / 2,
    590                    yyMin));
    591
    592            // draw the body
    593            Shape box = null;
    594            if (yyQ1Median > yyQ3Median) {
    595                box = new Rectangle2D.Double(xx - width / 2, yyQ3Median, width,
    596                        yyQ1Median - yyQ3Median);
    597            }
    598            else {
    599                box = new Rectangle2D.Double(xx - width / 2, yyQ1Median, width,
    600                        yyQ3Median - yyQ1Median);
    601            }
    602            if (this.fillBox) {
    603                g2.setPaint(lookupBoxPaint(series, item));
    604                g2.fill(box);
    605            }
    606            g2.setStroke(getItemOutlineStroke(series, item));
    607            g2.setPaint(getItemOutlinePaint(series, item));
    608            g2.draw(box);
    609
    610            // draw median
    611            g2.setPaint(getArtifactPaint());
    612            g2.draw(new Line2D.Double(xx - width / 2, yyMedian, xx + width / 2,
    613                    yyMedian));
    614
    615            double aRadius = 0;                 // average radius
    616            double oRadius = width / 3;    // outlier radius
    617
    618            // draw average - SPECIAL AIMS REQUIREMENT
    619            if (yAverage != null) {
    620                aRadius = width / 4;
    621                // here we check that the average marker will in fact be visible
    622                // before drawing it...
    623                if ((yyAverage > (dataArea.getMinY() - aRadius))
    624                        && (yyAverage < (dataArea.getMaxY() + aRadius))) {
    625                    Ellipse2D.Double avgEllipse = new Ellipse2D.Double(xx - aRadius,
    626                            yyAverage - aRadius, aRadius * 2, aRadius * 2);
    627                    g2.fill(avgEllipse);
    628                    g2.draw(avgEllipse);
    629                }
    630            }
    631
    632            List outliers = new ArrayList();
    633            OutlierListCollection outlierListCollection
    634                    = new OutlierListCollection();
    635
    636            /* From outlier array sort out which are outliers and put these into
    637             * an arraylist. If there are any farouts, set the flag on the
    638             * OutlierListCollection
    639             */
    640
    641            for (int i = 0; i < yOutliers.size(); i++) {
    642                double outlier = ((Number) yOutliers.get(i)).doubleValue();
    643                if (outlier > boxAndWhiskerData.getMaxOutlier(series,
    644                        item).doubleValue()) {
    645                    outlierListCollection.setHighFarOut(true);
    646                }
    647                else if (outlier < boxAndWhiskerData.getMinOutlier(series,
    648                        item).doubleValue()) {
    649                    outlierListCollection.setLowFarOut(true);
    650                }
    651                else if (outlier > boxAndWhiskerData.getMaxRegularValue(series,
    652                        item).doubleValue()) {
    653                    yyOutlier = rangeAxis.valueToJava2D(outlier, dataArea,
    654                            location);
    655                    outliers.add(new Outlier(xx, yyOutlier, oRadius));
    656                }
    657                else if (outlier < boxAndWhiskerData.getMinRegularValue(series,
    658                        item).doubleValue()) {
    659                    yyOutlier = rangeAxis.valueToJava2D(outlier, dataArea,
    660                            location);
    661                    outliers.add(new Outlier(xx, yyOutlier, oRadius));
    662                }
    663                Collections.sort(outliers);
    664            }
    665
    666            // Process outliers. Each outlier is either added to the appropriate
    667            // outlier list or a new outlier list is made
    668            for (Iterator iterator = outliers.iterator(); iterator.hasNext();) {
    669                Outlier outlier = (Outlier) iterator.next();
    670                outlierListCollection.add(outlier);
    671            }
    672
    673            // draw yOutliers
    674            double maxAxisValue = rangeAxis.valueToJava2D(rangeAxis.getUpperBound(),
    675                    dataArea, location) + aRadius;
    676            double minAxisValue = rangeAxis.valueToJava2D(rangeAxis.getLowerBound(),
    677                    dataArea, location) - aRadius;
    678
    679            // draw outliers
    680            for (Iterator iterator = outlierListCollection.iterator();
    681                    iterator.hasNext();) {
    682                OutlierList list = (OutlierList) iterator.next();
    683                Outlier outlier = list.getAveragedOutlier();
    684                Point2D point = outlier.getPoint();
    685
    686                if (list.isMultiple()) {
    687                    drawMultipleEllipse(point, width, oRadius, g2);
    688                }
    689                else {
    690                    drawEllipse(point, oRadius, g2);
    691                }
    692            }
    693
    694            // draw farout
    695            if (outlierListCollection.isHighFarOut()) {
    696                drawHighFarOut(aRadius, g2, xx, maxAxisValue);
    697            }
    698
    699            if (outlierListCollection.isLowFarOut()) {
    700                drawLowFarOut(aRadius, g2, xx, minAxisValue);
    701            }
    702
    703            // add an entity for the item...
    704            if (entities != null && box.intersects(dataArea)) {
    705                addEntity(entities, box, dataset, series, item, xx, yyAverage);
    706            }
    707
    708        }
    709
    710        /**
    711         * Draws an ellipse to represent an outlier.
    712         *
    713         * @param point  the location.
    714         * @param oRadius  the radius.
    715         * @param g2  the graphics device.
    716         */
    717        protected void drawEllipse(Point2D point, double oRadius, Graphics2D g2) {
    718            Ellipse2D.Double dot = new Ellipse2D.Double(point.getX() + oRadius / 2,
    719                    point.getY(), oRadius, oRadius);
    720            g2.draw(dot);
    721        }
    722
    723        /**
    724         * Draws two ellipses to represent overlapping outliers.
    725         *
    726         * @param point  the location.
    727         * @param boxWidth  the box width.
    728         * @param oRadius  the radius.
    729         * @param g2  the graphics device.
    730         */
    731        protected void drawMultipleEllipse(Point2D point, double boxWidth,
    732                                           double oRadius, Graphics2D g2) {
    733
    734            Ellipse2D.Double dot1 = new Ellipse2D.Double(point.getX()
    735                    - (boxWidth / 2) + oRadius, point.getY(), oRadius, oRadius);
    736            Ellipse2D.Double dot2 = new Ellipse2D.Double(point.getX()
    737                    + (boxWidth / 2), point.getY(), oRadius, oRadius);
    738            g2.draw(dot1);
    739            g2.draw(dot2);
    740
    741        }
    742
    743        /**
    744         * Draws a triangle to indicate the presence of far out values.
    745         *
    746         * @param aRadius  the radius.
    747         * @param g2  the graphics device.
    748         * @param xx  the x value.
    749         * @param m  the max y value.
    750         */
    751        protected void drawHighFarOut(double aRadius, Graphics2D g2, double xx,
    752                double m) {
    753            double side = aRadius * 2;
    754            g2.draw(new Line2D.Double(xx - side, m + side, xx + side, m + side));
    755            g2.draw(new Line2D.Double(xx - side, m + side, xx, m));
    756            g2.draw(new Line2D.Double(xx + side, m + side, xx, m));
    757        }
    758
    759        /**
    760         * Draws a triangle to indicate the presence of far out values.
    761         *
    762         * @param aRadius  the radius.
    763         * @param g2  the graphics device.
    764         * @param xx  the x value.
    765         * @param m  the min y value.
    766         */
    767        protected void drawLowFarOut(double aRadius, Graphics2D g2, double xx,
    768                double m) {
    769            double side = aRadius * 2;
    770            g2.draw(new Line2D.Double(xx - side, m - side, xx + side, m - side));
    771            g2.draw(new Line2D.Double(xx - side, m - side, xx, m));
    772            g2.draw(new Line2D.Double(xx + side, m - side, xx, m));
    773        }
    774
    775        /**
    776         * Tests this renderer for equality with another object.
    777         *
    778         * @param obj  the object (<code>null</code> permitted).
    779         *
    780         * @return <code>true</code> or <code>false</code>.
    781         */
    782        public boolean equals(Object obj) {
    783            if (obj == this) {
    784                return true;
    785            }
    786            if (!(obj instanceof XYBoxAndWhiskerRenderer)) {
    787                return false;
    788            }
    789            if (!super.equals(obj)) {
    790                return false;
    791            }
    792            XYBoxAndWhiskerRenderer that = (XYBoxAndWhiskerRenderer) obj;
    793            if (this.boxWidth != that.getBoxWidth()) {
    794                return false;
    795            }
    796            if (!PaintUtilities.equal(this.boxPaint, that.boxPaint)) {
    797                return false;
    798            }
    799            if (!PaintUtilities.equal(this.artifactPaint, that.artifactPaint)) {
    800                return false;
    801            }
    802            if (this.fillBox != that.fillBox) {
    803                return false;
    804            }
    805            return true;
    806
    807        }
    808
    809        /**
    810         * Provides serialization support.
    811         *
    812         * @param stream  the output stream.
    813         *
    814         * @throws IOException  if there is an I/O error.
    815         */
    816        private void writeObject(ObjectOutputStream stream) throws IOException {
    817            stream.defaultWriteObject();
    818            SerialUtilities.writePaint(this.boxPaint, stream);
    819            SerialUtilities.writePaint(this.artifactPaint, stream);
    820        }
    821
    822        /**
    823         * Provides serialization support.
    824         *
    825         * @param stream  the input stream.
    826         *
    827         * @throws IOException  if there is an I/O error.
    828         * @throws ClassNotFoundException  if there is a classpath problem.
    829         */
    830        private void readObject(ObjectInputStream stream)
    831            throws IOException, ClassNotFoundException {
    832
    833            stream.defaultReadObject();
    834            this.boxPaint = SerialUtilities.readPaint(stream);
    835            this.artifactPaint = SerialUtilities.readPaint(stream);
    836        }
    837
    838        /**
    839         * Returns a clone of the renderer.
    840         *
    841         * @return A clone.
    842         *
    843         * @throws CloneNotSupportedException  if the renderer cannot be cloned.
    844         */
    845        public Object clone() throws CloneNotSupportedException {
    846            return super.clone();
    847        }
    848
    849    }