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 * LineAndShapeRenderer.java
029 * -------------------------
030 * (C) Copyright 2001-2021, by Object Refinery Limited and Contributors.
031 *
032 * Original Author:  David Gilbert (for Object Refinery Limited);
033 * Contributor(s):   Mark Watson (www.markwatson.com);
034 *                   Jeremy Bowman;
035 *                   Richard Atkinson;
036 *                   Christian W. Zuckschwerdt;
037 *                   Peter Kolb (patch 2497611);
038 *
039 */
040
041package org.jfree.chart.renderer.category;
042
043import java.awt.Graphics2D;
044import java.awt.Paint;
045import java.awt.Shape;
046import java.awt.Stroke;
047import java.awt.geom.Line2D;
048import java.awt.geom.Rectangle2D;
049import java.io.Serializable;
050import java.util.Objects;
051
052import org.jfree.chart.LegendItem;
053import org.jfree.chart.axis.CategoryAxis;
054import org.jfree.chart.axis.ValueAxis;
055import org.jfree.chart.entity.EntityCollection;
056import org.jfree.chart.event.RendererChangeEvent;
057import org.jfree.chart.plot.CategoryPlot;
058import org.jfree.chart.plot.PlotOrientation;
059import org.jfree.chart.util.BooleanList;
060import org.jfree.chart.util.PublicCloneable;
061import org.jfree.chart.util.ShapeUtils;
062import org.jfree.data.category.CategoryDataset;
063
064/**
065 * A renderer that draws shapes for each data item, and lines between data
066 * items (for use with the {@link CategoryPlot} class).
067 * The example shown here is generated by the {@code LineChartDemo1.java}
068 * program included in the JFreeChart Demo Collection:
069 * <br><br>
070 * <img src="doc-files/LineAndShapeRendererSample.png"
071 * alt="LineAndShapeRendererSample.png">
072 */
073public class LineAndShapeRenderer extends AbstractCategoryItemRenderer
074        implements Cloneable, PublicCloneable, Serializable {
075
076    /** For serialization. */
077    private static final long serialVersionUID = -197749519869226398L;
078
079    /**
080     * A table of flags that control (per series) whether or not lines are
081     * visible.
082     */
083    private BooleanList seriesLinesVisible;
084
085    /**
086     * A flag indicating whether or not lines are drawn between non-null
087     * points.
088     */
089    private boolean defaultLinesVisible;
090
091    /**
092     * A table of flags that control (per series) whether or not shapes are
093     * visible.
094     */
095    private BooleanList seriesShapesVisible;
096
097    /** The default value returned by the getShapeVisible() method. */
098    private boolean defaultShapesVisible;
099
100    /**
101     * A table of flags that control (per series) whether or not shapes are
102     * filled.
103     */
104    private BooleanList seriesShapesFilled;
105
106    /** The default value returned by the getShapeFilled() method. */
107    private boolean defaultShapesFilled;
108
109    /**
110     * A flag that controls whether the fill paint is used for filling
111     * shapes.
112     */
113    private boolean useFillPaint;
114
115    /** A flag that controls whether outlines are drawn for shapes. */
116    private boolean drawOutlines;
117
118    /**
119     * A flag that controls whether the outline paint is used for drawing shape
120     * outlines - if not, the regular series paint is used.
121     */
122    private boolean useOutlinePaint;
123
124    /**
125     * A flag that controls whether or not the x-position for each item is
126     * offset within the category according to the series.
127     */
128    private boolean useSeriesOffset;
129
130    /**
131     * The item margin used for series offsetting - this allows the positioning
132     * to match the bar positions of the {@link BarRenderer} class.
133     */
134    private double itemMargin;
135
136    /**
137     * Creates a renderer with both lines and shapes visible by default.
138     */
139    public LineAndShapeRenderer() {
140        this(true, true);
141    }
142
143    /**
144     * Creates a new renderer with lines and/or shapes visible.
145     *
146     * @param lines  draw lines?
147     * @param shapes  draw shapes?
148     */
149    public LineAndShapeRenderer(boolean lines, boolean shapes) {
150        super();
151        this.seriesLinesVisible = new BooleanList();
152        this.defaultLinesVisible = lines;
153        this.seriesShapesVisible = new BooleanList();
154        this.defaultShapesVisible = shapes;
155        this.seriesShapesFilled = new BooleanList();
156        this.defaultShapesFilled = true;
157        this.useFillPaint = false;
158        this.drawOutlines = true;
159        this.useOutlinePaint = false;
160        this.useSeriesOffset = false;  // preserves old behaviour
161        this.itemMargin = 0.0;
162    }
163
164    // LINES VISIBLE
165
166    /**
167     * Returns the flag used to control whether or not the line for an item is
168     * visible.
169     *
170     * @param series  the series index (zero-based).
171     * @param item  the item index (zero-based).
172     *
173     * @return A boolean.
174     */
175    public boolean getItemLineVisible(int series, int item) {
176        Boolean flag = getSeriesLinesVisible(series);
177        if (flag != null) {
178            return flag;
179        }
180        return this.defaultLinesVisible;
181    }
182
183    /**
184     * Returns the flag used to control whether or not the lines for a series
185     * are visible.
186     *
187     * @param series  the series index (zero-based).
188     *
189     * @return The flag (possibly {@code null}).
190     *
191     * @see #setSeriesLinesVisible(int, Boolean)
192     */
193    public Boolean getSeriesLinesVisible(int series) {
194        return this.seriesLinesVisible.getBoolean(series);
195    }
196
197    /**
198     * Sets the 'lines visible' flag for a series and sends a
199     * {@link RendererChangeEvent} to all registered listeners.
200     *
201     * @param series  the series index (zero-based).
202     * @param flag  the flag ({@code null} permitted).
203     *
204     * @see #getSeriesLinesVisible(int)
205     */
206    public void setSeriesLinesVisible(int series, Boolean flag) {
207        this.seriesLinesVisible.setBoolean(series, flag);
208        fireChangeEvent();
209    }
210
211    /**
212     * Sets the 'lines visible' flag for a series and sends a
213     * {@link RendererChangeEvent} to all registered listeners.
214     *
215     * @param series  the series index (zero-based).
216     * @param visible  the flag.
217     *
218     * @see #getSeriesLinesVisible(int)
219     */
220    public void setSeriesLinesVisible(int series, boolean visible) {
221        setSeriesLinesVisible(series, Boolean.valueOf(visible));
222    }
223
224    /**
225     * Returns the default 'lines visible' attribute.
226     *
227     * @return The default flag.
228     *
229     * @see #getDefaultLinesVisible()
230     */
231    public boolean getDefaultLinesVisible() {
232        return this.defaultLinesVisible;
233    }
234
235    /**
236     * Sets the default 'lines visible' flag and sends a
237     * {@link RendererChangeEvent} to all registered listeners.
238     *
239     * @param flag  the flag.
240     *
241     * @see #getDefaultLinesVisible()
242     */
243    public void setDefaultLinesVisible(boolean flag) {
244        this.defaultLinesVisible = flag;
245        fireChangeEvent();
246    }
247
248    // SHAPES VISIBLE
249
250    /**
251     * Returns the flag used to control whether or not the shape for an item is
252     * visible.
253     *
254     * @param series  the series index (zero-based).
255     * @param item  the item index (zero-based).
256     *
257     * @return A boolean.
258     */
259    public boolean getItemShapeVisible(int series, int item) {
260        Boolean flag = getSeriesShapesVisible(series);
261        if (flag != null) {
262            return flag;
263        }
264        return this.defaultShapesVisible;
265    }
266
267    /**
268     * Returns the flag used to control whether or not the shapes for a series
269     * are visible.
270     *
271     * @param series  the series index (zero-based).
272     *
273     * @return A boolean.
274     *
275     * @see #setSeriesShapesVisible(int, Boolean)
276     */
277    public Boolean getSeriesShapesVisible(int series) {
278        return this.seriesShapesVisible.getBoolean(series);
279    }
280
281    /**
282     * Sets the 'shapes visible' flag for a series and sends a
283     * {@link RendererChangeEvent} to all registered listeners.
284     *
285     * @param series  the series index (zero-based).
286     * @param visible  the flag.
287     *
288     * @see #getSeriesShapesVisible(int)
289     */
290    public void setSeriesShapesVisible(int series, boolean visible) {
291        setSeriesShapesVisible(series, Boolean.valueOf(visible));
292    }
293
294    /**
295     * Sets the 'shapes visible' flag for a series and sends a
296     * {@link RendererChangeEvent} to all registered listeners.
297     *
298     * @param series  the series index (zero-based).
299     * @param flag  the flag.
300     *
301     * @see #getSeriesShapesVisible(int)
302     */
303    public void setSeriesShapesVisible(int series, Boolean flag) {
304        this.seriesShapesVisible.setBoolean(series, flag);
305        fireChangeEvent();
306    }
307
308    /**
309     * Returns the default 'shape visible' attribute.
310     *
311     * @return The base flag.
312     *
313     * @see #setDefaultShapesVisible(boolean)
314     */
315    public boolean getDefaultShapesVisible() {
316        return this.defaultShapesVisible;
317    }
318
319    /**
320     * Sets the default 'shapes visible' flag and sends a
321     * {@link RendererChangeEvent} to all registered listeners.
322     *
323     * @param flag  the flag.
324     *
325     * @see #getDefaultShapesVisible()
326     */
327    public void setDefaultShapesVisible(boolean flag) {
328        this.defaultShapesVisible = flag;
329        fireChangeEvent();
330    }
331
332    /**
333     * Returns {@code true} if outlines should be drawn for shapes, and
334     * {@code false} otherwise.
335     *
336     * @return A boolean.
337     *
338     * @see #setDrawOutlines(boolean)
339     */
340    public boolean getDrawOutlines() {
341        return this.drawOutlines;
342    }
343
344    /**
345     * Sets the flag that controls whether outlines are drawn for
346     * shapes, and sends a {@link RendererChangeEvent} to all registered
347     * listeners.
348     * <P>
349     * In some cases, shapes look better if they do NOT have an outline, but
350     * this flag allows you to set your own preference.
351     *
352     * @param flag  the flag.
353     *
354     * @see #getDrawOutlines()
355     */
356    public void setDrawOutlines(boolean flag) {
357        this.drawOutlines = flag;
358        fireChangeEvent();
359    }
360
361    /**
362     * Returns the flag that controls whether the outline paint is used for
363     * shape outlines.  If not, the regular series paint is used.
364     *
365     * @return A boolean.
366     *
367     * @see #setUseOutlinePaint(boolean)
368     */
369    public boolean getUseOutlinePaint() {
370        return this.useOutlinePaint;
371    }
372
373    /**
374     * Sets the flag that controls whether the outline paint is used for shape
375     * outlines, and sends a {@link RendererChangeEvent} to all registered
376     * listeners.
377     *
378     * @param use  the flag.
379     *
380     * @see #getUseOutlinePaint()
381     */
382    public void setUseOutlinePaint(boolean use) {
383        this.useOutlinePaint = use;
384        fireChangeEvent();
385    }
386
387    // SHAPES FILLED
388
389    /**
390     * Returns the flag used to control whether or not the shape for an item
391     * is filled. The default implementation passes control to the
392     * {@code getSeriesShapesFilled} method. You can override this method
393     * if you require different behaviour.
394     *
395     * @param series  the series index (zero-based).
396     * @param item  the item index (zero-based).
397     *
398     * @return A boolean.
399     */
400    public boolean getItemShapeFilled(int series, int item) {
401        return getSeriesShapesFilled(series);
402    }
403
404    /**
405     * Returns the flag used to control whether or not the shapes for a series
406     * are filled.
407     *
408     * @param series  the series index (zero-based).
409     *
410     * @return A boolean.
411     */
412    public boolean getSeriesShapesFilled(int series) {
413        Boolean flag = this.seriesShapesFilled.getBoolean(series);
414        if (flag != null) {
415            return flag;
416        }
417        return this.defaultShapesFilled;
418    }
419
420    /**
421     * Sets the 'shapes filled' flag for a series and sends a
422     * {@link RendererChangeEvent} to all registered listeners.
423     *
424     * @param series  the series index (zero-based).
425     * @param filled  the flag.
426     *
427     * @see #getSeriesShapesFilled(int)
428     */
429    public void setSeriesShapesFilled(int series, Boolean filled) {
430        this.seriesShapesFilled.setBoolean(series, filled);
431        fireChangeEvent();
432    }
433
434    /**
435     * Sets the 'shapes filled' flag for a series and sends a
436     * {@link RendererChangeEvent} to all registered listeners.
437     *
438     * @param series  the series index (zero-based).
439     * @param filled  the flag.
440     *
441     * @see #getSeriesShapesFilled(int)
442     */
443    public void setSeriesShapesFilled(int series, boolean filled) {
444        // delegate
445        setSeriesShapesFilled(series, Boolean.valueOf(filled));
446    }
447
448    /**
449     * Returns the default 'shape filled' attribute.
450     *
451     * @return The base flag.
452     *
453     * @see #setDefaultShapesFilled(boolean)
454     */
455    public boolean getDefaultShapesFilled() {
456        return this.defaultShapesFilled;
457    }
458
459    /**
460     * Sets the default 'shapes filled' flag and sends a
461     * {@link RendererChangeEvent} to all registered listeners.
462     *
463     * @param flag  the flag.
464     *
465     * @see #getDefaultShapesFilled()
466     */
467    public void setDefaultShapesFilled(boolean flag) {
468        this.defaultShapesFilled = flag;
469        fireChangeEvent();
470    }
471
472    /**
473     * Returns {@code true} if the renderer should use the fill paint
474     * setting to fill shapes, and {@code false} if it should just
475     * use the regular paint.
476     *
477     * @return A boolean.
478     *
479     * @see #setUseFillPaint(boolean)
480     */
481    public boolean getUseFillPaint() {
482        return this.useFillPaint;
483    }
484
485    /**
486     * Sets the flag that controls whether the fill paint is used to fill
487     * shapes, and sends a {@link RendererChangeEvent} to all
488     * registered listeners.
489     *
490     * @param flag  the flag.
491     *
492     * @see #getUseFillPaint()
493     */
494    public void setUseFillPaint(boolean flag) {
495        this.useFillPaint = flag;
496        fireChangeEvent();
497    }
498
499    /**
500     * Returns the flag that controls whether or not the x-position for each
501     * data item is offset within the category according to the series.
502     *
503     * @return A boolean.
504     *
505     * @see #setUseSeriesOffset(boolean)
506     */
507    public boolean getUseSeriesOffset() {
508        return this.useSeriesOffset;
509    }
510
511    /**
512     * Sets the flag that controls whether or not the x-position for each
513     * data item is offset within its category according to the series, and
514     * sends a {@link RendererChangeEvent} to all registered listeners.
515     *
516     * @param offset  the offset.
517     *
518     * @see #getUseSeriesOffset()
519     */
520    public void setUseSeriesOffset(boolean offset) {
521        this.useSeriesOffset = offset;
522        fireChangeEvent();
523    }
524
525    /**
526     * Returns the item margin, which is the gap between items within a
527     * category (expressed as a percentage of the overall category width).
528     * This can be used to match the offset alignment with the bars drawn by
529     * a {@link BarRenderer}).
530     *
531     * @return The item margin.
532     *
533     * @see #setItemMargin(double)
534     * @see #getUseSeriesOffset()
535     */
536    public double getItemMargin() {
537        return this.itemMargin;
538    }
539
540    /**
541     * Sets the item margin, which is the gap between items within a category
542     * (expressed as a percentage of the overall category width), and sends
543     * a {@link RendererChangeEvent} to all registered listeners.
544     *
545     * @param margin  the margin (0.0 &lt;= margin &lt; 1.0).
546     *
547     * @see #getItemMargin()
548     * @see #getUseSeriesOffset()
549     */
550    public void setItemMargin(double margin) {
551        if (margin < 0.0 || margin >= 1.0) {
552            throw new IllegalArgumentException("Requires 0.0 <= margin < 1.0.");
553        }
554        this.itemMargin = margin;
555        fireChangeEvent();
556    }
557
558    /**
559     * Returns a legend item for a series.
560     *
561     * @param datasetIndex  the dataset index (zero-based).
562     * @param series  the series index (zero-based).
563     *
564     * @return The legend item.
565     */
566    @Override
567    public LegendItem getLegendItem(int datasetIndex, int series) {
568
569        CategoryPlot cp = getPlot();
570        if (cp == null) {
571            return null;
572        }
573
574        if (isSeriesVisible(series) && isSeriesVisibleInLegend(series)) {
575            CategoryDataset dataset = cp.getDataset(datasetIndex);
576            String label = getLegendItemLabelGenerator().generateLabel(
577                    dataset, series);
578            String description = label;
579            String toolTipText = null;
580            if (getLegendItemToolTipGenerator() != null) {
581                toolTipText = getLegendItemToolTipGenerator().generateLabel(
582                        dataset, series);
583            }
584            String urlText = null;
585            if (getLegendItemURLGenerator() != null) {
586                urlText = getLegendItemURLGenerator().generateLabel(
587                        dataset, series);
588            }
589            Shape shape = lookupLegendShape(series);
590            Paint paint = lookupSeriesPaint(series);
591            Paint fillPaint = (this.useFillPaint
592                    ? getItemFillPaint(series, 0) : paint);
593            boolean shapeOutlineVisible = this.drawOutlines;
594            Paint outlinePaint = (this.useOutlinePaint
595                    ? getItemOutlinePaint(series, 0) : paint);
596            Stroke outlineStroke = lookupSeriesOutlineStroke(series);
597            boolean lineVisible = getItemLineVisible(series, 0);
598            boolean shapeVisible = getItemShapeVisible(series, 0);
599            LegendItem result = new LegendItem(label, description, toolTipText,
600                    urlText, shapeVisible, shape, getItemShapeFilled(series, 0),
601                    fillPaint, shapeOutlineVisible, outlinePaint, outlineStroke,
602                    lineVisible, new Line2D.Double(-7.0, 0.0, 7.0, 0.0),
603                    getItemStroke(series, 0), getItemPaint(series, 0));
604            result.setLabelFont(lookupLegendTextFont(series));
605            Paint labelPaint = lookupLegendTextPaint(series);
606            if (labelPaint != null) {
607                result.setLabelPaint(labelPaint);
608            }
609            result.setDataset(dataset);
610            result.setDatasetIndex(datasetIndex);
611            result.setSeriesKey(dataset.getRowKey(series));
612            result.setSeriesIndex(series);
613            return result;
614        }
615        return null;
616
617    }
618
619    /**
620     * This renderer uses two passes to draw the data.
621     *
622     * @return The pass count ({@code 2} for this renderer).
623     */
624    @Override
625    public int getPassCount() {
626        return 2;
627    }
628
629    /**
630     * Draw a single data item.
631     *
632     * @param g2  the graphics device.
633     * @param state  the renderer state.
634     * @param dataArea  the area in which the data is drawn.
635     * @param plot  the plot.
636     * @param domainAxis  the domain axis.
637     * @param rangeAxis  the range axis.
638     * @param dataset  the dataset.
639     * @param row  the row index (zero-based).
640     * @param column  the column index (zero-based).
641     * @param pass  the pass index.
642     */
643    @Override
644    public void drawItem(Graphics2D g2, CategoryItemRendererState state,
645            Rectangle2D dataArea, CategoryPlot plot, CategoryAxis domainAxis,
646            ValueAxis rangeAxis, CategoryDataset dataset, int row, int column,
647            int pass) {
648
649        // do nothing if item is not visible
650        if (!getItemVisible(row, column)) {
651            return;
652        }
653
654        // do nothing if both the line and shape are not visible
655        if (!getItemLineVisible(row, column)
656                && !getItemShapeVisible(row, column)) {
657            return;
658        }
659
660        // nothing is drawn for null...
661        Number v = dataset.getValue(row, column);
662        if (v == null) {
663            return;
664        }
665
666        int visibleRow = state.getVisibleSeriesIndex(row);
667        if (visibleRow < 0) {
668            return;
669        }
670        int visibleRowCount = state.getVisibleSeriesCount();
671
672        PlotOrientation orientation = plot.getOrientation();
673
674        // current data point...
675        double x1;
676        if (this.useSeriesOffset) {
677            x1 = domainAxis.getCategorySeriesMiddle(column,
678                    dataset.getColumnCount(), visibleRow, visibleRowCount,
679                    this.itemMargin, dataArea, plot.getDomainAxisEdge());
680        }
681        else {
682            x1 = domainAxis.getCategoryMiddle(column, getColumnCount(),
683                    dataArea, plot.getDomainAxisEdge());
684        }
685        double value = v.doubleValue();
686        double y1 = rangeAxis.valueToJava2D(value, dataArea,
687                plot.getRangeAxisEdge());
688
689        if (pass == 0 && getItemLineVisible(row, column)) {
690            if (column != 0) {
691                Number previousValue = dataset.getValue(row, column - 1);
692                if (previousValue != null) {
693                    // previous data point...
694                    double previous = previousValue.doubleValue();
695                    double x0;
696                    if (this.useSeriesOffset) {
697                        x0 = domainAxis.getCategorySeriesMiddle(
698                                column - 1, dataset.getColumnCount(),
699                                visibleRow, visibleRowCount,
700                                this.itemMargin, dataArea,
701                                plot.getDomainAxisEdge());
702                    }
703                    else {
704                        x0 = domainAxis.getCategoryMiddle(column - 1,
705                                getColumnCount(), dataArea,
706                                plot.getDomainAxisEdge());
707                    }
708                    double y0 = rangeAxis.valueToJava2D(previous, dataArea,
709                            plot.getRangeAxisEdge());
710
711                    Line2D line = null;
712                    if (orientation == PlotOrientation.HORIZONTAL) {
713                        line = new Line2D.Double(y0, x0, y1, x1);
714                    }
715                    else if (orientation == PlotOrientation.VERTICAL) {
716                        line = new Line2D.Double(x0, y0, x1, y1);
717                    }
718                    g2.setPaint(getItemPaint(row, column));
719                    g2.setStroke(getItemStroke(row, column));
720                    g2.draw(line);
721                }
722            }
723        }
724
725        if (pass == 1) {
726            Shape shape = getItemShape(row, column);
727            if (orientation == PlotOrientation.HORIZONTAL) {
728                shape = ShapeUtils.createTranslatedShape(shape, y1, x1);
729            }
730            else if (orientation == PlotOrientation.VERTICAL) {
731                shape = ShapeUtils.createTranslatedShape(shape, x1, y1);
732            }
733
734            if (getItemShapeVisible(row, column)) {
735                if (getItemShapeFilled(row, column)) {
736                    if (this.useFillPaint) {
737                        g2.setPaint(getItemFillPaint(row, column));
738                    }
739                    else {
740                        g2.setPaint(getItemPaint(row, column));
741                    }
742                    g2.fill(shape);
743                }
744                if (this.drawOutlines) {
745                    if (this.useOutlinePaint) {
746                        g2.setPaint(getItemOutlinePaint(row, column));
747                    }
748                    else {
749                        g2.setPaint(getItemPaint(row, column));
750                    }
751                    g2.setStroke(getItemOutlineStroke(row, column));
752                    g2.draw(shape);
753                }
754            }
755
756            // draw the item label if there is one...
757            if (isItemLabelVisible(row, column)) {
758                if (orientation == PlotOrientation.HORIZONTAL) {
759                    drawItemLabel(g2, orientation, dataset, row, column, y1,
760                            x1, (value < 0.0));
761                }
762                else if (orientation == PlotOrientation.VERTICAL) {
763                    drawItemLabel(g2, orientation, dataset, row, column, x1,
764                            y1, (value < 0.0));
765                }
766            }
767
768            // submit the current data point as a crosshair candidate
769            int datasetIndex = plot.indexOf(dataset);
770            updateCrosshairValues(state.getCrosshairState(),
771                    dataset.getRowKey(row), dataset.getColumnKey(column),
772                    value, datasetIndex, x1, y1, orientation);
773
774            // add an item entity, if this information is being collected
775            EntityCollection entities = state.getEntityCollection();
776            if (entities != null) {
777                addItemEntity(entities, dataset, row, column, shape);
778            }
779        }
780
781    }
782
783    /**
784     * Tests this renderer for equality with an arbitrary object.
785     *
786     * @param obj  the object ({@code null} permitted).
787     *
788     * @return A boolean.
789     */
790    @Override
791    public boolean equals(Object obj) {
792
793        if (obj == this) {
794            return true;
795        }
796        if (!(obj instanceof LineAndShapeRenderer)) {
797            return false;
798        }
799
800        LineAndShapeRenderer that = (LineAndShapeRenderer) obj;
801        if (this.defaultLinesVisible != that.defaultLinesVisible) {
802            return false;
803        }
804        if (!Objects.equals(this.seriesLinesVisible,
805                that.seriesLinesVisible)) {
806            return false;
807        }
808        if (this.defaultShapesVisible != that.defaultShapesVisible) {
809            return false;
810        }
811        if (!Objects.equals(this.seriesShapesVisible,
812                that.seriesShapesVisible)) {
813            return false;
814        }
815        if (!Objects.equals(this.seriesShapesFilled,
816                that.seriesShapesFilled)) {
817            return false;
818        }
819        if (this.defaultShapesFilled != that.defaultShapesFilled) {
820            return false;
821        }
822        if (this.useOutlinePaint != that.useOutlinePaint) {
823            return false;
824        }
825        if (this.useSeriesOffset != that.useSeriesOffset) {
826            return false;
827        }
828        if (this.itemMargin != that.itemMargin) {
829            return false;
830        }
831        return super.equals(obj);
832    }
833
834    /**
835     * Returns an independent copy of the renderer.
836     *
837     * @return A clone.
838     *
839     * @throws CloneNotSupportedException  should not happen.
840     */
841    @Override
842    public Object clone() throws CloneNotSupportedException {
843        LineAndShapeRenderer clone = (LineAndShapeRenderer) super.clone();
844        clone.seriesLinesVisible
845                = (BooleanList) this.seriesLinesVisible.clone();
846        clone.seriesShapesVisible
847                = (BooleanList) this.seriesShapesVisible.clone();
848        clone.seriesShapesFilled
849                = (BooleanList) this.seriesShapesFilled.clone();
850        return clone;
851    }
852
853}