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