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