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 * LegendItem.java
029 * ---------------
030 * (C) Copyright 2000-2021, by Object Refinery Limited and Contributors.
031 *
032 * Original Author:  David Gilbert (for Object Refinery Limited);
033 * Contributor(s):   Andrzej Porebski;
034 *                   David Li;
035 *                   Wolfgang Irler;
036 *                   Luke Quinane;
037 *
038 */
039
040package org.jfree.chart;
041
042import java.awt.BasicStroke;
043import java.awt.Color;
044import java.awt.Font;
045import java.awt.Paint;
046import java.awt.Shape;
047import java.awt.Stroke;
048import java.awt.geom.Line2D;
049import java.awt.geom.Rectangle2D;
050import java.io.IOException;
051import java.io.ObjectInputStream;
052import java.io.ObjectOutputStream;
053import java.io.Serializable;
054import java.text.AttributedString;
055import java.text.CharacterIterator;
056import java.util.Objects;
057import org.jfree.chart.text.AttributedStringUtils;
058import org.jfree.chart.ui.GradientPaintTransformer;
059import org.jfree.chart.ui.StandardGradientPaintTransformer;
060import org.jfree.chart.util.PaintUtils;
061import org.jfree.chart.util.Args;
062import org.jfree.chart.util.PublicCloneable;
063import org.jfree.chart.util.SerialUtils;
064import org.jfree.chart.util.ShapeUtils;
065import org.jfree.data.general.Dataset;
066
067/**
068 * A temporary storage object for recording the properties of a legend item,
069 * without any consideration for layout issues.
070 */
071public class LegendItem implements Cloneable, Serializable {
072
073    /** For serialization. */
074    private static final long serialVersionUID = -797214582948827144L;
075
076    /**
077     * The dataset.
078     */
079    private Dataset dataset;
080
081    /**
082     * The series key.
083     */
084    private Comparable seriesKey;
085
086    /** The dataset index. */
087    private int datasetIndex;
088
089    /** The series index. */
090    private int series;
091
092    /** The label. */
093    private String label;
094
095    /**
096     * The label font ({@code null} is permitted).
097     */
098    private Font labelFont;
099
100    /**
101     * The label paint ({@code null} is permitted).
102     */
103    private transient Paint labelPaint;
104
105    /** The attributed label (if null, fall back to the regular label). */
106    private transient AttributedString attributedLabel;
107
108    /**
109     * The description (not currently used - could be displayed as a tool tip).
110     */
111    private String description;
112
113    /** The tool tip text. */
114    private String toolTipText;
115
116    /** The url text. */
117    private String urlText;
118
119    /** A flag that controls whether or not the shape is visible. */
120    private boolean shapeVisible;
121
122    /** The shape. */
123    private transient Shape shape;
124
125    /** A flag that controls whether or not the shape is filled. */
126    private boolean shapeFilled;
127
128    /** The paint. */
129    private transient Paint fillPaint;
130
131    /**
132     * A gradient paint transformer.
133     */
134    private GradientPaintTransformer fillPaintTransformer;
135
136    /** A flag that controls whether or not the shape outline is visible. */
137    private boolean shapeOutlineVisible;
138
139    /** The outline paint. */
140    private transient Paint outlinePaint;
141
142    /** The outline stroke. */
143    private transient Stroke outlineStroke;
144
145    /** A flag that controls whether or not the line is visible. */
146    private boolean lineVisible;
147
148    /** The line. */
149    private transient Shape line;
150
151    /** The stroke. */
152    private transient Stroke lineStroke;
153
154    /** The line paint. */
155    private transient Paint linePaint;
156
157    /**
158     * The shape must be non-null for a LegendItem - if no shape is required,
159     * use this.
160     */
161    private static final Shape UNUSED_SHAPE = new Line2D.Float();
162
163    /**
164     * The stroke must be non-null for a LegendItem - if no stroke is required,
165     * use this.
166     */
167    private static final Stroke UNUSED_STROKE = new BasicStroke(0.0f);
168
169    /**
170     * Creates a legend item with the specified label.  The remaining
171     * attributes take default values.
172     *
173     * @param label  the label ({@code null} not permitted).
174     */
175    public LegendItem(String label) {
176        this(label, Color.BLACK);
177    }
178
179    /**
180     * Creates a legend item with the specified label and fill paint.  The
181     * remaining attributes take default values.
182     *
183     * @param label  the label ({@code null} not permitted).
184     * @param paint  the paint ({@code null} not permitted).
185     */
186    public LegendItem(String label, Paint paint) {
187        this(label, null, null, null, new Rectangle2D.Double(-4.0, -4.0, 8.0,
188                8.0), paint);
189    }
190
191    /**
192     * Creates a legend item with a filled shape.  The shape is not outlined,
193     * and no line is visible.
194     *
195     * @param label  the label ({@code null} not permitted).
196     * @param description  the description ({@code null} permitted).
197     * @param toolTipText  the tool tip text ({@code null} permitted).
198     * @param urlText  the URL text ({@code null} permitted).
199     * @param shape  the shape ({@code null} not permitted).
200     * @param fillPaint  the paint used to fill the shape ({@code null}
201     *                   not permitted).
202     */
203    public LegendItem(String label, String description,
204                      String toolTipText, String urlText,
205                      Shape shape, Paint fillPaint) {
206
207        this(label, description, toolTipText, urlText,
208                /* shape visible = */ true, shape,
209                /* shape filled = */ true, fillPaint,
210                /* shape outlined */ false, Color.BLACK, UNUSED_STROKE,
211                /* line visible */ false, UNUSED_SHAPE, UNUSED_STROKE,
212                Color.BLACK);
213
214    }
215
216    /**
217     * Creates a legend item with a filled and outlined shape.
218     *
219     * @param label  the label ({@code null} not permitted).
220     * @param description  the description ({@code null} permitted).
221     * @param toolTipText  the tool tip text ({@code null} permitted).
222     * @param urlText  the URL text ({@code null} permitted).
223     * @param shape  the shape ({@code null} not permitted).
224     * @param fillPaint  the paint used to fill the shape ({@code null}
225     *                   not permitted).
226     * @param outlineStroke  the outline stroke ({@code null} not
227     *                       permitted).
228     * @param outlinePaint  the outline paint ({@code null} not
229     *                      permitted).
230     */
231    public LegendItem(String label, String description, String toolTipText, 
232            String urlText, Shape shape, Paint fillPaint, Stroke outlineStroke, 
233            Paint outlinePaint) {
234
235        this(label, description, toolTipText, urlText,
236                /* shape visible = */ true, shape,
237                /* shape filled = */ true, fillPaint,
238                /* shape outlined = */ true, outlinePaint, outlineStroke,
239                /* line visible */ false, UNUSED_SHAPE, UNUSED_STROKE,
240                Color.BLACK);
241
242    }
243
244    /**
245     * Creates a legend item using a line.
246     *
247     * @param label  the label ({@code null} not permitted).
248     * @param description  the description ({@code null} permitted).
249     * @param toolTipText  the tool tip text ({@code null} permitted).
250     * @param urlText  the URL text ({@code null} permitted).
251     * @param line  the line ({@code null} not permitted).
252     * @param lineStroke  the line stroke ({@code null} not permitted).
253     * @param linePaint  the line paint ({@code null} not permitted).
254     */
255    public LegendItem(String label, String description, String toolTipText, 
256            String urlText, Shape line, Stroke lineStroke, Paint linePaint) {
257
258        this(label, description, toolTipText, urlText,
259                /* shape visible = */ false, UNUSED_SHAPE,
260                /* shape filled = */ false, Color.BLACK,
261                /* shape outlined = */ false, Color.BLACK, UNUSED_STROKE,
262                /* line visible = */ true, line, lineStroke, linePaint);
263    }
264
265    /**
266     * Creates a new legend item.
267     *
268     * @param label  the label ({@code null} not permitted).
269     * @param description  the description (not currently used,
270     *        {@code null} permitted).
271     * @param toolTipText  the tool tip text ({@code null} permitted).
272     * @param urlText  the URL text ({@code null} permitted).
273     * @param shapeVisible  a flag that controls whether or not the shape is
274     *                      displayed.
275     * @param shape  the shape ({@code null} permitted).
276     * @param shapeFilled  a flag that controls whether or not the shape is
277     *                     filled.
278     * @param fillPaint  the fill paint ({@code null} not permitted).
279     * @param shapeOutlineVisible  a flag that controls whether or not the
280     *                             shape is outlined.
281     * @param outlinePaint  the outline paint ({@code null} not permitted).
282     * @param outlineStroke  the outline stroke ({@code null} not
283     *                       permitted).
284     * @param lineVisible  a flag that controls whether or not the line is
285     *                     visible.
286     * @param line  the line.
287     * @param lineStroke  the stroke ({@code null} not permitted).
288     * @param linePaint  the line paint ({@code null} not permitted).
289     */
290    public LegendItem(String label, String description,
291                      String toolTipText, String urlText,
292                      boolean shapeVisible, Shape shape,
293                      boolean shapeFilled, Paint fillPaint,
294                      boolean shapeOutlineVisible, Paint outlinePaint,
295                      Stroke outlineStroke,
296                      boolean lineVisible, Shape line,
297                      Stroke lineStroke, Paint linePaint) {
298
299        Args.nullNotPermitted(label, "label");
300        Args.nullNotPermitted(fillPaint, "fillPaint");
301        Args.nullNotPermitted(lineStroke, "lineStroke");
302        Args.nullNotPermitted(outlinePaint, "outlinePaint");
303        Args.nullNotPermitted(outlineStroke, "outlineStroke");
304        this.label = label;
305        this.labelPaint = null;
306        this.attributedLabel = null;
307        this.description = description;
308        this.shapeVisible = shapeVisible;
309        this.shape = shape;
310        this.shapeFilled = shapeFilled;
311        this.fillPaint = fillPaint;
312        this.fillPaintTransformer = new StandardGradientPaintTransformer();
313        this.shapeOutlineVisible = shapeOutlineVisible;
314        this.outlinePaint = outlinePaint;
315        this.outlineStroke = outlineStroke;
316        this.lineVisible = lineVisible;
317        this.line = line;
318        this.lineStroke = lineStroke;
319        this.linePaint = linePaint;
320        this.toolTipText = toolTipText;
321        this.urlText = urlText;
322    }
323
324    /**
325     * Creates a legend item with a filled shape.  The shape is not outlined,
326     * and no line is visible.
327     *
328     * @param label  the label ({@code null} not permitted).
329     * @param description  the description ({@code null} permitted).
330     * @param toolTipText  the tool tip text ({@code null} permitted).
331     * @param urlText  the URL text ({@code null} permitted).
332     * @param shape  the shape ({@code null} not permitted).
333     * @param fillPaint  the paint used to fill the shape ({@code null}
334     *                   not permitted).
335     */
336    public LegendItem(AttributedString label, String description,
337                      String toolTipText, String urlText,
338                      Shape shape, Paint fillPaint) {
339
340        this(label, description, toolTipText, urlText,
341                /* shape visible = */ true, shape,
342                /* shape filled = */ true, fillPaint,
343                /* shape outlined = */ false, Color.BLACK, UNUSED_STROKE,
344                /* line visible = */ false, UNUSED_SHAPE, UNUSED_STROKE,
345                Color.BLACK);
346
347    }
348
349    /**
350     * Creates a legend item with a filled and outlined shape.
351     *
352     * @param label  the label ({@code null} not permitted).
353     * @param description  the description ({@code null} permitted).
354     * @param toolTipText  the tool tip text ({@code null} permitted).
355     * @param urlText  the URL text ({@code null} permitted).
356     * @param shape  the shape ({@code null} not permitted).
357     * @param fillPaint  the paint used to fill the shape ({@code null}
358     *                   not permitted).
359     * @param outlineStroke  the outline stroke ({@code null} not
360     *                       permitted).
361     * @param outlinePaint  the outline paint ({@code null} not
362     *                      permitted).
363     */
364    public LegendItem(AttributedString label, String description,
365                      String toolTipText, String urlText,
366                      Shape shape, Paint fillPaint,
367                      Stroke outlineStroke, Paint outlinePaint) {
368
369        this(label, description, toolTipText, urlText,
370                /* shape visible = */ true, shape,
371                /* shape filled = */ true, fillPaint,
372                /* shape outlined = */ true, outlinePaint, outlineStroke,
373                /* line visible = */ false, UNUSED_SHAPE, UNUSED_STROKE,
374                Color.BLACK);
375    }
376
377    /**
378     * Creates a legend item using a line.
379     *
380     * @param label  the label ({@code null} not permitted).
381     * @param description  the description ({@code null} permitted).
382     * @param toolTipText  the tool tip text ({@code null} permitted).
383     * @param urlText  the URL text ({@code null} permitted).
384     * @param line  the line ({@code null} not permitted).
385     * @param lineStroke  the line stroke ({@code null} not permitted).
386     * @param linePaint  the line paint ({@code null} not permitted).
387     */
388    public LegendItem(AttributedString label, String description,
389                      String toolTipText, String urlText,
390                      Shape line, Stroke lineStroke, Paint linePaint) {
391
392        this(label, description, toolTipText, urlText,
393                /* shape visible = */ false, UNUSED_SHAPE,
394                /* shape filled = */ false, Color.BLACK,
395                /* shape outlined = */ false, Color.BLACK, UNUSED_STROKE,
396                /* line visible = */ true, line, lineStroke, linePaint);
397    }
398
399    /**
400     * Creates a new legend item.
401     *
402     * @param label  the label ({@code null} not permitted).
403     * @param description  the description (not currently used,
404     *        {@code null} permitted).
405     * @param toolTipText  the tool tip text ({@code null} permitted).
406     * @param urlText  the URL text ({@code null} permitted).
407     * @param shapeVisible  a flag that controls whether or not the shape is
408     *                      displayed.
409     * @param shape  the shape ({@code null} permitted).
410     * @param shapeFilled  a flag that controls whether or not the shape is
411     *                     filled.
412     * @param fillPaint  the fill paint ({@code null} not permitted).
413     * @param shapeOutlineVisible  a flag that controls whether or not the
414     *                             shape is outlined.
415     * @param outlinePaint  the outline paint ({@code null} not permitted).
416     * @param outlineStroke  the outline stroke ({@code null} not
417     *                       permitted).
418     * @param lineVisible  a flag that controls whether or not the line is
419     *                     visible.
420     * @param line  the line ({@code null} not permitted).
421     * @param lineStroke  the stroke ({@code null} not permitted).
422     * @param linePaint  the line paint ({@code null} not permitted).
423     */
424    public LegendItem(AttributedString label, String description,
425                      String toolTipText, String urlText,
426                      boolean shapeVisible, Shape shape,
427                      boolean shapeFilled, Paint fillPaint,
428                      boolean shapeOutlineVisible, Paint outlinePaint,
429                      Stroke outlineStroke,
430                      boolean lineVisible, Shape line, Stroke lineStroke,
431                      Paint linePaint) {
432
433        Args.nullNotPermitted(label, "label");
434        Args.nullNotPermitted(fillPaint, "fillPaint");
435        Args.nullNotPermitted(lineStroke, "lineStroke");
436        Args.nullNotPermitted(line, "line");
437        Args.nullNotPermitted(linePaint, "linePaint");
438        Args.nullNotPermitted(outlinePaint, "outlinePaint");
439        Args.nullNotPermitted(outlineStroke, "outlineStroke");
440        this.label = characterIteratorToString(label.getIterator());
441        this.attributedLabel = label;
442        this.description = description;
443        this.shapeVisible = shapeVisible;
444        this.shape = shape;
445        this.shapeFilled = shapeFilled;
446        this.fillPaint = fillPaint;
447        this.fillPaintTransformer = new StandardGradientPaintTransformer();
448        this.shapeOutlineVisible = shapeOutlineVisible;
449        this.outlinePaint = outlinePaint;
450        this.outlineStroke = outlineStroke;
451        this.lineVisible = lineVisible;
452        this.line = line;
453        this.lineStroke = lineStroke;
454        this.linePaint = linePaint;
455        this.toolTipText = toolTipText;
456        this.urlText = urlText;
457    }
458
459    /**
460     * Returns a string containing the characters from the given iterator.
461     *
462     * @param iterator  the iterator ({@code null} not permitted).
463     *
464     * @return A string.
465     */
466    private String characterIteratorToString(CharacterIterator iterator) {
467        int endIndex = iterator.getEndIndex();
468        int beginIndex = iterator.getBeginIndex();
469        int count = endIndex - beginIndex;
470        if (count <= 0) {
471            return "";
472        }
473        char[] chars = new char[count];
474        int i = 0;
475        char c = iterator.first();
476        while (c != CharacterIterator.DONE) {
477            chars[i] = c;
478            i++;
479            c = iterator.next();
480        }
481        return new String(chars);
482    }
483
484    /**
485     * Returns the dataset.
486     *
487     * @return The dataset.
488     *
489     * @see #setDatasetIndex(int)
490     */
491    public Dataset getDataset() {
492        return this.dataset;
493    }
494
495    /**
496     * Sets the dataset.
497     *
498     * @param dataset  the dataset.
499     */
500    public void setDataset(Dataset dataset) {
501        this.dataset = dataset;
502    }
503
504    /**
505     * Returns the dataset index for this legend item.
506     *
507     * @return The dataset index.
508     *
509     * @see #setDatasetIndex(int)
510     * @see #getDataset()
511     */
512    public int getDatasetIndex() {
513        return this.datasetIndex;
514    }
515
516    /**
517     * Sets the dataset index for this legend item.
518     *
519     * @param index  the index.
520     *
521     * @see #getDatasetIndex()
522     */
523    public void setDatasetIndex(int index) {
524        this.datasetIndex = index;
525    }
526
527    /**
528     * Returns the series key.
529     *
530     * @return The series key.
531     *
532     * @see #setSeriesKey(Comparable)
533     */
534    public Comparable getSeriesKey() {
535        return this.seriesKey;
536    }
537
538    /**
539     * Sets the series key.
540     *
541     * @param key  the series key.
542     */
543    public void setSeriesKey(Comparable key) {
544        this.seriesKey = key;
545    }
546
547    /**
548     * Returns the series index for this legend item.
549     *
550     * @return The series index.
551     */
552    public int getSeriesIndex() {
553        return this.series;
554    }
555
556    /**
557     * Sets the series index for this legend item.
558     *
559     * @param index  the index.
560     */
561    public void setSeriesIndex(int index) {
562        this.series = index;
563    }
564
565    /**
566     * Returns the label.
567     *
568     * @return The label (never {@code null}).
569     */
570    public String getLabel() {
571        return this.label;
572    }
573
574    /**
575     * Returns the label font.
576     *
577     * @return The label font (possibly {@code null}).
578     */
579    public Font getLabelFont() {
580        return this.labelFont;
581    }
582
583    /**
584     * Sets the label font.
585     *
586     * @param font  the font ({@code null} permitted).
587     */
588    public void setLabelFont(Font font) {
589        this.labelFont = font;
590    }
591
592    /**
593     * Returns the paint used to draw the label.
594     *
595     * @return The paint (possibly {@code null}).
596     */
597    public Paint getLabelPaint() {
598        return this.labelPaint;
599    }
600
601    /**
602     * Sets the paint used to draw the label.
603     *
604     * @param paint  the paint ({@code null} permitted).
605     */
606    public void setLabelPaint(Paint paint) {
607        this.labelPaint = paint;
608    }
609
610    /**
611     * Returns the attributed label.
612     *
613     * @return The attributed label (possibly {@code null}).
614     */
615    public AttributedString getAttributedLabel() {
616        return this.attributedLabel;
617    }
618
619    /**
620     * Returns the description for the legend item.
621     *
622     * @return The description (possibly {@code null}).
623     *
624     * @see #setDescription(java.lang.String) 
625     */
626    public String getDescription() {
627        return this.description;
628    }
629
630    /**
631     * Sets the description for this legend item.
632     *
633     * @param text  the description ({@code null} permitted).
634     *
635     * @see #getDescription()
636     */
637    public void setDescription(String text) {
638        this.description = text;
639    }
640
641    /**
642     * Returns the tool tip text.
643     *
644     * @return The tool tip text (possibly {@code null}).
645     *
646     * @see #setToolTipText(java.lang.String) 
647     */
648    public String getToolTipText() {
649        return this.toolTipText;
650    }
651
652    /**
653     * Sets the tool tip text for this legend item.
654     *
655     * @param text  the text ({@code null} permitted).
656     *
657     * @see #getToolTipText()
658     */
659    public void setToolTipText(String text) {
660        this.toolTipText = text;
661    }
662
663    /**
664     * Returns the URL text.
665     *
666     * @return The URL text (possibly {@code null}).
667     *
668     * @see #setURLText(java.lang.String) 
669     */
670    public String getURLText() {
671        return this.urlText;
672    }
673
674    /**
675     * Sets the URL text.
676     *
677     * @param text  the text ({@code null} permitted).
678     *
679     * @see #getURLText()
680     */
681    public void setURLText(String text) {
682        this.urlText = text;
683    }
684
685    /**
686     * Returns a flag that indicates whether or not the shape is visible.
687     *
688     * @return A boolean.
689     *
690     * @see #setShapeVisible(boolean)
691     */
692    public boolean isShapeVisible() {
693        return this.shapeVisible;
694    }
695
696    /**
697     * Sets the flag that controls whether or not the shape is visible.
698     *
699     * @param visible  the new flag value.
700     *
701     * @see #isShapeVisible()
702     * @see #isLineVisible()
703     */
704    public void setShapeVisible(boolean visible) {
705        this.shapeVisible = visible;
706    }
707
708    /**
709     * Returns the shape used to label the series represented by this legend
710     * item.
711     *
712     * @return The shape (never {@code null}).
713     *
714     * @see #setShape(java.awt.Shape) 
715     */
716    public Shape getShape() {
717        return this.shape;
718    }
719
720    /**
721     * Sets the shape for the legend item.
722     *
723     * @param shape  the shape ({@code null} not permitted).
724     *
725     * @see #getShape()
726     */
727    public void setShape(Shape shape) {
728        Args.nullNotPermitted(shape, "shape");
729        this.shape = shape;
730    }
731
732    /**
733     * Returns a flag that controls whether or not the shape is filled.
734     *
735     * @return A boolean.
736     */
737    public boolean isShapeFilled() {
738        return this.shapeFilled;
739    }
740
741    /**
742     * Returns the fill paint.
743     *
744     * @return The fill paint (never {@code null}).
745     */
746    public Paint getFillPaint() {
747        return this.fillPaint;
748    }
749
750    /**
751     * Sets the fill paint.
752     *
753     * @param paint  the paint ({@code null} not permitted).
754     */
755    public void setFillPaint(Paint paint) {
756        Args.nullNotPermitted(paint, "paint");
757        this.fillPaint = paint;
758    }
759
760    /**
761     * Returns the flag that controls whether or not the shape outline
762     * is visible.
763     *
764     * @return A boolean.
765     */
766    public boolean isShapeOutlineVisible() {
767        return this.shapeOutlineVisible;
768    }
769
770    /**
771     * Returns the line stroke for the series.
772     *
773     * @return The stroke (never {@code null}).
774     */
775    public Stroke getLineStroke() {
776        return this.lineStroke;
777    }
778    
779    /**
780     * Sets the line stroke.
781     * 
782     * @param stroke  the stroke ({@code null} not permitted).
783     */
784    public void setLineStroke(Stroke stroke) {
785        Args.nullNotPermitted(stroke, "stroke");
786        this.lineStroke = stroke;
787    }
788
789    /**
790     * Returns the paint used for lines.
791     *
792     * @return The paint (never {@code null}).
793     */
794    public Paint getLinePaint() {
795        return this.linePaint;
796    }
797
798    /**
799     * Sets the line paint.
800     *
801     * @param paint  the paint ({@code null} not permitted).
802     */
803    public void setLinePaint(Paint paint) {
804        Args.nullNotPermitted(paint, "paint");
805        this.linePaint = paint;
806    }
807
808    /**
809     * Returns the outline paint.
810     *
811     * @return The outline paint (never {@code null}).
812     */
813    public Paint getOutlinePaint() {
814        return this.outlinePaint;
815    }
816
817    /**
818     * Sets the outline paint.
819     *
820     * @param paint  the paint ({@code null} not permitted).
821     */
822    public void setOutlinePaint(Paint paint) {
823        Args.nullNotPermitted(paint, "paint");
824        this.outlinePaint = paint;
825    }
826
827    /**
828     * Returns the outline stroke.
829     *
830     * @return The outline stroke (never {@code null}).
831     *
832     * @see #setOutlineStroke(java.awt.Stroke) 
833     */
834    public Stroke getOutlineStroke() {
835        return this.outlineStroke;
836    }
837
838    /**
839     * Sets the outline stroke.
840     *
841     * @param stroke  the stroke ({@code null} not permitted).
842     *
843     * @see #getOutlineStroke()
844     */
845    public void setOutlineStroke(Stroke stroke) {
846        Args.nullNotPermitted(stroke, "stroke");
847        this.outlineStroke = stroke;
848    }
849
850    /**
851     * Returns a flag that indicates whether or not the line is visible.
852     *
853     * @return A boolean.
854     *
855     * @see #setLineVisible(boolean) 
856     */
857    public boolean isLineVisible() {
858        return this.lineVisible;
859    }
860
861    /**
862     * Sets the flag that controls whether or not the line shape is visible for
863     * this legend item.
864     *
865     * @param visible  the new flag value.
866     *
867     * @see #isLineVisible()
868     */
869    public void setLineVisible(boolean visible) {
870        this.lineVisible = visible;
871    }
872
873    /**
874     * Returns the line.
875     *
876     * @return The line (never {@code null}).
877     *
878     * @see #setLine(java.awt.Shape)
879     * @see #isLineVisible() 
880     */
881    public Shape getLine() {
882        return this.line;
883    }
884
885    /**
886     * Sets the line.
887     *
888     * @param line  the line ({@code null} not permitted).
889     *
890     * @see #getLine()
891     */
892    public void setLine(Shape line) {
893        Args.nullNotPermitted(line, "line");
894        this.line = line;
895    }
896
897    /**
898     * Returns the transformer used when the fill paint is an instance of
899     * {@code GradientPaint}.
900     *
901     * @return The transformer (never {@code null}).
902     *
903     * @see #setFillPaintTransformer(GradientPaintTransformer)
904     */
905    public GradientPaintTransformer getFillPaintTransformer() {
906        return this.fillPaintTransformer;
907    }
908
909    /**
910     * Sets the transformer used when the fill paint is an instance of
911     * {@code GradientPaint}.
912     *
913     * @param transformer  the transformer ({@code null} not permitted).
914     *
915     * @see #getFillPaintTransformer()
916     */
917    public void setFillPaintTransformer(GradientPaintTransformer transformer) {
918        Args.nullNotPermitted(transformer, "transformer");
919        this.fillPaintTransformer = transformer;
920    }
921
922    /**
923     * Tests this item for equality with an arbitrary object.
924     *
925     * @param obj  the object ({@code null} permitted).
926     *
927     * @return A boolean.
928     */
929    @Override
930    public boolean equals(Object obj) {
931        if (obj == this) {
932            return true;
933        }
934        if (!(obj instanceof LegendItem)) {
935            return false;
936        }
937        LegendItem that = (LegendItem) obj;
938        if (this.datasetIndex != that.datasetIndex) {
939            return false;
940        }
941        if (this.series != that.series) {
942            return false;
943        }
944        if (!this.label.equals(that.label)) {
945            return false;
946        }
947        if (!AttributedStringUtils.equal(this.attributedLabel,
948                that.attributedLabel)) {
949            return false;
950        }
951        if (!Objects.equals(this.description, that.description)) {
952            return false;
953        }
954        if (this.shapeVisible != that.shapeVisible) {
955            return false;
956        }
957        if (!ShapeUtils.equal(this.shape, that.shape)) {
958            return false;
959        }
960        if (this.shapeFilled != that.shapeFilled) {
961            return false;
962        }
963        if (!PaintUtils.equal(this.fillPaint, that.fillPaint)) {
964            return false;
965        }
966        if (!Objects.equals(this.fillPaintTransformer,
967                that.fillPaintTransformer)) {
968            return false;
969        }
970        if (this.shapeOutlineVisible != that.shapeOutlineVisible) {
971            return false;
972        }
973        if (!this.outlineStroke.equals(that.outlineStroke)) {
974            return false;
975        }
976        if (!PaintUtils.equal(this.outlinePaint, that.outlinePaint)) {
977            return false;
978        }
979        if (!this.lineVisible == that.lineVisible) {
980            return false;
981        }
982        if (!ShapeUtils.equal(this.line, that.line)) {
983            return false;
984        }
985        if (!this.lineStroke.equals(that.lineStroke)) {
986            return false;
987        }
988        if (!PaintUtils.equal(this.linePaint, that.linePaint)) {
989            return false;
990        }
991        if (!Objects.equals(this.labelFont, that.labelFont)) {
992            return false;
993        }
994        if (!PaintUtils.equal(this.labelPaint, that.labelPaint)) {
995            return false;
996        }
997        return true;
998    }
999
1000    /**
1001     * Returns an independent copy of this object (except that the clone will
1002     * still reference the same dataset as the original {@code LegendItem}).
1003     *
1004     * @return A clone.
1005     *
1006     * @throws CloneNotSupportedException if the legend item cannot be cloned.
1007     */
1008    @Override
1009    public Object clone() throws CloneNotSupportedException {
1010        LegendItem clone = (LegendItem) super.clone();
1011        if (this.seriesKey instanceof PublicCloneable) {
1012            PublicCloneable pc = (PublicCloneable) this.seriesKey;
1013            clone.seriesKey = (Comparable) pc.clone();
1014        }
1015        // FIXME: Clone the attributed string if it is not null
1016        clone.shape = ShapeUtils.clone(this.shape);
1017        if (this.fillPaintTransformer instanceof PublicCloneable) {
1018            PublicCloneable pc = (PublicCloneable) this.fillPaintTransformer;
1019            clone.fillPaintTransformer = (GradientPaintTransformer) pc.clone();
1020
1021        }
1022        clone.line = ShapeUtils.clone(this.line);
1023        return clone;
1024    }
1025
1026    /**
1027     * Provides serialization support.
1028     *
1029     * @param stream  the output stream ({@code null} not permitted).
1030     *
1031     * @throws IOException  if there is an I/O error.
1032     */
1033    private void writeObject(ObjectOutputStream stream) throws IOException {
1034        stream.defaultWriteObject();
1035        SerialUtils.writeAttributedString(this.attributedLabel, stream);
1036        SerialUtils.writeShape(this.shape, stream);
1037        SerialUtils.writePaint(this.fillPaint, stream);
1038        SerialUtils.writeStroke(this.outlineStroke, stream);
1039        SerialUtils.writePaint(this.outlinePaint, stream);
1040        SerialUtils.writeShape(this.line, stream);
1041        SerialUtils.writeStroke(this.lineStroke, stream);
1042        SerialUtils.writePaint(this.linePaint, stream);
1043        SerialUtils.writePaint(this.labelPaint, stream);
1044    }
1045
1046    /**
1047     * Provides serialization support.
1048     *
1049     * @param stream  the input stream ({@code null} not permitted).
1050     *
1051     * @throws IOException  if there is an I/O error.
1052     * @throws ClassNotFoundException  if there is a classpath problem.
1053     */
1054    private void readObject(ObjectInputStream stream)
1055        throws IOException, ClassNotFoundException {
1056        stream.defaultReadObject();
1057        this.attributedLabel = SerialUtils.readAttributedString(stream);
1058        this.shape = SerialUtils.readShape(stream);
1059        this.fillPaint = SerialUtils.readPaint(stream);
1060        this.outlineStroke = SerialUtils.readStroke(stream);
1061        this.outlinePaint = SerialUtils.readPaint(stream);
1062        this.line = SerialUtils.readShape(stream);
1063        this.lineStroke = SerialUtils.readStroke(stream);
1064        this.linePaint = SerialUtils.readPaint(stream);
1065        this.labelPaint = SerialUtils.readPaint(stream);
1066    }
1067
1068}