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