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