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