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    }