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     * XYDifferenceRenderer.java
029     * -------------------------
030     * (C) Copyright 2003-2008, by Object Refinery Limited and Contributors.
031     *
032     * Original Author:  David Gilbert (for Object Refinery Limited);
033     * Contributor(s):   Richard West, Advanced Micro Devices, Inc. (major rewrite
034     *                   of difference drawing algorithm);
035     *
036     * Changes:
037     * --------
038     * 30-Apr-2003 : Version 1 (DG);
039     * 30-Jul-2003 : Modified entity constructor (CZ);
040     * 20-Aug-2003 : Implemented Cloneable and PublicCloneable (DG);
041     * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG);
042     * 09-Feb-2004 : Updated to support horizontal plot orientation (DG);
043     * 10-Feb-2004 : Added default constructor, setter methods and updated
044     *               Javadocs (DG);
045     * 25-Feb-2004 : Replaced CrosshairInfo with CrosshairState (DG);
046     * 30-Mar-2004 : Fixed bug in getNegativePaint() method (DG);
047     * 15-Jul-2004 : Switched getX() with getXValue() and getY() with
048     *               getYValue() (DG);
049     * 25-Aug-2004 : Fixed a bug preventing the use of crosshairs (DG);
050     * 11-Nov-2004 : Now uses ShapeUtilities to translate shapes (DG);
051     * 19-Jan-2005 : Now accesses only primitive values from dataset (DG);
052     * 22-Feb-2005 : Override getLegendItem(int, int) to return "line" items (DG);
053     * 13-Apr-2005 : Fixed shape positioning bug (id = 1182062) (DG);
054     * 20-Apr-2005 : Use generators for legend tooltips and URLs (DG);
055     * 04-May-2005 : Override equals() method, renamed get/setPlotShapes() -->
056     *               get/setShapesVisible (DG);
057     * 09-Jun-2005 : Updated equals() to handle GradientPaint (DG);
058     * 16-Jun-2005 : Fix bug (1221021) affecting stroke used for each series (DG);
059     * ------------- JFREECHART 1.0.x ---------------------------------------------
060     * 24-Jan-2007 : Added flag to allow rounding of x-coordinates, and fixed
061     *               bug in clone() (DG);
062     * 05-Feb-2007 : Added an extra call to updateCrosshairValues() in
063     *               drawItemPass1(), to fix bug 1564967 (DG);
064     * 06-Feb-2007 : Fixed bug 1086307, crosshairs with multiple axes (DG);
065     * 08-Mar-2007 : Fixed entity generation (DG);
066     * 20-Apr-2007 : Updated getLegendItem() for renderer change (DG);
067     * 23-Apr-2007 : Rewrite of difference drawing algorithm to allow use of
068     *               series with disjoint x-values (RW);
069     * 04-May-2007 : Set processVisibleItemsOnly flag to false (DG);
070     * 17-May-2007 : Set datasetIndex and seriesIndex in getLegendItem() (DG);
071     * 18-May-2007 : Set dataset and seriesKey for LegendItem (DG);
072     * 05-Nov-2007 : Draw item labels if visible (RW);
073     * 17-Jun-2008 : Apply legend shape, font and paint attributes (DG);
074     *
075     */
076    
077    package org.jfree.chart.renderer.xy;
078    
079    import java.awt.Color;
080    import java.awt.Graphics2D;
081    import java.awt.Paint;
082    import java.awt.Shape;
083    import java.awt.Stroke;
084    import java.awt.geom.GeneralPath;
085    import java.awt.geom.Line2D;
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.Collections;
092    import java.util.LinkedList;
093    
094    import org.jfree.chart.LegendItem;
095    import org.jfree.chart.axis.ValueAxis;
096    import org.jfree.chart.entity.EntityCollection;
097    import org.jfree.chart.entity.XYItemEntity;
098    import org.jfree.chart.event.RendererChangeEvent;
099    import org.jfree.chart.labels.XYToolTipGenerator;
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.urls.XYURLGenerator;
105    import org.jfree.data.xy.XYDataset;
106    import org.jfree.io.SerialUtilities;
107    import org.jfree.ui.RectangleEdge;
108    import org.jfree.util.PaintUtilities;
109    import org.jfree.util.PublicCloneable;
110    import org.jfree.util.ShapeUtilities;
111    
112    /**
113     * A renderer for an {@link XYPlot} that highlights the differences between two
114     * series.
115     */
116    public class XYDifferenceRenderer extends AbstractXYItemRenderer
117                                      implements XYItemRenderer,
118                                                 Cloneable,
119                                                 PublicCloneable,
120                                                 Serializable {
121    
122        /** For serialization. */
123        private static final long serialVersionUID = -8447915602375584857L;
124    
125        /** The paint used to highlight positive differences (y(0) > y(1)). */
126        private transient Paint positivePaint;
127    
128        /** The paint used to highlight negative differences (y(0) < y(1)). */
129        private transient Paint negativePaint;
130    
131        /** Display shapes at each point? */
132        private boolean shapesVisible;
133    
134        /** The shape to display in the legend item. */
135        private transient Shape legendLine;
136    
137        /**
138         * This flag controls whether or not the x-coordinates (in Java2D space)
139         * are rounded to integers.  When set to true, this can avoid the vertical
140         * striping that anti-aliasing can generate.  However, the rounding may not
141         * be appropriate for output in high resolution formats (for example,
142         * vector graphics formats such as SVG and PDF).
143         *
144         * @since 1.0.4
145         */
146        private boolean roundXCoordinates;
147    
148        /**
149         * Creates a new renderer with default attributes.
150         */
151        public XYDifferenceRenderer() {
152            this(Color.green, Color.red, false);
153        }
154    
155        /**
156         * Creates a new renderer.
157         *
158         * @param positivePaint  the highlight color for positive differences
159         *                       (<code>null</code> not permitted).
160         * @param negativePaint  the highlight color for negative differences
161         *                       (<code>null</code> not permitted).
162         * @param shapes  draw shapes?
163         */
164        public XYDifferenceRenderer(Paint positivePaint, Paint negativePaint,
165                                    boolean shapes) {
166            if (positivePaint == null) {
167                throw new IllegalArgumentException(
168                        "Null 'positivePaint' argument.");
169            }
170            if (negativePaint == null) {
171                throw new IllegalArgumentException(
172                        "Null 'negativePaint' argument.");
173            }
174            this.positivePaint = positivePaint;
175            this.negativePaint = negativePaint;
176            this.shapesVisible = shapes;
177            this.legendLine = new Line2D.Double(-7.0, 0.0, 7.0, 0.0);
178            this.roundXCoordinates = false;
179        }
180    
181        /**
182         * Returns the paint used to highlight positive differences.
183         *
184         * @return The paint (never <code>null</code>).
185         *
186         * @see #setPositivePaint(Paint)
187         */
188        public Paint getPositivePaint() {
189            return this.positivePaint;
190        }
191    
192        /**
193         * Sets the paint used to highlight positive differences and sends a
194         * {@link RendererChangeEvent} to all registered listeners.
195         *
196         * @param paint  the paint (<code>null</code> not permitted).
197         *
198         * @see #getPositivePaint()
199         */
200        public void setPositivePaint(Paint paint) {
201            if (paint == null) {
202                throw new IllegalArgumentException("Null 'paint' argument.");
203            }
204            this.positivePaint = paint;
205            fireChangeEvent();
206        }
207    
208        /**
209         * Returns the paint used to highlight negative differences.
210         *
211         * @return The paint (never <code>null</code>).
212         *
213         * @see #setNegativePaint(Paint)
214         */
215        public Paint getNegativePaint() {
216            return this.negativePaint;
217        }
218    
219        /**
220         * Sets the paint used to highlight negative differences.
221         *
222         * @param paint  the paint (<code>null</code> not permitted).
223         *
224         * @see #getNegativePaint()
225         */
226        public void setNegativePaint(Paint paint) {
227            if (paint == null) {
228                throw new IllegalArgumentException("Null 'paint' argument.");
229            }
230            this.negativePaint = paint;
231            notifyListeners(new RendererChangeEvent(this));
232        }
233    
234        /**
235         * Returns a flag that controls whether or not shapes are drawn for each
236         * data value.
237         *
238         * @return A boolean.
239         *
240         * @see #setShapesVisible(boolean)
241         */
242        public boolean getShapesVisible() {
243            return this.shapesVisible;
244        }
245    
246        /**
247         * Sets a flag that controls whether or not shapes are drawn for each
248         * data value, and sends a {@link RendererChangeEvent} to all registered
249         * listeners.
250         *
251         * @param flag  the flag.
252         *
253         * @see #getShapesVisible()
254         */
255        public void setShapesVisible(boolean flag) {
256            this.shapesVisible = flag;
257            fireChangeEvent();
258        }
259    
260        /**
261         * Returns the shape used to represent a line in the legend.
262         *
263         * @return The legend line (never <code>null</code>).
264         *
265         * @see #setLegendLine(Shape)
266         */
267        public Shape getLegendLine() {
268            return this.legendLine;
269        }
270    
271        /**
272         * Sets the shape used as a line in each legend item and sends a
273         * {@link RendererChangeEvent} to all registered listeners.
274         *
275         * @param line  the line (<code>null</code> not permitted).
276         *
277         * @see #getLegendLine()
278         */
279        public void setLegendLine(Shape line) {
280            if (line == null) {
281                throw new IllegalArgumentException("Null 'line' argument.");
282            }
283            this.legendLine = line;
284            fireChangeEvent();
285        }
286    
287        /**
288         * Returns the flag that controls whether or not the x-coordinates (in
289         * Java2D space) are rounded to integer values.
290         *
291         * @return The flag.
292         *
293         * @since 1.0.4
294         *
295         * @see #setRoundXCoordinates(boolean)
296         */
297        public boolean getRoundXCoordinates() {
298            return this.roundXCoordinates;
299        }
300    
301        /**
302         * Sets the flag that controls whether or not the x-coordinates (in
303         * Java2D space) are rounded to integer values, and sends a
304         * {@link RendererChangeEvent} to all registered listeners.
305         *
306         * @param round  the new flag value.
307         *
308         * @since 1.0.4
309         *
310         * @see #getRoundXCoordinates()
311         */
312        public void setRoundXCoordinates(boolean round) {
313            this.roundXCoordinates = round;
314            fireChangeEvent();
315        }
316    
317        /**
318         * Initialises the renderer and returns a state object that should be
319         * passed to subsequent calls to the drawItem() method.  This method will
320         * be called before the first item is rendered, giving the renderer an
321         * opportunity to initialise any state information it wants to maintain.
322         * The renderer can do nothing if it chooses.
323         *
324         * @param g2  the graphics device.
325         * @param dataArea  the area inside the axes.
326         * @param plot  the plot.
327         * @param data  the data.
328         * @param info  an optional info collection object to return data back to
329         *              the caller.
330         *
331         * @return A state object.
332         */
333        public XYItemRendererState initialise(Graphics2D g2,
334                                              Rectangle2D dataArea,
335                                              XYPlot plot,
336                                              XYDataset data,
337                                              PlotRenderingInfo info) {
338    
339            XYItemRendererState state = super.initialise(g2, dataArea, plot, data,
340                    info);
341            state.setProcessVisibleItemsOnly(false);
342            return state;
343    
344        }
345    
346        /**
347         * Returns <code>2</code>, the number of passes required by the renderer.
348         * The {@link XYPlot} will run through the dataset this number of times.
349         *
350         * @return The number of passes required by the renderer.
351         */
352        public int getPassCount() {
353            return 2;
354        }
355    
356        /**
357         * Draws the visual representation of a single data item.
358         *
359         * @param g2  the graphics device.
360         * @param state  the renderer state.
361         * @param dataArea  the area within which the data is being drawn.
362         * @param info  collects information about the drawing.
363         * @param plot  the plot (can be used to obtain standard color
364         *              information etc).
365         * @param domainAxis  the domain (horizontal) axis.
366         * @param rangeAxis  the range (vertical) axis.
367         * @param dataset  the dataset.
368         * @param series  the series index (zero-based).
369         * @param item  the item index (zero-based).
370         * @param crosshairState  crosshair information for the plot
371         *                        (<code>null</code> permitted).
372         * @param pass  the pass index.
373         */
374        public void drawItem(Graphics2D g2,
375                             XYItemRendererState state,
376                             Rectangle2D dataArea,
377                             PlotRenderingInfo info,
378                             XYPlot plot,
379                             ValueAxis domainAxis,
380                             ValueAxis rangeAxis,
381                             XYDataset dataset,
382                             int series,
383                             int item,
384                             CrosshairState crosshairState,
385                             int pass) {
386    
387            if (pass == 0) {
388                drawItemPass0(g2, dataArea, info, plot, domainAxis, rangeAxis,
389                        dataset, series, item, crosshairState);
390            }
391            else if (pass == 1) {
392                drawItemPass1(g2, dataArea, info, plot, domainAxis, rangeAxis,
393                        dataset, series, item, crosshairState);
394            }
395    
396        }
397    
398        /**
399         * Draws the visual representation of a single data item, first pass.
400         *
401         * @param x_graphics  the graphics device.
402         * @param x_dataArea  the area within which the data is being drawn.
403         * @param x_info  collects information about the drawing.
404         * @param x_plot  the plot (can be used to obtain standard color
405         *                information etc).
406         * @param x_domainAxis  the domain (horizontal) axis.
407         * @param x_rangeAxis  the range (vertical) axis.
408         * @param x_dataset  the dataset.
409         * @param x_series  the series index (zero-based).
410         * @param x_item  the item index (zero-based).
411         * @param x_crosshairState  crosshair information for the plot
412         *                          (<code>null</code> permitted).
413         */
414        protected void drawItemPass0(Graphics2D x_graphics,
415                                     Rectangle2D x_dataArea,
416                                     PlotRenderingInfo x_info,
417                                     XYPlot x_plot,
418                                     ValueAxis x_domainAxis,
419                                     ValueAxis x_rangeAxis,
420                                     XYDataset x_dataset,
421                                     int x_series,
422                                     int x_item,
423                                     CrosshairState x_crosshairState) {
424    
425            if (!((0 == x_series) && (0 == x_item))) {
426                return;
427            }
428    
429            boolean b_impliedZeroSubtrahend = (1 == x_dataset.getSeriesCount());
430    
431            // check if either series is a degenerate case (i.e. less than 2 points)
432            if (isEitherSeriesDegenerate(x_dataset, b_impliedZeroSubtrahend)) {
433                return;
434            }
435    
436            // check if series are disjoint (i.e. domain-spans do not overlap)
437            if (!b_impliedZeroSubtrahend && areSeriesDisjoint(x_dataset)) {
438                return;
439            }
440    
441            // polygon definitions
442            LinkedList l_minuendXs    = new LinkedList();
443            LinkedList l_minuendYs    = new LinkedList();
444            LinkedList l_subtrahendXs = new LinkedList();
445            LinkedList l_subtrahendYs = new LinkedList();
446            LinkedList l_polygonXs    = new LinkedList();
447            LinkedList l_polygonYs    = new LinkedList();
448    
449            // state
450            int l_minuendItem      = 0;
451            int l_minuendItemCount = x_dataset.getItemCount(0);
452            Double l_minuendCurX   = null;
453            Double l_minuendNextX  = null;
454            Double l_minuendCurY   = null;
455            Double l_minuendNextY  = null;
456            double l_minuendMaxY   = Double.NEGATIVE_INFINITY;
457            double l_minuendMinY   = Double.POSITIVE_INFINITY;
458    
459            int l_subtrahendItem      = 0;
460            int l_subtrahendItemCount = 0; // actual value set below
461            Double l_subtrahendCurX   = null;
462            Double l_subtrahendNextX  = null;
463            Double l_subtrahendCurY   = null;
464            Double l_subtrahendNextY  = null;
465            double l_subtrahendMaxY   = Double.NEGATIVE_INFINITY;
466            double l_subtrahendMinY   = Double.POSITIVE_INFINITY;
467    
468            // if a subtrahend is not specified, assume it is zero
469            if (b_impliedZeroSubtrahend) {
470                l_subtrahendItem      = 0;
471                l_subtrahendItemCount = 2;
472                l_subtrahendCurX      = new Double(x_dataset.getXValue(0, 0));
473                l_subtrahendNextX     = new Double(x_dataset.getXValue(0,
474                        (l_minuendItemCount - 1)));
475                l_subtrahendCurY      = new Double(0.0);
476                l_subtrahendNextY     = new Double(0.0);
477                l_subtrahendMaxY      = 0.0;
478                l_subtrahendMinY      = 0.0;
479    
480                l_subtrahendXs.add(l_subtrahendCurX);
481                l_subtrahendYs.add(l_subtrahendCurY);
482            }
483            else {
484                l_subtrahendItemCount = x_dataset.getItemCount(1);
485            }
486    
487            boolean b_minuendDone           = false;
488            boolean b_minuendAdvanced       = true;
489            boolean b_minuendAtIntersect    = false;
490            boolean b_minuendFastForward    = false;
491            boolean b_subtrahendDone        = false;
492            boolean b_subtrahendAdvanced    = true;
493            boolean b_subtrahendAtIntersect = false;
494            boolean b_subtrahendFastForward = false;
495            boolean b_colinear              = false;
496    
497            boolean b_positive;
498    
499            // coordinate pairs
500            double l_x1 = 0.0, l_y1 = 0.0; // current minuend point
501            double l_x2 = 0.0, l_y2 = 0.0; // next minuend point
502            double l_x3 = 0.0, l_y3 = 0.0; // current subtrahend point
503            double l_x4 = 0.0, l_y4 = 0.0; // next subtrahend point
504    
505            // fast-forward through leading tails
506            boolean b_fastForwardDone = false;
507            while (!b_fastForwardDone) {
508                // get the x and y coordinates
509                l_x1 = x_dataset.getXValue(0, l_minuendItem);
510                l_y1 = x_dataset.getYValue(0, l_minuendItem);
511                l_x2 = x_dataset.getXValue(0, l_minuendItem + 1);
512                l_y2 = x_dataset.getYValue(0, l_minuendItem + 1);
513    
514                l_minuendCurX  = new Double(l_x1);
515                l_minuendCurY  = new Double(l_y1);
516                l_minuendNextX = new Double(l_x2);
517                l_minuendNextY = new Double(l_y2);
518    
519                if (b_impliedZeroSubtrahend) {
520                    l_x3 = l_subtrahendCurX.doubleValue();
521                    l_y3 = l_subtrahendCurY.doubleValue();
522                    l_x4 = l_subtrahendNextX.doubleValue();
523                    l_y4 = l_subtrahendNextY.doubleValue();
524                }
525                else {
526                    l_x3 = x_dataset.getXValue(1, l_subtrahendItem);
527                    l_y3 = x_dataset.getYValue(1, l_subtrahendItem);
528                    l_x4 = x_dataset.getXValue(1, l_subtrahendItem + 1);
529                    l_y4 = x_dataset.getYValue(1, l_subtrahendItem + 1);
530    
531                    l_subtrahendCurX  = new Double(l_x3);
532                    l_subtrahendCurY  = new Double(l_y3);
533                    l_subtrahendNextX = new Double(l_x4);
534                    l_subtrahendNextY = new Double(l_y4);
535                }
536    
537                if (l_x2 <= l_x3) {
538                    // minuend needs to be fast forwarded
539                    l_minuendItem++;
540                    b_minuendFastForward = true;
541                    continue;
542                }
543    
544                if (l_x4 <= l_x1) {
545                    // subtrahend needs to be fast forwarded
546                    l_subtrahendItem++;
547                    b_subtrahendFastForward = true;
548                    continue;
549                }
550    
551                // check if initial polygon needs to be clipped
552                if ((l_x3 < l_x1) && (l_x1 < l_x4)) {
553                    // project onto subtrahend
554                    double l_slope   = (l_y4 - l_y3) / (l_x4 - l_x3);
555                    l_subtrahendCurX = l_minuendCurX;
556                    l_subtrahendCurY = new Double((l_slope * l_x1)
557                            + (l_y3 - (l_slope * l_x3)));
558    
559                    l_subtrahendXs.add(l_subtrahendCurX);
560                    l_subtrahendYs.add(l_subtrahendCurY);
561                }
562    
563                if ((l_x1 < l_x3) && (l_x3 < l_x2)) {
564                    // project onto minuend
565                    double l_slope = (l_y2 - l_y1) / (l_x2 - l_x1);
566                    l_minuendCurX  = l_subtrahendCurX;
567                    l_minuendCurY  = new Double((l_slope * l_x3)
568                            + (l_y1 - (l_slope * l_x1)));
569    
570                    l_minuendXs.add(l_minuendCurX);
571                    l_minuendYs.add(l_minuendCurY);
572                }
573    
574                l_minuendMaxY    = l_minuendCurY.doubleValue();
575                l_minuendMinY    = l_minuendCurY.doubleValue();
576                l_subtrahendMaxY = l_subtrahendCurY.doubleValue();
577                l_subtrahendMinY = l_subtrahendCurY.doubleValue();
578    
579                b_fastForwardDone = true;
580            }
581    
582            // start of algorithm
583            while (!b_minuendDone && !b_subtrahendDone) {
584                if (!b_minuendDone && !b_minuendFastForward && b_minuendAdvanced) {
585                    l_x1 = x_dataset.getXValue(0, l_minuendItem);
586                    l_y1 = x_dataset.getYValue(0, l_minuendItem);
587                    l_minuendCurX = new Double(l_x1);
588                    l_minuendCurY = new Double(l_y1);
589    
590                    if (!b_minuendAtIntersect) {
591                        l_minuendXs.add(l_minuendCurX);
592                        l_minuendYs.add(l_minuendCurY);
593                    }
594    
595                    l_minuendMaxY = Math.max(l_minuendMaxY, l_y1);
596                    l_minuendMinY = Math.min(l_minuendMinY, l_y1);
597    
598                    l_x2 = x_dataset.getXValue(0, l_minuendItem + 1);
599                    l_y2 = x_dataset.getYValue(0, l_minuendItem + 1);
600                    l_minuendNextX = new Double(l_x2);
601                    l_minuendNextY = new Double(l_y2);
602                }
603    
604                // never updated the subtrahend if it is implied to be zero
605                if (!b_impliedZeroSubtrahend && !b_subtrahendDone
606                        && !b_subtrahendFastForward && b_subtrahendAdvanced) {
607                    l_x3 = x_dataset.getXValue(1, l_subtrahendItem);
608                    l_y3 = x_dataset.getYValue(1, l_subtrahendItem);
609                    l_subtrahendCurX = new Double(l_x3);
610                    l_subtrahendCurY = new Double(l_y3);
611    
612                    if (!b_subtrahendAtIntersect) {
613                        l_subtrahendXs.add(l_subtrahendCurX);
614                        l_subtrahendYs.add(l_subtrahendCurY);
615                    }
616    
617                    l_subtrahendMaxY = Math.max(l_subtrahendMaxY, l_y3);
618                    l_subtrahendMinY = Math.min(l_subtrahendMinY, l_y3);
619    
620                    l_x4 = x_dataset.getXValue(1, l_subtrahendItem + 1);
621                    l_y4 = x_dataset.getYValue(1, l_subtrahendItem + 1);
622                    l_subtrahendNextX = new Double(l_x4);
623                    l_subtrahendNextY = new Double(l_y4);
624                }
625    
626                // deassert b_*FastForward (only matters for 1st time through loop)
627                b_minuendFastForward    = false;
628                b_subtrahendFastForward = false;
629    
630                Double l_intersectX = null;
631                Double l_intersectY = null;
632                boolean b_intersect = false;
633    
634                b_minuendAtIntersect    = false;
635                b_subtrahendAtIntersect = false;
636    
637                // check for intersect
638                if ((l_x2 == l_x4) && (l_y2 == l_y4)) {
639                    // check if line segments are colinear
640                    if ((l_x1 == l_x3) && (l_y1 == l_y3)) {
641                        b_colinear = true;
642                    }
643                    else {
644                        // the intersect is at the next point for both the minuend
645                        // and subtrahend
646                        l_intersectX = new Double(l_x2);
647                        l_intersectY = new Double(l_y2);
648    
649                        b_intersect             = true;
650                        b_minuendAtIntersect    = true;
651                        b_subtrahendAtIntersect = true;
652                     }
653                }
654                else {
655                    // compute common denominator
656                    double l_denominator = ((l_y4 - l_y3) * (l_x2 - l_x1))
657                            - ((l_x4 - l_x3) * (l_y2 - l_y1));
658    
659                    // compute common deltas
660                    double l_deltaY = l_y1 - l_y3;
661                    double l_deltaX = l_x1 - l_x3;
662    
663                    // compute numerators
664                    double l_numeratorA = ((l_x4 - l_x3) * l_deltaY)
665                            - ((l_y4 - l_y3) * l_deltaX);
666                    double l_numeratorB = ((l_x2 - l_x1) * l_deltaY)
667                            - ((l_y2 - l_y1) * l_deltaX);
668    
669                    // check if line segments are colinear
670                    if ((0 == l_numeratorA) && (0 == l_numeratorB)
671                            && (0 == l_denominator)) {
672                        b_colinear = true;
673                    }
674                    else {
675                        // check if previously colinear
676                        if (b_colinear) {
677                            // clear colinear points and flag
678                            l_minuendXs.clear();
679                            l_minuendYs.clear();
680                            l_subtrahendXs.clear();
681                            l_subtrahendYs.clear();
682                            l_polygonXs.clear();
683                            l_polygonYs.clear();
684    
685                            b_colinear = false;
686    
687                            // set new starting point for the polygon
688                            boolean b_useMinuend = ((l_x3 <= l_x1)
689                                    && (l_x1 <= l_x4));
690                            l_polygonXs.add(b_useMinuend ? l_minuendCurX
691                                    : l_subtrahendCurX);
692                            l_polygonYs.add(b_useMinuend ? l_minuendCurY
693                                    : l_subtrahendCurY);
694                        }
695    
696                        // compute slope components
697                        double l_slopeA = l_numeratorA / l_denominator;
698                        double l_slopeB = l_numeratorB / l_denominator;
699    
700                        // check if the line segments intersect
701                        if ((0 < l_slopeA) && (l_slopeA <= 1) && (0 < l_slopeB)
702                                && (l_slopeB <= 1)) {
703                            // compute the point of intersection
704                            double l_xi = l_x1 + (l_slopeA * (l_x2 - l_x1));
705                            double l_yi = l_y1 + (l_slopeA * (l_y2 - l_y1));
706    
707                            l_intersectX            = new Double(l_xi);
708                            l_intersectY            = new Double(l_yi);
709                            b_intersect             = true;
710                            b_minuendAtIntersect    = ((l_xi == l_x2)
711                                    && (l_yi == l_y2));
712                            b_subtrahendAtIntersect = ((l_xi == l_x4)
713                                    && (l_yi == l_y4));
714    
715                            // advance minuend and subtrahend to intesect
716                            l_minuendCurX    = l_intersectX;
717                            l_minuendCurY    = l_intersectY;
718                            l_subtrahendCurX = l_intersectX;
719                            l_subtrahendCurY = l_intersectY;
720                        }
721                    }
722                }
723    
724                if (b_intersect) {
725                    // create the polygon
726                    // add the minuend's points to polygon
727                    l_polygonXs.addAll(l_minuendXs);
728                    l_polygonYs.addAll(l_minuendYs);
729    
730                    // add intersection point to the polygon
731                    l_polygonXs.add(l_intersectX);
732                    l_polygonYs.add(l_intersectY);
733    
734                    // add the subtrahend's points to the polygon in reverse
735                    Collections.reverse(l_subtrahendXs);
736                    Collections.reverse(l_subtrahendYs);
737                    l_polygonXs.addAll(l_subtrahendXs);
738                    l_polygonYs.addAll(l_subtrahendYs);
739    
740                    // create an actual polygon
741                    b_positive = (l_subtrahendMaxY <= l_minuendMaxY)
742                            && (l_subtrahendMinY <= l_minuendMinY);
743                    createPolygon(x_graphics, x_dataArea, x_plot, x_domainAxis,
744                            x_rangeAxis, b_positive, l_polygonXs, l_polygonYs);
745    
746                    // clear the point vectors
747                    l_minuendXs.clear();
748                    l_minuendYs.clear();
749                    l_subtrahendXs.clear();
750                    l_subtrahendYs.clear();
751                    l_polygonXs.clear();
752                    l_polygonYs.clear();
753    
754                    // set the maxY and minY values to intersect y-value
755                    double l_y       = l_intersectY.doubleValue();
756                    l_minuendMaxY    = l_y;
757                    l_subtrahendMaxY = l_y;
758                    l_minuendMinY    = l_y;
759                    l_subtrahendMinY = l_y;
760    
761                    // add interection point to new polygon
762                    l_polygonXs.add(l_intersectX);
763                    l_polygonYs.add(l_intersectY);
764                }
765    
766                // advance the minuend if needed
767                if (l_x2 <= l_x4) {
768                    l_minuendItem++;
769                    b_minuendAdvanced = true;
770                }
771                else {
772                    b_minuendAdvanced = false;
773                }
774    
775                // advance the subtrahend if needed
776                if (l_x4 <= l_x2) {
777                    l_subtrahendItem++;
778                    b_subtrahendAdvanced = true;
779                }
780                else {
781                    b_subtrahendAdvanced = false;
782                }
783    
784                b_minuendDone    = (l_minuendItem == (l_minuendItemCount - 1));
785                b_subtrahendDone = (l_subtrahendItem == (l_subtrahendItemCount
786                        - 1));
787            }
788    
789            // check if the final polygon needs to be clipped
790            if (b_minuendDone && (l_x3 < l_x2) && (l_x2 < l_x4)) {
791                // project onto subtrahend
792                double l_slope    = (l_y4 - l_y3) / (l_x4 - l_x3);
793                l_subtrahendNextX = l_minuendNextX;
794                l_subtrahendNextY = new Double((l_slope * l_x2)
795                        + (l_y3 - (l_slope * l_x3)));
796            }
797    
798            if (b_subtrahendDone && (l_x1 < l_x4) && (l_x4 < l_x2)) {
799                // project onto minuend
800                double l_slope = (l_y2 - l_y1) / (l_x2 - l_x1);
801                l_minuendNextX = l_subtrahendNextX;
802                l_minuendNextY = new Double((l_slope * l_x4)
803                        + (l_y1 - (l_slope * l_x1)));
804            }
805    
806            // consider last point of minuend and subtrahend for determining
807            // positivity
808            l_minuendMaxY    = Math.max(l_minuendMaxY,
809                    l_minuendNextY.doubleValue());
810            l_subtrahendMaxY = Math.max(l_subtrahendMaxY,
811                    l_subtrahendNextY.doubleValue());
812            l_minuendMinY    = Math.min(l_minuendMinY,
813                    l_minuendNextY.doubleValue());
814            l_subtrahendMinY = Math.min(l_subtrahendMinY,
815                    l_subtrahendNextY.doubleValue());
816    
817            // add the last point of the minuned and subtrahend
818            l_minuendXs.add(l_minuendNextX);
819            l_minuendYs.add(l_minuendNextY);
820            l_subtrahendXs.add(l_subtrahendNextX);
821            l_subtrahendYs.add(l_subtrahendNextY);
822    
823            // create the polygon
824            // add the minuend's points to polygon
825            l_polygonXs.addAll(l_minuendXs);
826            l_polygonYs.addAll(l_minuendYs);
827    
828            // add the subtrahend's points to the polygon in reverse
829            Collections.reverse(l_subtrahendXs);
830            Collections.reverse(l_subtrahendYs);
831            l_polygonXs.addAll(l_subtrahendXs);
832            l_polygonYs.addAll(l_subtrahendYs);
833    
834            // create an actual polygon
835            b_positive = (l_subtrahendMaxY <= l_minuendMaxY)
836                    && (l_subtrahendMinY <= l_minuendMinY);
837            createPolygon(x_graphics, x_dataArea, x_plot, x_domainAxis,
838                    x_rangeAxis, b_positive, l_polygonXs, l_polygonYs);
839        }
840    
841        /**
842         * Draws the visual representation of a single data item, second pass.  In
843         * the second pass, the renderer draws the lines and shapes for the
844         * individual points in the two series.
845         *
846         * @param x_graphics  the graphics device.
847         * @param x_dataArea  the area within which the data is being drawn.
848         * @param x_info  collects information about the drawing.
849         * @param x_plot  the plot (can be used to obtain standard color
850         *         information etc).
851         * @param x_domainAxis  the domain (horizontal) axis.
852         * @param x_rangeAxis  the range (vertical) axis.
853         * @param x_dataset  the dataset.
854         * @param x_series  the series index (zero-based).
855         * @param x_item  the item index (zero-based).
856         * @param x_crosshairState  crosshair information for the plot
857         *                          (<code>null</code> permitted).
858         */
859        protected void drawItemPass1(Graphics2D x_graphics,
860                                     Rectangle2D x_dataArea,
861                                     PlotRenderingInfo x_info,
862                                     XYPlot x_plot,
863                                     ValueAxis x_domainAxis,
864                                     ValueAxis x_rangeAxis,
865                                     XYDataset x_dataset,
866                                     int x_series,
867                                     int x_item,
868                                     CrosshairState x_crosshairState) {
869    
870            Shape l_entityArea = null;
871            EntityCollection l_entities = null;
872            if (null != x_info) {
873                l_entities = x_info.getOwner().getEntityCollection();
874            }
875    
876            Paint l_seriesPaint   = getItemPaint(x_series, x_item);
877            Stroke l_seriesStroke = getItemStroke(x_series, x_item);
878            x_graphics.setPaint(l_seriesPaint);
879            x_graphics.setStroke(l_seriesStroke);
880    
881            PlotOrientation l_orientation      = x_plot.getOrientation();
882            RectangleEdge l_domainAxisLocation = x_plot.getDomainAxisEdge();
883            RectangleEdge l_rangeAxisLocation  = x_plot.getRangeAxisEdge();
884    
885            double l_x0 = x_dataset.getXValue(x_series, x_item);
886            double l_y0 = x_dataset.getYValue(x_series, x_item);
887            double l_x1 = x_domainAxis.valueToJava2D(l_x0, x_dataArea,
888                    l_domainAxisLocation);
889            double l_y1 = x_rangeAxis.valueToJava2D(l_y0, x_dataArea,
890                    l_rangeAxisLocation);
891    
892            if (getShapesVisible()) {
893                Shape l_shape = getItemShape(x_series, x_item);
894                if (l_orientation == PlotOrientation.HORIZONTAL) {
895                    l_shape = ShapeUtilities.createTranslatedShape(l_shape,
896                            l_y1, l_x1);
897                }
898                else {
899                    l_shape = ShapeUtilities.createTranslatedShape(l_shape,
900                            l_x1, l_y1);
901                }
902                if (l_shape.intersects(x_dataArea)) {
903                    x_graphics.setPaint(getItemPaint(x_series, x_item));
904                    x_graphics.fill(l_shape);
905                }
906                l_entityArea = l_shape;
907            }
908    
909            // add an entity for the item...
910            if (null != l_entities) {
911                if (null == l_entityArea) {
912                    l_entityArea = new Rectangle2D.Double((l_x1 - 2), (l_y1 - 2),
913                            4, 4);
914                }
915                String l_tip = null;
916                XYToolTipGenerator l_tipGenerator = getToolTipGenerator(x_series,
917                        x_item);
918                if (null != l_tipGenerator) {
919                    l_tip = l_tipGenerator.generateToolTip(x_dataset, x_series,
920                            x_item);
921                }
922                String l_url = null;
923                XYURLGenerator l_urlGenerator = getURLGenerator();
924                if (null != l_urlGenerator) {
925                    l_url = l_urlGenerator.generateURL(x_dataset, x_series,
926                            x_item);
927                }
928                XYItemEntity l_entity = new XYItemEntity(l_entityArea, x_dataset,
929                        x_series, x_item, l_tip, l_url);
930                l_entities.add(l_entity);
931            }
932    
933            // draw the item label if there is one...
934            if (isItemLabelVisible(x_series, x_item)) {
935                drawItemLabel(x_graphics, l_orientation, x_dataset, x_series,
936                              x_item, l_x1, l_y1, (l_y1 < 0.0));
937            }
938    
939            int l_domainAxisIndex = x_plot.getDomainAxisIndex(x_domainAxis);
940            int l_rangeAxisIndex  = x_plot.getRangeAxisIndex(x_rangeAxis);
941            updateCrosshairValues(x_crosshairState, l_x0, l_y0, l_domainAxisIndex,
942                                  l_rangeAxisIndex, l_x1, l_y1, l_orientation);
943    
944            if (0 == x_item) {
945                return;
946            }
947    
948            double l_x2 = x_domainAxis.valueToJava2D(x_dataset.getXValue(x_series,
949                    (x_item - 1)), x_dataArea, l_domainAxisLocation);
950            double l_y2 = x_rangeAxis.valueToJava2D(x_dataset.getYValue(x_series,
951                    (x_item - 1)), x_dataArea, l_rangeAxisLocation);
952    
953            Line2D l_line = null;
954            if (PlotOrientation.HORIZONTAL == l_orientation) {
955                l_line = new Line2D.Double(l_y1, l_x1, l_y2, l_x2);
956            }
957            else if (PlotOrientation.VERTICAL == l_orientation) {
958                l_line = new Line2D.Double(l_x1, l_y1, l_x2, l_y2);
959            }
960    
961            if ((null != l_line) && l_line.intersects(x_dataArea)) {
962                x_graphics.setPaint(getItemPaint(x_series, x_item));
963                x_graphics.setStroke(getItemStroke(x_series, x_item));
964                x_graphics.draw(l_line);
965            }
966        }
967    
968        /**
969         * Determines if a dataset is degenerate.  A degenerate dataset is a
970         * dataset where either series has less than two (2) points.
971         *
972         * @param x_dataset  the dataset.
973         * @param x_impliedZeroSubtrahend  if false, do not check the subtrahend
974         *
975         * @return true if the dataset is degenerate.
976         */
977        private boolean isEitherSeriesDegenerate(XYDataset x_dataset,
978                boolean x_impliedZeroSubtrahend) {
979    
980            if (x_impliedZeroSubtrahend) {
981                return (x_dataset.getItemCount(0) < 2);
982            }
983    
984            return ((x_dataset.getItemCount(0) < 2)
985                    || (x_dataset.getItemCount(1) < 2));
986        }
987    
988        /**
989         * Determines if the two (2) series are disjoint.
990         * Disjoint series do not overlap in the domain space.
991         *
992         * @param x_dataset  the dataset.
993         *
994         * @return true if the dataset is degenerate.
995         */
996        private boolean areSeriesDisjoint(XYDataset x_dataset) {
997    
998            int l_minuendItemCount = x_dataset.getItemCount(0);
999            double l_minuendFirst  = x_dataset.getXValue(0, 0);
1000            double l_minuendLast   = x_dataset.getXValue(0, l_minuendItemCount - 1);
1001    
1002            int l_subtrahendItemCount = x_dataset.getItemCount(1);
1003            double l_subtrahendFirst  = x_dataset.getXValue(1, 0);
1004            double l_subtrahendLast   = x_dataset.getXValue(1,
1005                    l_subtrahendItemCount - 1);
1006    
1007            return ((l_minuendLast < l_subtrahendFirst)
1008                    || (l_subtrahendLast < l_minuendFirst));
1009        }
1010    
1011        /**
1012         * Draws the visual representation of a polygon
1013         *
1014         * @param x_graphics  the graphics device.
1015         * @param x_dataArea  the area within which the data is being drawn.
1016         * @param x_plot  the plot (can be used to obtain standard color
1017         *                information etc).
1018         * @param x_domainAxis  the domain (horizontal) axis.
1019         * @param x_rangeAxis  the range (vertical) axis.
1020         * @param x_positive  indicates if the polygon is positive (true) or
1021         *                    negative (false).
1022         * @param x_xValues  a linked list of the x values (expects values to be
1023         *                   of type Double).
1024         * @param x_yValues  a linked list of the y values (expects values to be
1025         *                   of type Double).
1026         */
1027        private void createPolygon (Graphics2D x_graphics,
1028                                    Rectangle2D x_dataArea,
1029                                    XYPlot x_plot,
1030                                    ValueAxis x_domainAxis,
1031                                    ValueAxis x_rangeAxis,
1032                                    boolean x_positive,
1033                                    LinkedList x_xValues,
1034                                    LinkedList x_yValues) {
1035    
1036            PlotOrientation l_orientation      = x_plot.getOrientation();
1037            RectangleEdge l_domainAxisLocation = x_plot.getDomainAxisEdge();
1038            RectangleEdge l_rangeAxisLocation  = x_plot.getRangeAxisEdge();
1039    
1040            Object[] l_xValues = x_xValues.toArray();
1041            Object[] l_yValues = x_yValues.toArray();
1042    
1043            GeneralPath l_path = new GeneralPath();
1044    
1045            if (PlotOrientation.VERTICAL == l_orientation) {
1046                double l_x = x_domainAxis.valueToJava2D((
1047                        (Double) l_xValues[0]).doubleValue(), x_dataArea,
1048                        l_domainAxisLocation);
1049                if (this.roundXCoordinates) {
1050                    l_x = Math.rint(l_x);
1051                }
1052    
1053                double l_y = x_rangeAxis.valueToJava2D((
1054                        (Double) l_yValues[0]).doubleValue(), x_dataArea,
1055                        l_rangeAxisLocation);
1056    
1057                l_path.moveTo((float) l_x, (float) l_y);
1058                for (int i = 1; i < l_xValues.length; i++) {
1059                    l_x = x_domainAxis.valueToJava2D((
1060                            (Double) l_xValues[i]).doubleValue(), x_dataArea,
1061                            l_domainAxisLocation);
1062                    if (this.roundXCoordinates) {
1063                        l_x = Math.rint(l_x);
1064                    }
1065    
1066                    l_y = x_rangeAxis.valueToJava2D((
1067                            (Double) l_yValues[i]).doubleValue(), x_dataArea,
1068                            l_rangeAxisLocation);
1069                    l_path.lineTo((float) l_x, (float) l_y);
1070                }
1071                l_path.closePath();
1072            }
1073            else {
1074                double l_x = x_domainAxis.valueToJava2D((
1075                        (Double) l_xValues[0]).doubleValue(), x_dataArea,
1076                        l_domainAxisLocation);
1077                if (this.roundXCoordinates) {
1078                    l_x = Math.rint(l_x);
1079                }
1080    
1081                double l_y = x_rangeAxis.valueToJava2D((
1082                        (Double) l_yValues[0]).doubleValue(), x_dataArea,
1083                        l_rangeAxisLocation);
1084    
1085                l_path.moveTo((float) l_y, (float) l_x);
1086                for (int i = 1; i < l_xValues.length; i++) {
1087                    l_x = x_domainAxis.valueToJava2D((
1088                            (Double) l_xValues[i]).doubleValue(), x_dataArea,
1089                            l_domainAxisLocation);
1090                    if (this.roundXCoordinates) {
1091                        l_x = Math.rint(l_x);
1092                    }
1093    
1094                    l_y = x_rangeAxis.valueToJava2D((
1095                            (Double) l_yValues[i]).doubleValue(), x_dataArea,
1096                            l_rangeAxisLocation);
1097                    l_path.lineTo((float) l_y, (float) l_x);
1098                }
1099                l_path.closePath();
1100            }
1101    
1102            if (l_path.intersects(x_dataArea)) {
1103                x_graphics.setPaint(x_positive ? getPositivePaint()
1104                        : getNegativePaint());
1105                x_graphics.fill(l_path);
1106            }
1107        }
1108    
1109        /**
1110         * Returns a default legend item for the specified series.  Subclasses
1111         * should override this method to generate customised items.
1112         *
1113         * @param datasetIndex  the dataset index (zero-based).
1114         * @param series  the series index (zero-based).
1115         *
1116         * @return A legend item for the series.
1117         */
1118        public LegendItem getLegendItem(int datasetIndex, int series) {
1119            LegendItem result = null;
1120            XYPlot p = getPlot();
1121            if (p != null) {
1122                XYDataset dataset = p.getDataset(datasetIndex);
1123                if (dataset != null) {
1124                    if (getItemVisible(series, 0)) {
1125                        String label = getLegendItemLabelGenerator().generateLabel(
1126                                dataset, series);
1127                        String description = label;
1128                        String toolTipText = null;
1129                        if (getLegendItemToolTipGenerator() != null) {
1130                            toolTipText
1131                                = getLegendItemToolTipGenerator().generateLabel(
1132                                        dataset, series);
1133                        }
1134                        String urlText = null;
1135                        if (getLegendItemURLGenerator() != null) {
1136                            urlText = getLegendItemURLGenerator().generateLabel(
1137                                    dataset, series);
1138                        }
1139                        Paint paint = lookupSeriesPaint(series);
1140                        Stroke stroke = lookupSeriesStroke(series);
1141                        Shape line = getLegendLine();
1142                        result = new LegendItem(label, description,
1143                                toolTipText, urlText, line, stroke, paint);
1144                        result.setLabelFont(lookupLegendTextFont(series));
1145                        Paint labelPaint = lookupLegendTextPaint(series);
1146                        if (labelPaint != null) {
1147                            result.setLabelPaint(labelPaint);
1148                        }
1149                        result.setDataset(dataset);
1150                        result.setDatasetIndex(datasetIndex);
1151                        result.setSeriesKey(dataset.getSeriesKey(series));
1152                        result.setSeriesIndex(series);
1153                    }
1154                }
1155    
1156            }
1157    
1158            return result;
1159    
1160        }
1161    
1162        /**
1163         * Tests this renderer for equality with an arbitrary object.
1164         *
1165         * @param obj  the object (<code>null</code> permitted).
1166         *
1167         * @return A boolean.
1168         */
1169        public boolean equals(Object obj) {
1170            if (obj == this) {
1171                return true;
1172            }
1173            if (!(obj instanceof XYDifferenceRenderer)) {
1174                return false;
1175            }
1176            if (!super.equals(obj)) {
1177                return false;
1178            }
1179            XYDifferenceRenderer that = (XYDifferenceRenderer) obj;
1180            if (!PaintUtilities.equal(this.positivePaint, that.positivePaint)) {
1181                return false;
1182            }
1183            if (!PaintUtilities.equal(this.negativePaint, that.negativePaint)) {
1184                return false;
1185            }
1186            if (this.shapesVisible != that.shapesVisible) {
1187                return false;
1188            }
1189            if (!ShapeUtilities.equal(this.legendLine, that.legendLine)) {
1190                return false;
1191            }
1192            if (this.roundXCoordinates != that.roundXCoordinates) {
1193                return false;
1194            }
1195            return true;
1196        }
1197    
1198        /**
1199         * Returns a clone of the renderer.
1200         *
1201         * @return A clone.
1202         *
1203         * @throws CloneNotSupportedException  if the renderer cannot be cloned.
1204         */
1205        public Object clone() throws CloneNotSupportedException {
1206            XYDifferenceRenderer clone = (XYDifferenceRenderer) super.clone();
1207            clone.legendLine = ShapeUtilities.clone(this.legendLine);
1208            return clone;
1209        }
1210    
1211        /**
1212         * Provides serialization support.
1213         *
1214         * @param stream  the output stream.
1215         *
1216         * @throws IOException  if there is an I/O error.
1217         */
1218        private void writeObject(ObjectOutputStream stream) throws IOException {
1219            stream.defaultWriteObject();
1220            SerialUtilities.writePaint(this.positivePaint, stream);
1221            SerialUtilities.writePaint(this.negativePaint, stream);
1222            SerialUtilities.writeShape(this.legendLine, stream);
1223        }
1224    
1225        /**
1226         * Provides serialization support.
1227         *
1228         * @param stream  the input stream.
1229         *
1230         * @throws IOException  if there is an I/O error.
1231         * @throws ClassNotFoundException  if there is a classpath problem.
1232         */
1233        private void readObject(ObjectInputStream stream)
1234            throws IOException, ClassNotFoundException {
1235            stream.defaultReadObject();
1236            this.positivePaint = SerialUtilities.readPaint(stream);
1237            this.negativePaint = SerialUtilities.readPaint(stream);
1238            this.legendLine = SerialUtilities.readShape(stream);
1239        }
1240    
1241    }