001/* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-present, by David Gilbert and Contributors.
006 *
007 * Project Info:  http://www.jfree.org/jfreechart/index.html
008 *
009 * This library is free software; you can redistribute it and/or modify it
010 * under the terms of the GNU Lesser General Public License as published by
011 * the Free Software Foundation; either version 2.1 of the License, or
012 * (at your option) any later version.
013 *
014 * This library is distributed in the hope that it will be useful, but
015 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
016 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
017 * License for more details.
018 *
019 * You should have received a copy of the GNU Lesser General Public
020 * License along with this library; if not, write to the Free Software
021 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
022 * USA.
023 *
024 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates.
025 * Other names may be trademarks of their respective owners.]
026 *
027 * ---------------------------
028 * XYLineAndShapeRenderer.java
029 * ---------------------------
030 * (C) Copyright 2004-present, by David Gilbert.
031 *
032 * Original Author:  David Gilbert;
033 * Contributor(s):   -;
034 *
035 */
036
037package org.jfree.chart.renderer.xy;
038
039import java.awt.Graphics2D;
040import java.awt.Paint;
041import java.awt.Shape;
042import java.awt.Stroke;
043import java.awt.geom.GeneralPath;
044import java.awt.geom.Line2D;
045import java.awt.geom.Rectangle2D;
046import java.io.IOException;
047import java.io.ObjectInputStream;
048import java.io.ObjectOutputStream;
049import java.io.Serializable;
050import java.util.Objects;
051
052import org.jfree.chart.LegendItem;
053import org.jfree.chart.axis.ValueAxis;
054import org.jfree.chart.entity.EntityCollection;
055import org.jfree.chart.event.RendererChangeEvent;
056import org.jfree.chart.plot.CrosshairState;
057import org.jfree.chart.plot.PlotOrientation;
058import org.jfree.chart.plot.PlotRenderingInfo;
059import org.jfree.chart.plot.XYPlot;
060import org.jfree.chart.ui.RectangleEdge;
061import org.jfree.chart.util.BooleanList;
062import org.jfree.chart.util.LineUtils;
063import org.jfree.chart.util.Args;
064import org.jfree.chart.util.PublicCloneable;
065import org.jfree.chart.util.SerialUtils;
066import org.jfree.chart.util.ShapeUtils;
067import org.jfree.data.xy.XYDataset;
068
069/**
070 * A renderer that connects data points with lines and/or draws shapes at each
071 * data point.  This renderer is designed for use with the {@link XYPlot}
072 * class.  The example shown here is generated by
073 * the {@code XYLineAndShapeRendererDemo2.java} program included in the
074 * JFreeChart demo collection:
075 * <br><br>
076 * <img src="doc-files/XYLineAndShapeRendererSample.png"
077 * alt="XYLineAndShapeRendererSample.png">
078 *
079 */
080public class XYLineAndShapeRenderer extends AbstractXYItemRenderer
081        implements XYItemRenderer, Cloneable, PublicCloneable, Serializable {
082
083    /** For serialization. */
084    private static final long serialVersionUID = -7435246895986425885L;
085
086    /**
087     * A table of flags that control (per series) whether or not lines are
088     * visible.
089     */
090    private BooleanList seriesLinesVisible;
091
092    /** The default value returned by the getLinesVisible() method. */
093    private boolean defaultLinesVisible;
094
095    /** The shape that is used to represent a line in the legend. */
096    private transient Shape legendLine;
097
098    /**
099     * A table of flags that control (per series) whether or not shapes are
100     * visible.
101     */
102    private BooleanList seriesShapesVisible;
103
104    /** The default value returned by the getShapeVisible() method. */
105    private boolean defaultShapesVisible;
106
107    /**
108     * A table of flags that control (per series) whether or not shapes are
109     * filled.
110     */
111    private BooleanList seriesShapesFilled;
112
113    /** The default value returned by the getShapeFilled() method. */
114    private boolean defaultShapesFilled;
115
116    /** A flag that controls whether outlines are drawn for shapes. */
117    private boolean drawOutlines;
118
119    /**
120     * A flag that controls whether the fill paint is used for filling
121     * shapes.
122     */
123    private boolean useFillPaint;
124
125    /**
126     * A flag that controls whether the outline paint is used for drawing shape
127     * outlines.
128     */
129    private boolean useOutlinePaint;
130
131    /**
132     * A flag that controls whether or not each series is drawn as a single
133     * path.
134     */
135    private boolean drawSeriesLineAsPath;
136
137    /**
138     * Creates a new renderer with both lines and shapes visible.
139     */
140    public XYLineAndShapeRenderer() {
141        this(true, true);
142    }
143
144    /**
145     * Creates a new renderer.
146     *
147     * @param lines  lines visible?
148     * @param shapes  shapes visible?
149     */
150    public XYLineAndShapeRenderer(boolean lines, boolean shapes) {
151        this.seriesLinesVisible = new BooleanList();
152        this.defaultLinesVisible = lines;
153        this.legendLine = new Line2D.Double(-7.0, 0.0, 7.0, 0.0);
154
155        this.seriesShapesVisible = new BooleanList();
156        this.defaultShapesVisible = shapes;
157
158        this.useFillPaint = false;     // use item paint for fills by default
159        this.seriesShapesFilled = new BooleanList();
160        this.defaultShapesFilled = true;
161
162        this.drawOutlines = true;
163        this.useOutlinePaint = false;  // use item paint for outlines by
164                                       // default, not outline paint
165
166        this.drawSeriesLineAsPath = false;
167    }
168
169    /**
170     * Returns a flag that controls whether each series is drawn as a single path.  The default value is {@code false}.
171     *
172     * @return A boolean.
173     *
174     * @see #setDrawSeriesLineAsPath(boolean)
175     */
176    public boolean getDrawSeriesLineAsPath() {
177        return this.drawSeriesLineAsPath;
178    }
179
180    /**
181     * Sets the flag that controls whether each series is drawn as a
182     * single path and sends a {@link RendererChangeEvent} to all registered
183     * listeners.
184     *
185     * @param flag  the flag.
186     *
187     * @see #getDrawSeriesLineAsPath()
188     */
189    public void setDrawSeriesLineAsPath(boolean flag) {
190        if (this.drawSeriesLineAsPath != flag) {
191            this.drawSeriesLineAsPath = flag;
192            fireChangeEvent();
193        }
194    }
195
196    /**
197     * Returns the number of passes through the data that the renderer requires
198     * in order to draw the chart.  Most charts will require a single pass, but
199     * some require two passes.
200     *
201     * @return The pass count.
202     */
203    @Override
204    public int getPassCount() {
205        return 2;
206    }
207
208    // LINES VISIBLE
209
210    /**
211     * Returns the flag used to control whether or not the shape for an item is
212     * visible.
213     *
214     * @param series  the series index (zero-based).
215     * @param item  the item index (zero-based).
216     *
217     * @return A boolean.
218     */
219    public boolean getItemLineVisible(int series, int item) {
220        Boolean flag = getSeriesLinesVisible(series);
221        if (flag != null) {
222            return flag;
223        }
224        return this.defaultLinesVisible;
225    }
226
227    /**
228     * Returns the flag used to control whether or not the lines for a series
229     * are visible.
230     *
231     * @param series  the series index (zero-based).
232     *
233     * @return The flag (possibly {@code null}).
234     *
235     * @see #setSeriesLinesVisible(int, Boolean)
236     */
237    public Boolean getSeriesLinesVisible(int series) {
238        return this.seriesLinesVisible.getBoolean(series);
239    }
240
241    /**
242     * Sets the 'lines visible' flag for a series and sends a
243     * {@link RendererChangeEvent} to all registered listeners.
244     *
245     * @param series  the series index (zero-based).
246     * @param flag  the flag ({@code null} permitted).
247     *
248     * @see #getSeriesLinesVisible(int)
249     */
250    public void setSeriesLinesVisible(int series, Boolean flag) {
251        this.seriesLinesVisible.setBoolean(series, flag);
252        fireChangeEvent();
253    }
254
255    /**
256     * Sets the 'lines visible' flag for a series and sends a
257     * {@link RendererChangeEvent} to all registered listeners.
258     *
259     * @param series  the series index (zero-based).
260     * @param visible  the flag.
261     *
262     * @see #getSeriesLinesVisible(int)
263     */
264    public void setSeriesLinesVisible(int series, boolean visible) {
265        setSeriesLinesVisible(series, Boolean.valueOf(visible));
266    }
267
268    /**
269     * Returns the default 'lines visible' attribute.
270     *
271     * @return The default flag.
272     *
273     * @see #setDefaultLinesVisible(boolean)
274     */
275    public boolean getDefaultLinesVisible() {
276        return this.defaultLinesVisible;
277    }
278
279    /**
280     * Sets the default 'lines visible' flag and sends a
281     * {@link RendererChangeEvent} to all registered listeners.
282     *
283     * @param flag  the flag.
284     *
285     * @see #getDefaultLinesVisible()
286     */
287    public void setDefaultLinesVisible(boolean flag) {
288        this.defaultLinesVisible = flag;
289        fireChangeEvent();
290    }
291
292    /**
293     * Returns the shape used to represent a line in the legend.
294     *
295     * @return The legend line (never {@code null}).
296     *
297     * @see #setLegendLine(Shape)
298     */
299    public Shape getLegendLine() {
300        return this.legendLine;
301    }
302
303    /**
304     * Sets the shape used as a line in each legend item and sends a
305     * {@link RendererChangeEvent} to all registered listeners.
306     *
307     * @param line  the line ({@code null} not permitted).
308     *
309     * @see #getLegendLine()
310     */
311    public void setLegendLine(Shape line) {
312        Args.nullNotPermitted(line, "line");
313        this.legendLine = line;
314        fireChangeEvent();
315    }
316
317    // SHAPES VISIBLE
318
319    /**
320     * Returns the flag used to control whether or not the shape for an item is
321     * visible.
322     * <p>
323     * The default implementation passes control to the
324     * {@code getSeriesShapesVisible()} method. You can override this method
325     * if you require different behaviour.
326     *
327     * @param series  the series index (zero-based).
328     * @param item  the item index (zero-based).
329     *
330     * @return A boolean.
331     */
332    public boolean getItemShapeVisible(int series, int item) {
333        Boolean flag = getSeriesShapesVisible(series);
334        if (flag != null) {
335            return flag;
336        }
337        return this.defaultShapesVisible;
338    }
339
340    /**
341     * Returns the flag used to control whether or not the shapes for a series
342     * are visible.
343     *
344     * @param series  the series index (zero-based).
345     *
346     * @return A boolean.
347     *
348     * @see #setSeriesShapesVisible(int, Boolean)
349     */
350    public Boolean getSeriesShapesVisible(int series) {
351        return this.seriesShapesVisible.getBoolean(series);
352    }
353
354    /**
355     * Sets the 'shapes visible' flag for a series and sends a
356     * {@link RendererChangeEvent} to all registered listeners.
357     *
358     * @param series  the series index (zero-based).
359     * @param visible  the flag.
360     *
361     * @see #getSeriesShapesVisible(int)
362     */
363    public void setSeriesShapesVisible(int series, boolean visible) {
364        setSeriesShapesVisible(series, Boolean.valueOf(visible));
365    }
366
367    /**
368     * Sets the 'shapes visible' flag for a series and sends a
369     * {@link RendererChangeEvent} to all registered listeners.
370     *
371     * @param series  the series index (zero-based).
372     * @param flag  the flag.
373     *
374     * @see #getSeriesShapesVisible(int)
375     */
376    public void setSeriesShapesVisible(int series, Boolean flag) {
377        this.seriesShapesVisible.setBoolean(series, flag);
378        fireChangeEvent();
379    }
380
381    /**
382     * Returns the default 'shape visible' attribute.
383     *
384     * @return The default flag.
385     *
386     * @see #setDefaultShapesVisible(boolean)
387     */
388    public boolean getDefaultShapesVisible() {
389        return this.defaultShapesVisible;
390    }
391
392    /**
393     * Sets the default 'shapes visible' flag and sends a
394     * {@link RendererChangeEvent} to all registered listeners.
395     *
396     * @param flag  the flag.
397     *
398     * @see #getDefaultShapesVisible()
399     */
400    public void setDefaultShapesVisible(boolean flag) {
401        this.defaultShapesVisible = flag;
402        fireChangeEvent();
403    }
404
405    // SHAPES FILLED
406
407    /**
408     * Returns the flag used to control whether or not the shape for an item
409     * is filled.
410     * <p>
411     * The default implementation passes control to the
412     * {@code getSeriesShapesFilled} method. You can override this method
413     * if you require different behaviour.
414     *
415     * @param series  the series index (zero-based).
416     * @param item  the item index (zero-based).
417     *
418     * @return A boolean.
419     */
420    public boolean getItemShapeFilled(int series, int item) {
421        Boolean flag = getSeriesShapesFilled(series);
422        if (flag != null) {
423            return flag;
424        }
425        return this.defaultShapesFilled;
426       
427    }
428
429    /**
430     * Returns the flag used to control whether or not the shapes for a series
431     * are filled.
432     *
433     * @param series  the series index (zero-based).
434     *
435     * @return A boolean.
436     *
437     * @see #setSeriesShapesFilled(int, Boolean)
438     */
439    public Boolean getSeriesShapesFilled(int series) {
440        return this.seriesShapesFilled.getBoolean(series);
441    }
442
443    /**
444     * Sets the 'shapes filled' flag for a series and sends a
445     * {@link RendererChangeEvent} to all registered listeners.
446     *
447     * @param series  the series index (zero-based).
448     * @param flag  the flag.
449     *
450     * @see #getSeriesShapesFilled(int)
451     */
452    public void setSeriesShapesFilled(int series, boolean flag) {
453        setSeriesShapesFilled(series, Boolean.valueOf(flag));
454    }
455
456    /**
457     * Sets the 'shapes filled' flag for a series and sends a
458     * {@link RendererChangeEvent} to all registered listeners.
459     *
460     * @param series  the series index (zero-based).
461     * @param flag  the flag.
462     *
463     * @see #getSeriesShapesFilled(int)
464     */
465    public void setSeriesShapesFilled(int series, Boolean flag) {
466        this.seriesShapesFilled.setBoolean(series, flag);
467        fireChangeEvent();
468    }
469
470    /**
471     * Returns the default 'shape filled' attribute.
472     *
473     * @return The default flag.
474     *
475     * @see #setDefaultShapesFilled(boolean)
476     */
477    public boolean getDefaultShapesFilled() {
478        return this.defaultShapesFilled;
479    }
480
481    /**
482     * Sets the default 'shapes filled' flag and sends a
483     * {@link RendererChangeEvent} to all registered listeners.
484     *
485     * @param flag  the flag.
486     *
487     * @see #getDefaultShapesFilled()
488     */
489    public void setDefaultShapesFilled(boolean flag) {
490        this.defaultShapesFilled = flag;
491        fireChangeEvent();
492    }
493
494    /**
495     * Returns {@code true} if outlines should be drawn for shapes, and
496     * {@code false} otherwise.
497     *
498     * @return A boolean.
499     *
500     * @see #setDrawOutlines(boolean)
501     */
502    public boolean getDrawOutlines() {
503        return this.drawOutlines;
504    }
505
506    /**
507     * Sets the flag that controls whether outlines are drawn for
508     * shapes, and sends a {@link RendererChangeEvent} to all registered
509     * listeners.
510     * <P>
511     * In some cases, shapes look better if they do NOT have an outline, but
512     * this flag allows you to set your own preference.
513     *
514     * @param flag  the flag.
515     *
516     * @see #getDrawOutlines()
517     */
518    public void setDrawOutlines(boolean flag) {
519        this.drawOutlines = flag;
520        fireChangeEvent();
521    }
522
523    /**
524     * Returns {@code true} if the renderer should use the fill paint
525     * setting to fill shapes, and {@code false} if it should just
526     * use the regular paint.
527     * <p>
528     * Refer to {@code XYLineAndShapeRendererDemo2.java} to see the
529     * effect of this flag.
530     *
531     * @return A boolean.
532     *
533     * @see #setUseFillPaint(boolean)
534     * @see #getUseOutlinePaint()
535     */
536    public boolean getUseFillPaint() {
537        return this.useFillPaint;
538    }
539
540    /**
541     * Sets the flag that controls whether the fill paint is used to fill
542     * shapes, and sends a {@link RendererChangeEvent} to all
543     * registered listeners.
544     *
545     * @param flag  the flag.
546     *
547     * @see #getUseFillPaint()
548     */
549    public void setUseFillPaint(boolean flag) {
550        this.useFillPaint = flag;
551        fireChangeEvent();
552    }
553
554    /**
555     * Returns {@code true} if the renderer should use the outline paint
556     * setting to draw shape outlines, and {@code false} if it should just
557     * use the regular paint.
558     *
559     * @return A boolean.
560     *
561     * @see #setUseOutlinePaint(boolean)
562     * @see #getUseFillPaint()
563     */
564    public boolean getUseOutlinePaint() {
565        return this.useOutlinePaint;
566    }
567
568    /**
569     * Sets the flag that controls whether the outline paint is used to draw
570     * shape outlines, and sends a {@link RendererChangeEvent} to all
571     * registered listeners.
572     * <p>
573     * Refer to {@code XYLineAndShapeRendererDemo2.java} to see the
574     * effect of this flag.
575     *
576     * @param flag  the flag.
577     *
578     * @see #getUseOutlinePaint()
579     */
580    public void setUseOutlinePaint(boolean flag) {
581        this.useOutlinePaint = flag;
582        fireChangeEvent();
583    }
584
585    /**
586     * Records the state for the renderer.  This is used to preserve state
587     * information between calls to the drawItem() method for a single chart
588     * drawing.
589     */
590    public static class State extends XYItemRendererState {
591
592        /** The path for the current series. */
593        public GeneralPath seriesPath;
594
595        /**
596         * A flag that indicates if the last (x, y) point was 'good'
597         * (non-null).
598         */
599        private boolean lastPointGood;
600
601        /**
602         * Creates a new state instance.
603         *
604         * @param info  the plot rendering info.
605         */
606        public State(PlotRenderingInfo info) {
607            super(info);
608            this.seriesPath = new GeneralPath();
609        }
610
611        /**
612         * Returns a flag that indicates if the last point drawn (in the
613         * current series) was 'good' (non-null).
614         *
615         * @return A boolean.
616         */
617        public boolean isLastPointGood() {
618            return this.lastPointGood;
619        }
620
621        /**
622         * Sets a flag that indicates if the last point drawn (in the current
623         * series) was 'good' (non-null).
624         *
625         * @param good  the flag.
626         */
627        public void setLastPointGood(boolean good) {
628            this.lastPointGood = good;
629        }
630
631        /**
632         * This method is called by the {@link XYPlot} at the start of each
633         * series pass.  We reset the state for the current series.
634         *
635         * @param dataset  the dataset.
636         * @param series  the series index.
637         * @param firstItem  the first item index for this pass.
638         * @param lastItem  the last item index for this pass.
639         * @param pass  the current pass index.
640         * @param passCount  the number of passes.
641         */
642        @Override
643        public void startSeriesPass(XYDataset dataset, int series,
644                int firstItem, int lastItem, int pass, int passCount) {
645            this.seriesPath.reset();
646            this.lastPointGood = false;
647            super.startSeriesPass(dataset, series, firstItem, lastItem, pass,
648                    passCount);
649       }
650
651    }
652
653    /**
654     * Initialises the renderer.
655     * <P>
656     * This method will be called before the first item is rendered, giving the
657     * renderer an opportunity to initialise any state information it wants to
658     * maintain.  The renderer can do nothing if it chooses.
659     *
660     * @param g2  the graphics device.
661     * @param dataArea  the area inside the axes.
662     * @param plot  the plot.
663     * @param data  the data.
664     * @param info  an optional info collection object to return data back to
665     *              the caller.
666     *
667     * @return The renderer state.
668     */
669    @Override
670    public XYItemRendererState initialise(Graphics2D g2, Rectangle2D dataArea,
671            XYPlot plot, XYDataset data, PlotRenderingInfo info) {
672        return new State(info);
673    }
674
675    /**
676     * Draws the visual representation of a single data item.
677     *
678     * @param g2  the graphics device.
679     * @param state  the renderer state.
680     * @param dataArea  the area within which the data is being drawn.
681     * @param info  collects information about the drawing.
682     * @param plot  the plot (can be used to obtain standard color
683     *              information etc).
684     * @param domainAxis  the domain axis.
685     * @param rangeAxis  the range axis.
686     * @param dataset  the dataset.
687     * @param series  the series index (zero-based).
688     * @param item  the item index (zero-based).
689     * @param crosshairState  crosshair information for the plot
690     *                        ({@code null} permitted).
691     * @param pass  the pass index.
692     */
693    @Override
694    public void drawItem(Graphics2D g2, XYItemRendererState state,
695            Rectangle2D dataArea, PlotRenderingInfo info, XYPlot plot,
696            ValueAxis domainAxis, ValueAxis rangeAxis, XYDataset dataset,
697            int series, int item, CrosshairState crosshairState, int pass) {
698
699        // do nothing if item is not visible
700        if (!getItemVisible(series, item)) {
701            return;
702        }
703
704        // first pass draws the background (lines, for instance)
705        if (isLinePass(pass)) {
706            if (getItemLineVisible(series, item)) {
707                if (this.drawSeriesLineAsPath) {
708                    drawPrimaryLineAsPath(state, g2, plot, dataset, pass,
709                            series, item, domainAxis, rangeAxis, dataArea);
710                }
711                else {
712                    drawPrimaryLine(state, g2, plot, dataset, pass, series,
713                            item, domainAxis, rangeAxis, dataArea);
714                }
715            }
716        }
717        // second pass adds shapes where the items are ..
718        else if (isItemPass(pass)) {
719
720            // setup for collecting optional entity info...
721            EntityCollection entities = null;
722            if (info != null && info.getOwner() != null) {
723                entities = info.getOwner().getEntityCollection();
724            }
725
726            drawSecondaryPass(g2, plot, dataset, pass, series, item,
727                    domainAxis, dataArea, rangeAxis, crosshairState, entities);
728        }
729    }
730
731    /**
732     * Returns {@code true} if the specified pass is the one for drawing
733     * lines.
734     *
735     * @param pass  the pass.
736     *
737     * @return A boolean.
738     */
739    protected boolean isLinePass(int pass) {
740        return pass == 0;
741    }
742
743    /**
744     * Returns {@code true} if the specified pass is the one for drawing
745     * items.
746     *
747     * @param pass  the pass.
748     *
749     * @return A boolean.
750     */
751    protected boolean isItemPass(int pass) {
752        return pass == 1;
753    }
754
755    /**
756     * Draws the item (first pass). This method draws the lines
757     * connecting the items.
758     *
759     * @param g2  the graphics device.
760     * @param state  the renderer state.
761     * @param dataArea  the area within which the data is being drawn.
762     * @param plot  the plot (can be used to obtain standard color
763     *              information etc).
764     * @param domainAxis  the domain axis.
765     * @param rangeAxis  the range axis.
766     * @param dataset  the dataset.
767     * @param pass  the pass.
768     * @param series  the series index (zero-based).
769     * @param item  the item index (zero-based).
770     */
771    protected void drawPrimaryLine(XYItemRendererState state,
772                                   Graphics2D g2,
773                                   XYPlot plot,
774                                   XYDataset dataset,
775                                   int pass,
776                                   int series,
777                                   int item,
778                                   ValueAxis domainAxis,
779                                   ValueAxis rangeAxis,
780                                   Rectangle2D dataArea) {
781        if (item == 0) {
782            return;
783        }
784
785        // get the data point...
786        double x1 = dataset.getXValue(series, item);
787        double y1 = dataset.getYValue(series, item);
788        if (Double.isNaN(y1) || Double.isNaN(x1)) {
789            return;
790        }
791
792        double x0 = dataset.getXValue(series, item - 1);
793        double y0 = dataset.getYValue(series, item - 1);
794        if (Double.isNaN(y0) || Double.isNaN(x0)) {
795            return;
796        }
797
798        RectangleEdge xAxisLocation = plot.getDomainAxisEdge();
799        RectangleEdge yAxisLocation = plot.getRangeAxisEdge();
800
801        double transX0 = domainAxis.valueToJava2D(x0, dataArea, xAxisLocation);
802        double transY0 = rangeAxis.valueToJava2D(y0, dataArea, yAxisLocation);
803
804        double transX1 = domainAxis.valueToJava2D(x1, dataArea, xAxisLocation);
805        double transY1 = rangeAxis.valueToJava2D(y1, dataArea, yAxisLocation);
806
807        // only draw if we have good values
808        if (Double.isNaN(transX0) || Double.isNaN(transY0)
809            || Double.isNaN(transX1) || Double.isNaN(transY1)) {
810            return;
811        }
812
813        PlotOrientation orientation = plot.getOrientation();
814        boolean visible;
815        if (orientation == PlotOrientation.HORIZONTAL) {
816            state.workingLine.setLine(transY0, transX0, transY1, transX1);
817        }
818        else if (orientation == PlotOrientation.VERTICAL) {
819            state.workingLine.setLine(transX0, transY0, transX1, transY1);
820        }
821        visible = LineUtils.clipLine(state.workingLine, dataArea);
822        if (visible) {
823            drawFirstPassShape(g2, pass, series, item, state.workingLine);
824        }
825    }
826
827    /**
828     * Draws the first pass shape.
829     *
830     * @param g2  the graphics device.
831     * @param pass  the pass.
832     * @param series  the series index.
833     * @param item  the item index.
834     * @param shape  the shape.
835     */
836    protected void drawFirstPassShape(Graphics2D g2, int pass, int series,
837                                      int item, Shape shape) {
838        g2.setStroke(getItemStroke(series, item));
839        g2.setPaint(getItemPaint(series, item));
840        g2.draw(shape);
841    }
842
843
844    /**
845     * Draws the item (first pass). This method draws the lines
846     * connecting the items. Instead of drawing separate lines,
847     * a {@code GeneralPath} is constructed and drawn at the end of
848     * the series painting.
849     *
850     * @param g2  the graphics device.
851     * @param state  the renderer state.
852     * @param plot  the plot (can be used to obtain standard color information
853     *              etc).
854     * @param dataset  the dataset.
855     * @param pass  the pass.
856     * @param series  the series index (zero-based).
857     * @param item  the item index (zero-based).
858     * @param domainAxis  the domain axis.
859     * @param rangeAxis  the range axis.
860     * @param dataArea  the area within which the data is being drawn.
861     */
862    protected void drawPrimaryLineAsPath(XYItemRendererState state,
863            Graphics2D g2, XYPlot plot, XYDataset dataset, int pass,
864            int series, int item, ValueAxis domainAxis, ValueAxis rangeAxis,
865            Rectangle2D dataArea) {
866
867        RectangleEdge xAxisLocation = plot.getDomainAxisEdge();
868        RectangleEdge yAxisLocation = plot.getRangeAxisEdge();
869
870        // get the data point...
871        double x1 = dataset.getXValue(series, item);
872        double y1 = dataset.getYValue(series, item);
873        double transX1 = domainAxis.valueToJava2D(x1, dataArea, xAxisLocation);
874        double transY1 = rangeAxis.valueToJava2D(y1, dataArea, yAxisLocation);
875
876        State s = (State) state;
877        // update path to reflect latest point
878        if (!Double.isNaN(transX1) && !Double.isNaN(transY1)) {
879            float x = (float) transX1;
880            float y = (float) transY1;
881            PlotOrientation orientation = plot.getOrientation();
882            if (orientation == PlotOrientation.HORIZONTAL) {
883                x = (float) transY1;
884                y = (float) transX1;
885            }
886            if (s.isLastPointGood()) {
887                s.seriesPath.lineTo(x, y);
888            }
889            else {
890                s.seriesPath.moveTo(x, y);
891            }
892            s.setLastPointGood(true);
893        } else {
894            s.setLastPointGood(false);
895        }
896        // if this is the last item, draw the path ...
897        if (item == s.getLastItemIndex()) {
898            // draw path
899            drawFirstPassShape(g2, pass, series, item, s.seriesPath);
900        }
901    }
902
903    /**
904     * Draws the item shapes and adds chart entities (second pass). This method
905     * draws the shapes which mark the item positions. If {@code entities}
906     * is not {@code null} it will be populated with entity information
907     * for points that fall within the data area.
908     *
909     * @param g2  the graphics device.
910     * @param plot  the plot (can be used to obtain standard color
911     *              information etc).
912     * @param domainAxis  the domain axis.
913     * @param dataArea  the area within which the data is being drawn.
914     * @param rangeAxis  the range axis.
915     * @param dataset  the dataset.
916     * @param pass  the pass.
917     * @param series  the series index (zero-based).
918     * @param item  the item index (zero-based).
919     * @param crosshairState  the crosshair state.
920     * @param entities the entity collection.
921     */
922    protected void drawSecondaryPass(Graphics2D g2, XYPlot plot, 
923            XYDataset dataset, int pass, int series, int item,
924            ValueAxis domainAxis, Rectangle2D dataArea, ValueAxis rangeAxis,
925            CrosshairState crosshairState, EntityCollection entities) {
926
927        Shape entityArea = null;
928
929        // get the data point...
930        double x1 = dataset.getXValue(series, item);
931        double y1 = dataset.getYValue(series, item);
932        if (Double.isNaN(y1) || Double.isNaN(x1)) {
933            return;
934        }
935
936        PlotOrientation orientation = plot.getOrientation();
937        RectangleEdge xAxisLocation = plot.getDomainAxisEdge();
938        RectangleEdge yAxisLocation = plot.getRangeAxisEdge();
939        double transX1 = domainAxis.valueToJava2D(x1, dataArea, xAxisLocation);
940        double transY1 = rangeAxis.valueToJava2D(y1, dataArea, yAxisLocation);
941
942        if (getItemShapeVisible(series, item)) {
943            Shape shape = getItemShape(series, item);
944            if (orientation == PlotOrientation.HORIZONTAL) {
945                shape = ShapeUtils.createTranslatedShape(shape, transY1, transX1);
946            }
947            else if (orientation == PlotOrientation.VERTICAL) {
948                shape = ShapeUtils.createTranslatedShape(shape, transX1, transY1);
949            }
950            entityArea = shape;
951            if (shape.intersects(dataArea)) {
952                if (getItemShapeFilled(series, item)) {
953                    if (this.useFillPaint) {
954                        g2.setPaint(getItemFillPaint(series, item));
955                    }
956                    else {
957                        g2.setPaint(getItemPaint(series, item));
958                    }
959                    g2.fill(shape);
960                }
961                if (this.drawOutlines) {
962                    if (getUseOutlinePaint()) {
963                        g2.setPaint(getItemOutlinePaint(series, item));
964                    }
965                    else {
966                        g2.setPaint(getItemPaint(series, item));
967                    }
968                    g2.setStroke(getItemOutlineStroke(series, item));
969                    g2.draw(shape);
970                }
971            }
972        }
973
974        double xx = transX1;
975        double yy = transY1;
976        if (orientation == PlotOrientation.HORIZONTAL) {
977            xx = transY1;
978            yy = transX1;
979        }
980
981        // draw the item label if there is one...
982        if (isItemLabelVisible(series, item)) {
983            drawItemLabel(g2, orientation, dataset, series, item, xx, yy,
984                    (y1 < 0.0));
985        }
986
987        int datasetIndex = plot.indexOf(dataset);
988        updateCrosshairValues(crosshairState, x1, y1, datasetIndex,
989                transX1, transY1, orientation);
990
991        // add an entity for the item, but only if it falls within the data
992        // area...
993        if (entities != null && ShapeUtils.isPointInRect(dataArea, xx, yy)) {
994            addEntity(entities, entityArea, dataset, series, item, xx, yy);
995        }
996    }
997
998
999    /**
1000     * Returns a legend item for the specified series.
1001     *
1002     * @param datasetIndex  the dataset index (zero-based).
1003     * @param series  the series index (zero-based).
1004     *
1005     * @return A legend item for the series (possibly {@code null}).
1006     */
1007    @Override
1008    public LegendItem getLegendItem(int datasetIndex, int series) {
1009        XYPlot plot = getPlot();
1010        if (plot == null) {
1011            return null;
1012        }
1013
1014        XYDataset dataset = plot.getDataset(datasetIndex);
1015        if (dataset == null) {
1016            return null;
1017        }
1018
1019        if (!getItemVisible(series, 0)) {
1020            return null;
1021        }
1022        String label = getLegendItemLabelGenerator().generateLabel(dataset,
1023                series);
1024        String description = label;
1025        String toolTipText = null;
1026        if (getLegendItemToolTipGenerator() != null) {
1027            toolTipText = getLegendItemToolTipGenerator().generateLabel(
1028                    dataset, series);
1029        }
1030        String urlText = null;
1031        if (getLegendItemURLGenerator() != null) {
1032            urlText = getLegendItemURLGenerator().generateLabel(dataset,
1033                    series);
1034        }
1035        boolean shapeIsVisible = getItemShapeVisible(series, 0);
1036        Shape shape = lookupLegendShape(series);
1037        boolean shapeIsFilled = getItemShapeFilled(series, 0);
1038        Paint fillPaint = (this.useFillPaint ? lookupSeriesFillPaint(series)
1039                : lookupSeriesPaint(series));
1040        boolean shapeOutlineVisible = this.drawOutlines;
1041        Paint outlinePaint = (this.useOutlinePaint ? lookupSeriesOutlinePaint(
1042                series) : lookupSeriesPaint(series));
1043        Stroke outlineStroke = lookupSeriesOutlineStroke(series);
1044        boolean lineVisible = getItemLineVisible(series, 0);
1045        Stroke lineStroke = lookupSeriesStroke(series);
1046        Paint linePaint = lookupSeriesPaint(series);
1047        LegendItem result = new LegendItem(label, description, toolTipText,
1048                urlText, shapeIsVisible, shape, shapeIsFilled, fillPaint,
1049                shapeOutlineVisible, outlinePaint, outlineStroke, lineVisible,
1050                this.legendLine, lineStroke, linePaint);
1051        result.setLabelFont(lookupLegendTextFont(series));
1052        Paint labelPaint = lookupLegendTextPaint(series);
1053        if (labelPaint != null) {
1054            result.setLabelPaint(labelPaint);
1055        }
1056        result.setSeriesKey(dataset.getSeriesKey(series));
1057        result.setSeriesIndex(series);
1058        result.setDataset(dataset);
1059        result.setDatasetIndex(datasetIndex);
1060
1061        return result;
1062    }
1063
1064    /**
1065     * Returns a clone of the renderer.
1066     *
1067     * @return A clone.
1068     *
1069     * @throws CloneNotSupportedException if the clone cannot be created.
1070     */
1071    @Override
1072    public Object clone() throws CloneNotSupportedException {
1073        XYLineAndShapeRenderer clone = (XYLineAndShapeRenderer) super.clone();
1074        clone.seriesLinesVisible
1075                = (BooleanList) this.seriesLinesVisible.clone();
1076        if (this.legendLine != null) {
1077            clone.legendLine = ShapeUtils.clone(this.legendLine);
1078        }
1079        clone.seriesShapesVisible
1080                = (BooleanList) this.seriesShapesVisible.clone();
1081        clone.seriesShapesFilled
1082                = (BooleanList) this.seriesShapesFilled.clone();
1083        return clone;
1084    }
1085
1086    /**
1087     * Tests this renderer for equality with an arbitrary object.
1088     *
1089     * @param obj  the object ({@code null} permitted).
1090     *
1091     * @return {@code true} or {@code false}.
1092     */
1093    @Override
1094    public boolean equals(Object obj) {
1095        if (obj == this) {
1096            return true;
1097        }
1098        if (!(obj instanceof XYLineAndShapeRenderer)) {
1099            return false;
1100        }
1101        if (!super.equals(obj)) {
1102            return false;
1103        }
1104        XYLineAndShapeRenderer that = (XYLineAndShapeRenderer) obj;
1105        if (!Objects.equals(
1106            this.seriesLinesVisible, that.seriesLinesVisible)
1107        ) {
1108            return false;
1109        }
1110        if (this.defaultLinesVisible != that.defaultLinesVisible) {
1111            return false;
1112        }
1113        if (!ShapeUtils.equal(this.legendLine, that.legendLine)) {
1114            return false;
1115        }
1116        if (!Objects.equals(
1117            this.seriesShapesVisible, that.seriesShapesVisible)
1118        ) {
1119            return false;
1120        }
1121        if (this.defaultShapesVisible != that.defaultShapesVisible) {
1122            return false;
1123        }
1124        if (!Objects.equals(
1125            this.seriesShapesFilled, that.seriesShapesFilled)
1126        ) {
1127            return false;
1128        }
1129        if (this.defaultShapesFilled != that.defaultShapesFilled) {
1130            return false;
1131        }
1132        if (this.drawOutlines != that.drawOutlines) {
1133            return false;
1134        }
1135        if (this.useOutlinePaint != that.useOutlinePaint) {
1136            return false;
1137        }
1138        if (this.useFillPaint != that.useFillPaint) {
1139            return false;
1140        }
1141        if (this.drawSeriesLineAsPath != that.drawSeriesLineAsPath) {
1142            return false;
1143        }
1144        return true;
1145    }
1146
1147    /**
1148     * Returns a hash code for this instance.
1149     *
1150     * @return A hash code for this instance.
1151     */
1152    @Override
1153    public int hashCode() {
1154        int result = super.hashCode();
1155        result = 31 * result + seriesLinesVisible.hashCode();
1156        result = 31 * result + (defaultLinesVisible ? 1 : 0);
1157        result = 31 * result + seriesShapesVisible.hashCode();
1158        result = 31 * result + (defaultShapesVisible ? 1 : 0);
1159        result = 31 * result + seriesShapesFilled.hashCode();
1160        result = 31 * result + (defaultShapesFilled ? 1 : 0);
1161        result = 31 * result + (drawOutlines ? 1 : 0);
1162        result = 31 * result + (useFillPaint ? 1 : 0);
1163        result = 31 * result + (useOutlinePaint ? 1 : 0);
1164        result = 31 * result + (drawSeriesLineAsPath ? 1 : 0);
1165        return result;
1166    }
1167
1168    /**
1169     * Provides serialization support.
1170     *
1171     * @param stream  the input stream.
1172     *
1173     * @throws IOException  if there is an I/O error.
1174     * @throws ClassNotFoundException  if there is a classpath problem.
1175     */
1176    private void readObject(ObjectInputStream stream)
1177            throws IOException, ClassNotFoundException {
1178        stream.defaultReadObject();
1179        this.legendLine = SerialUtils.readShape(stream);
1180    }
1181
1182    /**
1183     * Provides serialization support.
1184     *
1185     * @param stream  the output stream.
1186     *
1187     * @throws IOException  if there is an I/O error.
1188     */
1189    private void writeObject(ObjectOutputStream stream) throws IOException {
1190        stream.defaultWriteObject();
1191        SerialUtils.writeShape(this.legendLine, stream);
1192    }
1193
1194}