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