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