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