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