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