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