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 * LegendGraphic.java
029 * ------------------
030 * (C) Copyright 2004-2021, by Object Refinery Limited.
031 *
032 * Original Author:  David Gilbert (for Object Refinery Limited);
033 * Contributor(s):   -;
034 *
035 */
036
037package org.jfree.chart.title;
038
039import java.awt.GradientPaint;
040import java.awt.Graphics2D;
041import java.awt.Paint;
042import java.awt.Shape;
043import java.awt.Stroke;
044import java.awt.geom.Point2D;
045import java.awt.geom.Rectangle2D;
046import java.io.IOException;
047import java.io.ObjectInputStream;
048import java.io.ObjectOutputStream;
049import java.util.Objects;
050
051import org.jfree.chart.block.AbstractBlock;
052import org.jfree.chart.block.Block;
053import org.jfree.chart.block.LengthConstraintType;
054import org.jfree.chart.block.RectangleConstraint;
055import org.jfree.chart.ui.GradientPaintTransformer;
056import org.jfree.chart.ui.RectangleAnchor;
057import org.jfree.chart.ui.Size2D;
058import org.jfree.chart.ui.StandardGradientPaintTransformer;
059import org.jfree.chart.util.ObjectUtils;
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;
065
066/**
067 * The graphical item within a legend item.
068 */
069public class LegendGraphic extends AbstractBlock
070                           implements Block, PublicCloneable {
071
072    /** For serialization. */
073    static final long serialVersionUID = -1338791523854985009L;
074
075    /**
076     * A flag that controls whether or not the shape is visible - see also
077     * lineVisible.
078     */
079    private boolean shapeVisible;
080
081    /**
082     * The shape to display.  To allow for accurate positioning, the center
083     * of the shape should be at (0, 0).
084     */
085    private transient Shape shape;
086
087    /**
088     * Defines the location within the block to which the shape will be aligned.
089     */
090    private RectangleAnchor shapeLocation;
091
092    /**
093     * Defines the point on the shape's bounding rectangle that will be
094     * aligned to the drawing location when the shape is rendered.
095     */
096    private RectangleAnchor shapeAnchor;
097
098    /** A flag that controls whether or not the shape is filled. */
099    private boolean shapeFilled;
100
101    /** The fill paint for the shape. */
102    private transient Paint fillPaint;
103
104    /**
105     * The fill paint transformer (used if the fillPaint is an instance of
106     * GradientPaint).
107     */
108    private GradientPaintTransformer fillPaintTransformer;
109
110    /** A flag that controls whether or not the shape outline is visible. */
111    private boolean shapeOutlineVisible;
112
113    /** The outline paint for the shape. */
114    private transient Paint outlinePaint;
115
116    /** The outline stroke for the shape. */
117    private transient Stroke outlineStroke;
118
119    /**
120     * A flag that controls whether or not the line is visible - see also
121     * shapeVisible.
122     */
123    private boolean lineVisible;
124
125    /** The line. */
126    private transient Shape line;
127
128    /** The line stroke. */
129    private transient Stroke lineStroke;
130
131    /** The line paint. */
132    private transient Paint linePaint;
133
134    /**
135     * Creates a new legend graphic.
136     *
137     * @param shape  the shape ({@code null} not permitted).
138     * @param fillPaint  the fill paint ({@code null} not permitted).
139     */
140    public LegendGraphic(Shape shape, Paint fillPaint) {
141        Args.nullNotPermitted(shape, "shape");
142        Args.nullNotPermitted(fillPaint, "fillPaint");
143        this.shapeVisible = true;
144        this.shape = shape;
145        this.shapeAnchor = RectangleAnchor.CENTER;
146        this.shapeLocation = RectangleAnchor.CENTER;
147        this.shapeFilled = true;
148        this.fillPaint = fillPaint;
149        this.fillPaintTransformer = new StandardGradientPaintTransformer();
150        setPadding(2.0, 2.0, 2.0, 2.0);
151    }
152
153    /**
154     * Returns a flag that controls whether or not the shape
155     * is visible.
156     *
157     * @return A boolean.
158     *
159     * @see #setShapeVisible(boolean)
160     */
161    public boolean isShapeVisible() {
162        return this.shapeVisible;
163    }
164
165    /**
166     * Sets a flag that controls whether or not the shape is
167     * visible.
168     *
169     * @param visible  the flag.
170     *
171     * @see #isShapeVisible()
172     */
173    public void setShapeVisible(boolean visible) {
174        this.shapeVisible = visible;
175    }
176
177    /**
178     * Returns the shape.
179     *
180     * @return The shape.
181     *
182     * @see #setShape(Shape)
183     */
184    public Shape getShape() {
185        return this.shape;
186    }
187
188    /**
189     * Sets the shape.
190     *
191     * @param shape  the shape.
192     *
193     * @see #getShape()
194     */
195    public void setShape(Shape shape) {
196        this.shape = shape;
197    }
198
199    /**
200     * Returns a flag that controls whether or not the shapes
201     * are filled.
202     *
203     * @return A boolean.
204     *
205     * @see #setShapeFilled(boolean)
206     */
207    public boolean isShapeFilled() {
208        return this.shapeFilled;
209    }
210
211    /**
212     * Sets a flag that controls whether or not the shape is
213     * filled.
214     *
215     * @param filled  the flag.
216     *
217     * @see #isShapeFilled()
218     */
219    public void setShapeFilled(boolean filled) {
220        this.shapeFilled = filled;
221    }
222
223    /**
224     * Returns the paint used to fill the shape.
225     *
226     * @return The fill paint.
227     *
228     * @see #setFillPaint(Paint)
229     */
230    public Paint getFillPaint() {
231        return this.fillPaint;
232    }
233
234    /**
235     * Sets the paint used to fill the shape.
236     *
237     * @param paint  the paint.
238     *
239     * @see #getFillPaint()
240     */
241    public void setFillPaint(Paint paint) {
242        this.fillPaint = paint;
243    }
244
245    /**
246     * Returns the transformer used when the fill paint is an instance of
247     * {@code GradientPaint}.
248     *
249     * @return The transformer (never {@code null}).
250     *
251     * @see #setFillPaintTransformer(GradientPaintTransformer)
252     */
253    public GradientPaintTransformer getFillPaintTransformer() {
254        return this.fillPaintTransformer;
255    }
256
257    /**
258     * Sets the transformer used when the fill paint is an instance of
259     * {@code GradientPaint}.
260     *
261     * @param transformer  the transformer ({@code null} not permitted).
262     *
263     * @see #getFillPaintTransformer()
264     */
265    public void setFillPaintTransformer(GradientPaintTransformer transformer) {
266        Args.nullNotPermitted(transformer, "transformer");
267        this.fillPaintTransformer = transformer;
268    }
269
270    /**
271     * Returns a flag that controls whether the shape outline is visible.
272     *
273     * @return A boolean.
274     *
275     * @see #setShapeOutlineVisible(boolean)
276     */
277    public boolean isShapeOutlineVisible() {
278        return this.shapeOutlineVisible;
279    }
280
281    /**
282     * Sets a flag that controls whether or not the shape outline
283     * is visible.
284     *
285     * @param visible  the flag.
286     *
287     * @see #isShapeOutlineVisible()
288     */
289    public void setShapeOutlineVisible(boolean visible) {
290        this.shapeOutlineVisible = visible;
291    }
292
293    /**
294     * Returns the outline paint.
295     *
296     * @return The paint.
297     *
298     * @see #setOutlinePaint(Paint)
299     */
300    public Paint getOutlinePaint() {
301        return this.outlinePaint;
302    }
303
304    /**
305     * Sets the outline paint.
306     *
307     * @param paint  the paint.
308     *
309     * @see #getOutlinePaint()
310     */
311    public void setOutlinePaint(Paint paint) {
312        this.outlinePaint = paint;
313    }
314
315    /**
316     * Returns the outline stroke.
317     *
318     * @return The stroke.
319     *
320     * @see #setOutlineStroke(Stroke)
321     */
322    public Stroke getOutlineStroke() {
323        return this.outlineStroke;
324    }
325
326    /**
327     * Sets the outline stroke.
328     *
329     * @param stroke  the stroke.
330     *
331     * @see #getOutlineStroke()
332     */
333    public void setOutlineStroke(Stroke stroke) {
334        this.outlineStroke = stroke;
335    }
336
337    /**
338     * Returns the shape anchor.
339     *
340     * @return The shape anchor.
341     *
342     * @see #getShapeAnchor()
343     */
344    public RectangleAnchor getShapeAnchor() {
345        return this.shapeAnchor;
346    }
347
348    /**
349     * Sets the shape anchor.  This defines a point on the shapes bounding
350     * rectangle that will be used to align the shape to a location.
351     *
352     * @param anchor  the anchor ({@code null} not permitted).
353     *
354     * @see #setShapeAnchor(RectangleAnchor)
355     */
356    public void setShapeAnchor(RectangleAnchor anchor) {
357        Args.nullNotPermitted(anchor, "anchor");
358        this.shapeAnchor = anchor;
359    }
360
361    /**
362     * Returns the shape location.
363     *
364     * @return The shape location.
365     *
366     * @see #setShapeLocation(RectangleAnchor)
367     */
368    public RectangleAnchor getShapeLocation() {
369        return this.shapeLocation;
370    }
371
372    /**
373     * Sets the shape location.  This defines a point within the drawing
374     * area that will be used to align the shape to.
375     *
376     * @param location  the location ({@code null} not permitted).
377     *
378     * @see #getShapeLocation()
379     */
380    public void setShapeLocation(RectangleAnchor location) {
381        Args.nullNotPermitted(location, "location");
382        this.shapeLocation = location;
383    }
384
385    /**
386     * Returns the flag that controls whether or not the line is visible.
387     *
388     * @return A boolean.
389     *
390     * @see #setLineVisible(boolean)
391     */
392    public boolean isLineVisible() {
393        return this.lineVisible;
394    }
395
396    /**
397     * Sets the flag that controls whether or not the line is visible.
398     *
399     * @param visible  the flag.
400     *
401     * @see #isLineVisible()
402     */
403    public void setLineVisible(boolean visible) {
404        this.lineVisible = visible;
405    }
406
407    /**
408     * Returns the line centered about (0, 0).
409     *
410     * @return The line.
411     *
412     * @see #setLine(Shape)
413     */
414    public Shape getLine() {
415        return this.line;
416    }
417
418    /**
419     * Sets the line.  A Shape is used here, because then you can use Line2D,
420     * GeneralPath or any other Shape to represent the line.
421     *
422     * @param line  the line.
423     *
424     * @see #getLine()
425     */
426    public void setLine(Shape line) {
427        this.line = line;
428    }
429
430    /**
431     * Returns the line paint.
432     *
433     * @return The paint.
434     *
435     * @see #setLinePaint(Paint)
436     */
437    public Paint getLinePaint() {
438        return this.linePaint;
439    }
440
441    /**
442     * Sets the line paint.
443     *
444     * @param paint  the paint.
445     *
446     * @see #getLinePaint()
447     */
448    public void setLinePaint(Paint paint) {
449        this.linePaint = paint;
450    }
451
452    /**
453     * Returns the line stroke.
454     *
455     * @return The stroke.
456     *
457     * @see #setLineStroke(Stroke)
458     */
459    public Stroke getLineStroke() {
460        return this.lineStroke;
461    }
462
463    /**
464     * Sets the line stroke.
465     *
466     * @param stroke  the stroke.
467     *
468     * @see #getLineStroke()
469     */
470    public void setLineStroke(Stroke stroke) {
471        this.lineStroke = stroke;
472    }
473
474    /**
475     * Arranges the contents of the block, within the given constraints, and
476     * returns the block size.
477     *
478     * @param g2  the graphics device.
479     * @param constraint  the constraint ({@code null} not permitted).
480     *
481     * @return The block size (in Java2D units, never {@code null}).
482     */
483    @Override
484    public Size2D arrange(Graphics2D g2, RectangleConstraint constraint) {
485        RectangleConstraint contentConstraint = toContentConstraint(constraint);
486        LengthConstraintType w = contentConstraint.getWidthConstraintType();
487        LengthConstraintType h = contentConstraint.getHeightConstraintType();
488        Size2D contentSize = null;
489        if (w == LengthConstraintType.NONE) {
490            if (h == LengthConstraintType.NONE) {
491                contentSize = arrangeNN(g2);
492            }
493            else if (h == LengthConstraintType.RANGE) {
494                throw new RuntimeException("Not yet implemented.");
495            }
496            else if (h == LengthConstraintType.FIXED) {
497                throw new RuntimeException("Not yet implemented.");
498            }
499        }
500        else if (w == LengthConstraintType.RANGE) {
501            if (h == LengthConstraintType.NONE) {
502                throw new RuntimeException("Not yet implemented.");
503            }
504            else if (h == LengthConstraintType.RANGE) {
505                throw new RuntimeException("Not yet implemented.");
506            }
507            else if (h == LengthConstraintType.FIXED) {
508                throw new RuntimeException("Not yet implemented.");
509            }
510        }
511        else if (w == LengthConstraintType.FIXED) {
512            if (h == LengthConstraintType.NONE) {
513                throw new RuntimeException("Not yet implemented.");
514            }
515            else if (h == LengthConstraintType.RANGE) {
516                throw new RuntimeException("Not yet implemented.");
517            }
518            else if (h == LengthConstraintType.FIXED) {
519                contentSize = new Size2D(contentConstraint.getWidth(),
520                        contentConstraint.getHeight());
521            }
522        }
523        assert contentSize != null;
524        return new Size2D(calculateTotalWidth(contentSize.getWidth()),
525                calculateTotalHeight(contentSize.getHeight()));
526    }
527
528    /**
529     * Performs the layout with no constraint, so the content size is
530     * determined by the bounds of the shape and/or line drawn to represent
531     * the series.
532     *
533     * @param g2  the graphics device.
534     *
535     * @return  The content size.
536     */
537    protected Size2D arrangeNN(Graphics2D g2) {
538        Rectangle2D contentSize = new Rectangle2D.Double();
539        if (this.line != null) {
540            contentSize.setRect(this.line.getBounds2D());
541        }
542        if (this.shape != null) {
543            contentSize = contentSize.createUnion(this.shape.getBounds2D());
544        }
545        return new Size2D(contentSize.getWidth(), contentSize.getHeight());
546    }
547
548    /**
549     * Draws the graphic item within the specified area.
550     *
551     * @param g2  the graphics device.
552     * @param area  the area.
553     */
554    @Override
555    public void draw(Graphics2D g2, Rectangle2D area) {
556
557        area = trimMargin(area);
558        drawBorder(g2, area);
559        area = trimBorder(area);
560        area = trimPadding(area);
561
562        if (this.lineVisible) {
563            Point2D location = this.shapeLocation.getAnchorPoint(area);
564            Shape aLine = ShapeUtils.createTranslatedShape(getLine(),
565                    this.shapeAnchor, location.getX(), location.getY());
566            g2.setPaint(this.linePaint);
567            g2.setStroke(this.lineStroke);
568            g2.draw(aLine);
569        }
570
571        if (this.shapeVisible) {
572            Point2D location = this.shapeLocation.getAnchorPoint(area);
573
574            Shape s = ShapeUtils.createTranslatedShape(this.shape,
575                    this.shapeAnchor, location.getX(), location.getY());
576            if (this.shapeFilled) {
577                Paint p = this.fillPaint;
578                if (p instanceof GradientPaint) {
579                    GradientPaint gp = (GradientPaint) this.fillPaint;
580                    p = this.fillPaintTransformer.transform(gp, s);
581                }
582                g2.setPaint(p);
583                g2.fill(s);
584            }
585            if (this.shapeOutlineVisible) {
586                g2.setPaint(this.outlinePaint);
587                g2.setStroke(this.outlineStroke);
588                g2.draw(s);
589            }
590        }
591    }
592
593    /**
594     * Draws the block within the specified area.
595     *
596     * @param g2  the graphics device.
597     * @param area  the area.
598     * @param params  ignored ({@code null} permitted).
599     *
600     * @return Always {@code null}.
601     */
602    @Override
603    public Object draw(Graphics2D g2, Rectangle2D area, Object params) {
604        draw(g2, area);
605        return null;
606    }
607
608    /**
609     * Tests this {@code LegendGraphic} instance for equality with an
610     * arbitrary object.
611     *
612     * @param obj  the object ({@code null} permitted).
613     *
614     * @return A boolean.
615     */
616    @Override
617    public boolean equals(Object obj) {
618        if (!(obj instanceof LegendGraphic)) {
619            return false;
620        }
621        LegendGraphic that = (LegendGraphic) obj;
622        if (this.shapeVisible != that.shapeVisible) {
623            return false;
624        }
625        if (!ShapeUtils.equal(this.shape, that.shape)) {
626            return false;
627        }
628        if (this.shapeFilled != that.shapeFilled) {
629            return false;
630        }
631        if (!PaintUtils.equal(this.fillPaint, that.fillPaint)) {
632            return false;
633        }
634        if (!Objects.equals(this.fillPaintTransformer,
635                that.fillPaintTransformer)) {
636            return false;
637        }
638        if (this.shapeOutlineVisible != that.shapeOutlineVisible) {
639            return false;
640        }
641        if (!PaintUtils.equal(this.outlinePaint, that.outlinePaint)) {
642            return false;
643        }
644        if (!Objects.equals(this.outlineStroke, that.outlineStroke)) {
645            return false;
646        }
647        if (this.shapeAnchor != that.shapeAnchor) {
648            return false;
649        }
650        if (this.shapeLocation != that.shapeLocation) {
651            return false;
652        }
653        if (this.lineVisible != that.lineVisible) {
654            return false;
655        }
656        if (!ShapeUtils.equal(this.line, that.line)) {
657            return false;
658        }
659        if (!PaintUtils.equal(this.linePaint, that.linePaint)) {
660            return false;
661        }
662        if (!Objects.equals(this.lineStroke, that.lineStroke)) {
663            return false;
664        }
665        return super.equals(obj);
666    }
667
668    /**
669     * Returns a hash code for this instance.
670     *
671     * @return A hash code.
672     */
673    @Override
674    public int hashCode() {
675        int result = 193;
676        result = 37 * result + ObjectUtils.hashCode(this.fillPaint);
677        // FIXME: use other fields too
678        return result;
679    }
680
681    /**
682     * Returns a clone of this {@code LegendGraphic} instance.
683     *
684     * @return A clone of this {@code LegendGraphic} instance.
685     *
686     * @throws CloneNotSupportedException if there is a problem cloning.
687     */
688    @Override
689    public Object clone() throws CloneNotSupportedException {
690        LegendGraphic clone = (LegendGraphic) super.clone();
691        clone.shape = ShapeUtils.clone(this.shape);
692        clone.line = ShapeUtils.clone(this.line);
693        return clone;
694    }
695
696    /**
697     * Provides serialization support.
698     *
699     * @param stream  the output stream.
700     *
701     * @throws IOException  if there is an I/O error.
702     */
703    private void writeObject(ObjectOutputStream stream) throws IOException {
704        stream.defaultWriteObject();
705        SerialUtils.writeShape(this.shape, stream);
706        SerialUtils.writePaint(this.fillPaint, stream);
707        SerialUtils.writePaint(this.outlinePaint, stream);
708        SerialUtils.writeStroke(this.outlineStroke, stream);
709        SerialUtils.writeShape(this.line, stream);
710        SerialUtils.writePaint(this.linePaint, stream);
711        SerialUtils.writeStroke(this.lineStroke, stream);
712    }
713
714    /**
715     * Provides serialization support.
716     *
717     * @param stream  the input stream.
718     *
719     * @throws IOException  if there is an I/O error.
720     * @throws ClassNotFoundException  if there is a classpath problem.
721     */
722    private void readObject(ObjectInputStream stream)
723            throws IOException, ClassNotFoundException {
724        stream.defaultReadObject();
725        this.shape = SerialUtils.readShape(stream);
726        this.fillPaint = SerialUtils.readPaint(stream);
727        this.outlinePaint = SerialUtils.readPaint(stream);
728        this.outlineStroke = SerialUtils.readStroke(stream);
729        this.line = SerialUtils.readShape(stream);
730        this.linePaint = SerialUtils.readPaint(stream);
731        this.lineStroke = SerialUtils.readStroke(stream);
732    }
733
734}