001    /* ===========================================================
002     * JFreeChart : a free chart library for the Java(tm) platform
003     * ===========================================================
004     *
005     * (C) Copyright 2000-2005, 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-2005, by Object Refinery Limited.
031     *
032     * Original Author:  David Gilbert (for Object Refinery Limited);
033     * Contributor(s):   Christian W. Zuckschwerdt;
034     *
035     * $Id: XYDifferenceRenderer.java,v 1.12.2.6 2005/12/10 21:51:02 mungady Exp $
036     *
037     * Changes:
038     * --------
039     * 30-Apr-2003 : Version 1 (DG);
040     * 30-Jul-2003 : Modified entity constructor (CZ);
041     * 20-Aug-2003 : Implemented Cloneable and PublicCloneable (DG);
042     * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG);
043     * 09-Feb-2004 : Updated to support horizontal plot orientation (DG);
044     * 10-Feb-2004 : Added default constructor, setter methods and updated 
045     *               Javadocs (DG);
046     * 25-Feb-2004 : Replaced CrosshairInfo with CrosshairState (DG);
047     * 30-Mar-2004 : Fixed bug in getNegativePaint() method (DG);
048     * 15-Jul-2004 : Switched getX() with getXValue() and getY() with 
049     *               getYValue() (DG);
050     * 25-Aug-2004 : Fixed a bug preventing the use of crosshairs (DG);
051     * 11-Nov-2004 : Now uses ShapeUtilities to translate shapes (DG);
052     * 19-Jan-2005 : Now accesses only primitive values from dataset (DG);
053     * 22-Feb-2005 : Override getLegendItem(int, int) to return "line" items (DG);
054     * 13-Apr-2005 : Fixed shape positioning bug (id = 1182062) (DG);
055     * 20-Apr-2005 : Use generators for legend tooltips and URLs (DG);
056     * 04-May-2005 : Override equals() method, renamed get/setPlotShapes() -->
057     *               get/setShapesVisible (DG);
058     * 09-Jun-2005 : Updated equals() to handle GradientPaint (DG);
059     * 16-Jun-2005 : Fix bug (1221021) affecting stroke used for each series (DG);
060     * 
061     */
062    
063    package org.jfree.chart.renderer.xy;
064    
065    import java.awt.Color;
066    import java.awt.Graphics2D;
067    import java.awt.Paint;
068    import java.awt.Shape;
069    import java.awt.Stroke;
070    import java.awt.geom.GeneralPath;
071    import java.awt.geom.Line2D;
072    import java.awt.geom.Rectangle2D;
073    import java.io.IOException;
074    import java.io.ObjectInputStream;
075    import java.io.ObjectOutputStream;
076    import java.io.Serializable;
077    
078    import org.jfree.chart.LegendItem;
079    import org.jfree.chart.axis.ValueAxis;
080    import org.jfree.chart.entity.EntityCollection;
081    import org.jfree.chart.entity.XYItemEntity;
082    import org.jfree.chart.event.RendererChangeEvent;
083    import org.jfree.chart.labels.XYToolTipGenerator;
084    import org.jfree.chart.plot.CrosshairState;
085    import org.jfree.chart.plot.PlotOrientation;
086    import org.jfree.chart.plot.PlotRenderingInfo;
087    import org.jfree.chart.plot.XYPlot;
088    import org.jfree.data.xy.XYDataset;
089    import org.jfree.io.SerialUtilities;
090    import org.jfree.ui.RectangleEdge;
091    import org.jfree.util.PaintUtilities;
092    import org.jfree.util.PublicCloneable;
093    import org.jfree.util.ShapeUtilities;
094    
095    /**
096     * A renderer for an {@link XYPlot} that highlights the differences between two
097     * series.  The renderer expects a dataset that:
098     * <ul>
099     * <li>has exactly two series;</li>
100     * <li>each series has the same x-values;</li>
101     * <li>no <code>null</code> values;
102     * </ul>
103     */
104    public class XYDifferenceRenderer extends AbstractXYItemRenderer 
105                                      implements XYItemRenderer, 
106                                                 Cloneable,
107                                                 PublicCloneable,
108                                                 Serializable {
109    
110        /** For serialization. */
111        private static final long serialVersionUID = -8447915602375584857L;
112        
113        /** The paint used to highlight positive differences (y(0) > y(1)). */
114        private transient Paint positivePaint;
115    
116        /** The paint used to highlight negative differences (y(0) < y(1)). */
117        private transient Paint negativePaint;
118    
119        /** Display shapes at each point? */
120        private boolean shapesVisible;
121        
122        /** The shape to display in the legend item. */
123        private transient Shape legendLine;
124    
125        /**
126         * Creates a new renderer with default attributes.
127         */
128        public XYDifferenceRenderer() {
129            this(Color.green, Color.red, false);
130        }
131        
132        /**
133         * Creates a new renderer.
134         *
135         * @param positivePaint  the highlight color for positive differences 
136         *                       (<code>null</code> not permitted).
137         * @param negativePaint  the highlight color for negative differences 
138         *                       (<code>null</code> not permitted).
139         * @param shapes  draw shapes?
140         */
141        public XYDifferenceRenderer(Paint positivePaint, Paint negativePaint, 
142                                    boolean shapes) {
143            if (positivePaint == null) {
144                throw new IllegalArgumentException(
145                        "Null 'positivePaint' argument.");
146            }
147            if (negativePaint == null) {
148                throw new IllegalArgumentException(
149                        "Null 'negativePaint' argument.");
150            }
151            this.positivePaint = positivePaint;
152            this.negativePaint = negativePaint;
153            this.shapesVisible = shapes;
154            this.legendLine = new Line2D.Double(-7.0, 0.0, 7.0, 0.0);
155        }
156    
157        /**
158         * Returns the paint used to highlight positive differences.
159         *
160         * @return The paint (never <code>null</code>).
161         */
162        public Paint getPositivePaint() {
163            return this.positivePaint;
164        }
165    
166        /**
167         * Sets the paint used to highlight positive differences.
168         * 
169         * @param paint  the paint (<code>null</code> not permitted).
170         */
171        public void setPositivePaint(Paint paint) {
172            if (paint == null) {
173                throw new IllegalArgumentException("Null 'paint' argument.");
174            }
175            this.positivePaint = paint;
176            notifyListeners(new RendererChangeEvent(this));
177        }
178    
179        /**
180         * Returns the paint used to highlight negative differences.
181         *
182         * @return The paint (never <code>null</code>).
183         */
184        public Paint getNegativePaint() {
185            return this.negativePaint;
186        }
187        
188        /**
189         * Sets the paint used to highlight negative differences.
190         * 
191         * @param paint  the paint (<code>null</code> not permitted).
192         */
193        public void setNegativePaint(Paint paint) {
194            if (paint == null) {
195                throw new IllegalArgumentException("Null 'paint' argument.");
196            }
197            this.negativePaint = paint;
198            notifyListeners(new RendererChangeEvent(this));
199        }
200    
201        /**
202         * Returns a flag that controls whether or not shapes are drawn for each 
203         * data value.
204         * 
205         * @return A boolean.
206         */
207        public boolean getShapesVisible() {
208            return this.shapesVisible;
209        }
210    
211        /**
212         * Sets a flag that controls whether or not shapes are drawn for each 
213         * data value.
214         * 
215         * @param flag  the flag.
216         */
217        public void setShapesVisible(boolean flag) {
218            this.shapesVisible = flag;
219            notifyListeners(new RendererChangeEvent(this));
220        }
221        
222        /**
223         * Returns the shape used to represent a line in the legend.
224         * 
225         * @return The legend line (never <code>null</code>).
226         */
227        public Shape getLegendLine() {
228            return this.legendLine;   
229        }
230        
231        /**
232         * Sets the shape used as a line in each legend item and sends a 
233         * {@link RendererChangeEvent} to all registered listeners.
234         * 
235         * @param line  the line (<code>null</code> not permitted).
236         */
237        public void setLegendLine(Shape line) {
238            if (line == null) {
239                throw new IllegalArgumentException("Null 'line' argument.");   
240            }
241            this.legendLine = line;
242            notifyListeners(new RendererChangeEvent(this));
243        }
244    
245        /**
246         * Initialises the renderer and returns a state object that should be 
247         * passed to subsequent calls to the drawItem() method.  This method will 
248         * be called before the first item is rendered, giving the renderer an 
249         * opportunity to initialise any state information it wants to maintain.  
250         * The renderer can do nothing if it chooses.
251         *
252         * @param g2  the graphics device.
253         * @param dataArea  the area inside the axes.
254         * @param plot  the plot.
255         * @param data  the data.
256         * @param info  an optional info collection object to return data back to 
257         *              the caller.
258         *
259         * @return A state object.
260         */
261        public XYItemRendererState initialise(Graphics2D g2,
262                                              Rectangle2D dataArea,
263                                              XYPlot plot,
264                                              XYDataset data,
265                                              PlotRenderingInfo info) {
266    
267            return super.initialise(g2, dataArea, plot, data, info);
268    
269        }
270    
271        /**
272         * Returns <code>2</code>, the number of passes required by the renderer.  
273         * The {@link XYPlot} will run through the dataset this number of times.
274         * 
275         * @return The number of passes required by the renderer.
276         */
277        public int getPassCount() {
278            return 2;
279        }
280        
281        /**
282         * Draws the visual representation of a single data item.
283         *
284         * @param g2  the graphics device.
285         * @param state  the renderer state.
286         * @param dataArea  the area within which the data is being drawn.
287         * @param info  collects information about the drawing.
288         * @param plot  the plot (can be used to obtain standard color 
289         *              information etc).
290         * @param domainAxis  the domain (horizontal) axis.
291         * @param rangeAxis  the range (vertical) axis.
292         * @param dataset  the dataset.
293         * @param series  the series index (zero-based).
294         * @param item  the item index (zero-based).
295         * @param crosshairState  crosshair information for the plot 
296         *                        (<code>null</code> permitted).
297         * @param pass  the pass index.
298         */
299        public void drawItem(Graphics2D g2,
300                             XYItemRendererState state,
301                             Rectangle2D dataArea,
302                             PlotRenderingInfo info,
303                             XYPlot plot,
304                             ValueAxis domainAxis,
305                             ValueAxis rangeAxis,
306                             XYDataset dataset,
307                             int series,
308                             int item,
309                             CrosshairState crosshairState,
310                             int pass) {
311    
312            if (pass == 0) {
313                drawItemPass0(g2, dataArea, info, plot, domainAxis, rangeAxis, 
314                        dataset, series, item, crosshairState);
315            }
316            else if (pass == 1) {
317                drawItemPass1(g2, dataArea, info, plot, domainAxis, rangeAxis, 
318                        dataset, series, item, crosshairState);
319            }
320    
321        }
322    
323        /**
324         * Draws the visual representation of a single data item, first pass.
325         *
326         * @param g2  the graphics device.
327         * @param dataArea  the area within which the data is being drawn.
328         * @param info  collects information about the drawing.
329         * @param plot  the plot (can be used to obtain standard color 
330         *              information etc).
331         * @param domainAxis  the domain (horizontal) axis.
332         * @param rangeAxis  the range (vertical) axis.
333         * @param dataset  the dataset.
334         * @param series  the series index (zero-based).
335         * @param item  the item index (zero-based).
336         * @param crosshairState  crosshair information for the plot 
337         *                        (<code>null</code> permitted).
338         */
339        protected void drawItemPass0(Graphics2D g2,
340                                     Rectangle2D dataArea,
341                                     PlotRenderingInfo info,
342                                     XYPlot plot,
343                                     ValueAxis domainAxis,
344                                     ValueAxis rangeAxis,
345                                     XYDataset dataset,
346                                     int series,
347                                     int item,
348                                     CrosshairState crosshairState) {
349    
350            if (series == 0) {
351    
352                PlotOrientation orientation = plot.getOrientation();
353                RectangleEdge domainAxisLocation = plot.getDomainAxisEdge();
354                RectangleEdge rangeAxisLocation = plot.getRangeAxisEdge();
355                
356                double y0 = dataset.getYValue(0, item);
357                double x1 = dataset.getXValue(1, item);
358                double y1 = dataset.getYValue(1, item);
359    
360                double transY0 = rangeAxis.valueToJava2D(y0, dataArea, 
361                        rangeAxisLocation);
362                double transX1 = domainAxis.valueToJava2D(x1, dataArea, 
363                        domainAxisLocation);
364                double transY1 = rangeAxis.valueToJava2D(y1, dataArea, 
365                        rangeAxisLocation);
366    
367                if (item > 0) {
368                    double prevx0 = dataset.getXValue(0, item - 1);
369                    double prevy0 = dataset.getYValue(0, item - 1);
370                    double prevy1 = dataset.getYValue(1, item - 1);
371    
372                    double prevtransX0 = domainAxis.valueToJava2D(prevx0, dataArea, 
373                            domainAxisLocation);
374                    double prevtransY0 = rangeAxis.valueToJava2D(prevy0, dataArea, 
375                            rangeAxisLocation);
376                    double prevtransY1 = rangeAxis.valueToJava2D(prevy1, dataArea, 
377                            rangeAxisLocation);
378    
379                    Shape positive = getPositiveArea((float) prevtransX0, 
380                            (float) prevtransY0, (float) prevtransY1,
381                            (float) transX1, (float) transY0, (float) transY1,
382                            orientation);
383                    if (positive != null) {
384                        g2.setPaint(getPositivePaint());
385                        g2.fill(positive);
386                    }
387    
388                    Shape negative = getNegativeArea((float) prevtransX0, 
389                            (float) prevtransY0, (float) prevtransY1,
390                            (float) transX1, (float) transY0, (float) transY1,
391                            orientation);
392    
393                    if (negative != null) {
394                        g2.setPaint(getNegativePaint());
395                        g2.fill(negative);
396                    }
397                }
398            }
399    
400        }
401    
402        /**
403         * Draws the visual representation of a single data item, second pass.  In 
404         * the second pass, the renderer draws the lines and shapes for the 
405         * individual points in the two series.
406         *
407         * @param g2  the graphics device.
408         * @param dataArea  the area within which the data is being drawn.
409         * @param info  collects information about the drawing.
410         * @param plot  the plot (can be used to obtain standard color information 
411         *              etc).
412         * @param domainAxis  the domain (horizontal) axis.
413         * @param rangeAxis  the range (vertical) axis.
414         * @param dataset  the dataset.
415         * @param series  the series index (zero-based).
416         * @param item  the item index (zero-based).
417         * @param crosshairState  crosshair information for the plot 
418         *                        (<code>null</code> permitted).
419         */
420        protected void drawItemPass1(Graphics2D g2,
421                                     Rectangle2D dataArea,
422                                     PlotRenderingInfo info,
423                                     XYPlot plot,
424                                     ValueAxis domainAxis,
425                                     ValueAxis rangeAxis,
426                                     XYDataset dataset,
427                                     int series,
428                                     int item,
429                                     CrosshairState crosshairState) {
430    
431            Shape entityArea = null;
432            EntityCollection entities = null;
433            if (info != null) {
434                entities = info.getOwner().getEntityCollection();
435            }
436    
437            Paint seriesPaint = getItemPaint(series, item);
438            Stroke seriesStroke = getItemStroke(series, item);
439            g2.setPaint(seriesPaint);
440            g2.setStroke(seriesStroke);
441    
442            if (series == 0) {
443    
444                PlotOrientation orientation = plot.getOrientation(); 
445                RectangleEdge domainAxisLocation = plot.getDomainAxisEdge();
446                RectangleEdge rangeAxisLocation = plot.getRangeAxisEdge();
447    
448                double x0 = dataset.getXValue(0, item);
449                double y0 = dataset.getYValue(0, item);
450                double x1 = dataset.getXValue(1, item);
451                double y1 = dataset.getYValue(1, item);
452    
453                double transX0 = domainAxis.valueToJava2D(x0, dataArea, 
454                        domainAxisLocation);
455                double transY0 = rangeAxis.valueToJava2D(y0, dataArea, 
456                        rangeAxisLocation);
457                double transX1 = domainAxis.valueToJava2D(x1, dataArea, 
458                        domainAxisLocation);
459                double transY1 = rangeAxis.valueToJava2D(y1, dataArea, 
460                        rangeAxisLocation);
461    
462                if (item > 0) {
463                    // get the previous data points...
464                    double prevx0 = dataset.getXValue(0, item - 1);
465                    double prevy0 = dataset.getYValue(0, item - 1);
466                    double prevx1 = dataset.getXValue(1, item - 1);
467                    double prevy1 = dataset.getYValue(1, item - 1);
468    
469                    double prevtransX0 = domainAxis.valueToJava2D(prevx0, dataArea,
470                            domainAxisLocation);
471                    double prevtransY0 = rangeAxis.valueToJava2D(prevy0, dataArea, 
472                            rangeAxisLocation);
473                    double prevtransX1 = domainAxis.valueToJava2D(prevx1, dataArea, 
474                            domainAxisLocation);
475                    double prevtransY1 = rangeAxis.valueToJava2D(prevy1, dataArea, 
476                            rangeAxisLocation);
477    
478                    Line2D line0 = null;
479                    Line2D line1 = null;
480                    if (orientation == PlotOrientation.HORIZONTAL) {
481                        line0 = new Line2D.Double(transY0, transX0, prevtransY0, 
482                                prevtransX0);
483                        line1 = new Line2D.Double(transY1, transX1, prevtransY1, 
484                                prevtransX1);
485                    }
486                    else if (orientation == PlotOrientation.VERTICAL) {
487                        line0 = new Line2D.Double(transX0, transY0, prevtransX0, 
488                                prevtransY0);
489                        line1 = new Line2D.Double(transX1, transY1, prevtransX1, 
490                                prevtransY1);
491                    }
492                    if (line0 != null && line0.intersects(dataArea)) {
493                        g2.setPaint(getItemPaint(series, item));
494                        g2.setStroke(getItemStroke(series, item));
495                        g2.draw(line0);
496                    }
497                    if (line1 != null && line1.intersects(dataArea)) {
498                        g2.setPaint(getItemPaint(1, item));
499                        g2.setStroke(getItemStroke(1, item));
500                        g2.draw(line1);
501                    }
502                }
503    
504                if (getShapesVisible()) {
505                    Shape shape0 = getItemShape(series, item);
506                    if (orientation == PlotOrientation.HORIZONTAL) {
507                        shape0 = ShapeUtilities.createTranslatedShape(shape0, 
508                                transY0, transX0);
509                    }
510                    else {  // vertical
511                        shape0 = ShapeUtilities.createTranslatedShape(shape0, 
512                                transX0, transY0);
513                    }
514                    if (shape0.intersects(dataArea)) {
515                        g2.setPaint(getItemPaint(series, item));
516                        g2.fill(shape0);
517                    }
518                    entityArea = shape0;
519    
520                    // add an entity for the item...
521                    if (entities != null) {
522                        if (entityArea == null) {
523                            entityArea = new Rectangle2D.Double(transX0 - 2, 
524                                    transY0 - 2, 4, 4);
525                        }
526                        String tip = null;
527                        XYToolTipGenerator generator = getToolTipGenerator(series, 
528                                item);
529                        if (generator != null) {
530                            tip = generator.generateToolTip(dataset, series, item);
531                        }
532                        String url = null;
533                        if (getURLGenerator() != null) {
534                            url = getURLGenerator().generateURL(dataset, series, 
535                                    item);
536                        }
537                        XYItemEntity entity = new XYItemEntity(entityArea, dataset, 
538                                series, item, tip, url);
539                        entities.add(entity);
540                    }
541    
542                    Shape shape1 = getItemShape(series + 1, item);
543                    if (orientation == PlotOrientation.HORIZONTAL) {
544                        shape1 = ShapeUtilities.createTranslatedShape(shape1, 
545                                transY1, transX1);
546                    }
547                    else {  // vertical
548                        shape1 = ShapeUtilities.createTranslatedShape(shape1, 
549                                transX1, transY1);
550                    }
551                    if (shape1.intersects(dataArea)) {
552                        g2.setPaint(getItemPaint(series + 1, item));
553                        g2.fill(shape1);
554                    }
555                    entityArea = shape1;
556    
557                    // add an entity for the item...
558                    if (entities != null) {
559                        if (entityArea == null) {
560                            entityArea = new Rectangle2D.Double(transX1 - 2, 
561                                    transY1 - 2, 4, 4);
562                        }
563                        String tip = null;
564                        XYToolTipGenerator generator = getToolTipGenerator(series, 
565                                item);
566                        if (generator != null) {
567                            tip = generator.generateToolTip(dataset, series + 1, 
568                                    item);
569                        }
570                        String url = null;
571                        if (getURLGenerator() != null) {
572                            url = getURLGenerator().generateURL(dataset, 
573                                    series + 1, item);
574                        }
575                        XYItemEntity entity = new XYItemEntity(entityArea, dataset, 
576                                series + 1, item, tip, url);
577                        entities.add(entity);
578                    }
579                }
580                updateCrosshairValues(crosshairState, x1, y1, transX1, transY1, 
581                        orientation);
582            }
583    
584        }
585    
586        /**
587         * Returns the positive area for a crossover point.
588         * 
589         * @param x0  x coordinate.
590         * @param y0A  y coordinate A.
591         * @param y0B  y coordinate B.
592         * @param x1  x coordinate.
593         * @param y1A  y coordinate A.
594         * @param y1B  y coordinate B.
595         * @param orientation  the plot orientation.
596         * 
597         * @return The positive area.
598         */
599        protected Shape getPositiveArea(float x0, float y0A, float y0B, 
600                                        float x1, float y1A, float y1B,
601                                        PlotOrientation orientation) {
602    
603            Shape result = null;
604    
605            boolean startsNegative = (y0A >= y0B);  
606            boolean endsNegative = (y1A >= y1B);
607            if (orientation == PlotOrientation.HORIZONTAL) {
608                startsNegative = (y0B >= y0A);
609                endsNegative = (y1B >= y1A);
610            }
611            
612            if (startsNegative) {  // starts negative
613                if (endsNegative) {
614                    // all negative - return null
615                    result = null;
616                }
617                else {
618                    // changed from negative to positive
619                    float[] p = getIntersection(x0, y0A, x1, y1A, x0, y0B, x1, y1B);
620                    GeneralPath area = new GeneralPath();
621                    if (orientation == PlotOrientation.HORIZONTAL) {
622                        area.moveTo(y1A, x1);
623                        area.lineTo(p[1], p[0]);
624                        area.lineTo(y1B, x1);
625                        area.closePath();
626                    }
627                    else if (orientation == PlotOrientation.VERTICAL) {
628                        area.moveTo(x1, y1A);
629                        area.lineTo(p[0], p[1]);
630                        area.lineTo(x1, y1B);
631                        area.closePath();
632                    }
633                    result = area;
634                }
635            }
636            else {  // starts positive
637                if (endsNegative) {
638                    // changed from positive to negative
639                    float[] p = getIntersection(x0, y0A, x1, y1A, x0, y0B, x1, y1B);
640                    GeneralPath area = new GeneralPath();
641                    if (orientation == PlotOrientation.HORIZONTAL) {
642                        area.moveTo(y0A, x0);
643                        area.lineTo(p[1], p[0]);
644                        area.lineTo(y0B, x0);
645                        area.closePath();
646                    }
647                    else if (orientation == PlotOrientation.VERTICAL) {
648                        area.moveTo(x0, y0A);
649                        area.lineTo(p[0], p[1]);
650                        area.lineTo(x0, y0B);
651                        area.closePath();
652                    }
653                    result = area;
654    
655                }
656                else {
657                    GeneralPath area = new GeneralPath();
658                    if (orientation == PlotOrientation.HORIZONTAL) {
659                        area.moveTo(y0A, x0);
660                        area.lineTo(y1A, x1);
661                        area.lineTo(y1B, x1);
662                        area.lineTo(y0B, x0);
663                        area.closePath();
664                    }
665                    else if (orientation == PlotOrientation.VERTICAL) {
666                        area.moveTo(x0, y0A);
667                        area.lineTo(x1, y1A);
668                        area.lineTo(x1, y1B);
669                        area.lineTo(x0, y0B);
670                        area.closePath();
671                    }
672                    result = area;
673                }
674    
675            }
676    
677            return result;
678    
679        }
680    
681        /**
682         * Returns the negative area for a cross-over section.
683         * 
684         * @param x0  x coordinate.
685         * @param y0A  y coordinate A.
686         * @param y0B  y coordinate B.
687         * @param x1  x coordinate.
688         * @param y1A  y coordinate A.
689         * @param y1B  y coordinate B.
690         * @param orientation  the plot orientation.
691         * 
692         * @return The negative area.
693         */
694        protected Shape getNegativeArea(float x0, float y0A, float y0B, 
695                                        float x1, float y1A, float y1B,
696                                        PlotOrientation orientation) {
697    
698            Shape result = null;
699    
700            boolean startsNegative = (y0A >= y0B);
701            boolean endsNegative = (y1A >= y1B);
702            if (orientation == PlotOrientation.HORIZONTAL) {
703                startsNegative = (y0B >= y0A);
704                endsNegative = (y1B >= y1A);
705            }
706            if (startsNegative) {  // starts negative
707                if (endsNegative) {  // all negative
708                    GeneralPath area = new GeneralPath();
709                    if (orientation == PlotOrientation.HORIZONTAL) {
710                        area.moveTo(y0A, x0);
711                        area.lineTo(y1A, x1);
712                        area.lineTo(y1B, x1);
713                        area.lineTo(y0B, x0);
714                        area.closePath();
715                    }
716                    else if (orientation == PlotOrientation.VERTICAL) {
717                        area.moveTo(x0, y0A);
718                        area.lineTo(x1, y1A);
719                        area.lineTo(x1, y1B);
720                        area.lineTo(x0, y0B);
721                        area.closePath();
722                    }
723                    result = area;
724                }
725                else {  // changed from negative to positive
726                    float[] p = getIntersection(x0, y0A, x1, y1A, x0, y0B, x1, y1B);
727                    GeneralPath area = new GeneralPath();
728                    if (orientation == PlotOrientation.HORIZONTAL) {
729                        area.moveTo(y0A, x0);
730                        area.lineTo(p[1], p[0]);
731                        area.lineTo(y0B, x0);
732                        area.closePath();
733                    }
734                    else if (orientation == PlotOrientation.VERTICAL) {
735                        area.moveTo(x0, y0A);
736                        area.lineTo(p[0], p[1]);
737                        area.lineTo(x0, y0B);
738                        area.closePath();
739                    }
740                    result = area;
741                }
742            }
743            else {
744                if (endsNegative) {
745                    // changed from positive to negative
746                    float[] p = getIntersection(x0, y0A, x1, y1A, x0, y0B, x1, y1B);
747                    GeneralPath area = new GeneralPath();
748                    if (orientation == PlotOrientation.HORIZONTAL) {
749                        area.moveTo(y1A, x1);
750                        area.lineTo(p[1], p[0]);
751                        area.lineTo(y1B, x1);
752                        area.closePath();
753                    }
754                    else if (orientation == PlotOrientation.VERTICAL) {
755                        area.moveTo(x1, y1A);
756                        area.lineTo(p[0], p[1]);
757                        area.lineTo(x1, y1B);
758                        area.closePath();
759                    }
760                    result = area;
761                }
762                else {
763                    // all negative - return null
764                }
765    
766            }
767    
768            return result;
769    
770        }
771    
772        /**
773         * Returns the intersection point of two lines.
774         * 
775         * @param x1  x1
776         * @param y1  y1
777         * @param x2  x2
778         * @param y2  y2
779         * @param x3  x3
780         * @param y3  y3
781         * @param x4  x4
782         * @param y4  y4
783         * 
784         * @return The intersection point.
785         */
786        private float[] getIntersection(float x1, float y1, float x2, float y2,
787                                        float x3, float y3, float x4, float y4) {
788    
789            float n = (x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3);
790            float d = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1);
791            float u = n / d;
792    
793            float[] result = new float[2];
794            result[0] = x1 + u * (x2 - x1);
795            result[1] = y1 + u * (y2 - y1);
796            return result;
797    
798        }
799        
800        /**
801         * Returns a default legend item for the specified series.  Subclasses 
802         * should override this method to generate customised items.
803         *
804         * @param datasetIndex  the dataset index (zero-based).
805         * @param series  the series index (zero-based).
806         *
807         * @return A legend item for the series.
808         */
809        public LegendItem getLegendItem(int datasetIndex, int series) {
810            LegendItem result = null;
811            XYPlot p = getPlot();
812            if (p != null) {
813                XYDataset dataset = p.getDataset(datasetIndex);
814                if (dataset != null) {
815                    if (getItemVisible(series, 0)) {
816                        String label = getLegendItemLabelGenerator().generateLabel(
817                                dataset, series);
818                        String description = label;
819                        String toolTipText = null;
820                        if (getLegendItemToolTipGenerator() != null) {
821                            toolTipText 
822                                = getLegendItemToolTipGenerator().generateLabel(
823                                        dataset, series);
824                        }
825                        String urlText = null;
826                        if (getLegendItemURLGenerator() != null) {
827                            urlText = getLegendItemURLGenerator().generateLabel(
828                                    dataset, series);
829                        }
830                        Paint paint = getSeriesPaint(series);
831                        Stroke stroke = getSeriesStroke(series);
832                        // TODO:  the following hard-coded line needs generalising
833                        Line2D line = new Line2D.Double(-7.0, 0.0, 7.0, 0.0);
834                        result = new LegendItem(label, description, 
835                                toolTipText, urlText, line, stroke, paint);
836                    }
837                }
838    
839            }
840    
841            return result;
842    
843        }
844    
845        /**
846         * Tests this renderer for equality with an arbitrary object.
847         * 
848         * @param obj  the object (<code>null</code> permitted).
849         * 
850         * @return A boolean.
851         */    
852        public boolean equals(Object obj) {
853            if (obj == this) {
854                return true;   
855            }
856            if (!(obj instanceof XYDifferenceRenderer)) {
857                return false;   
858            }
859            if (!super.equals(obj)) {
860                return false;   
861            }
862            XYDifferenceRenderer that = (XYDifferenceRenderer) obj;
863            if (!PaintUtilities.equal(this.positivePaint, that.positivePaint)) {
864                return false;   
865            }
866            if (!PaintUtilities.equal(this.negativePaint, that.negativePaint)) {
867                return false;   
868            }
869            if (this.shapesVisible != that.shapesVisible) {
870                return false;   
871            }
872            if (!ShapeUtilities.equal(this.legendLine, that.legendLine)) {
873                return false;   
874            }
875            return true;
876        }
877        
878        /**
879         * Returns a clone of the renderer.
880         * 
881         * @return A clone.
882         * 
883         * @throws CloneNotSupportedException  if the renderer cannot be cloned.
884         */
885        public Object clone() throws CloneNotSupportedException {
886            return super.clone();
887        }
888    
889        /**
890         * Provides serialization support.
891         *
892         * @param stream  the output stream.
893         *
894         * @throws IOException  if there is an I/O error.
895         */
896        private void writeObject(ObjectOutputStream stream) throws IOException {
897            stream.defaultWriteObject();
898            SerialUtilities.writePaint(this.positivePaint, stream);
899            SerialUtilities.writePaint(this.negativePaint, stream);
900            SerialUtilities.writeShape(this.legendLine, stream);
901        }
902    
903        /**
904         * Provides serialization support.
905         *
906         * @param stream  the input stream.
907         *
908         * @throws IOException  if there is an I/O error.
909         * @throws ClassNotFoundException  if there is a classpath problem.
910         */
911        private void readObject(ObjectInputStream stream) 
912            throws IOException, ClassNotFoundException {
913            stream.defaultReadObject();
914            this.positivePaint = SerialUtilities.readPaint(stream);
915            this.negativePaint = SerialUtilities.readPaint(stream);
916            this.legendLine = SerialUtilities.readShape(stream);
917        }
918    
919    }