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