1 /* ===========================================================
2  * JFreeChart : a free chart library for the Java(tm) platform
3  * ===========================================================
4  *
5  * (C) Copyright 2000-2013, by Object Refinery Limited and Contributors.
6  *
7  * Project Info:  http://www.jfree.org/jfreechart/index.html
8  *
9  * This library is free software; you can redistribute it and/or modify it
10  * under the terms of the GNU Lesser General Public License as published by
11  * the Free Software Foundation; either version 2.1 of the License, or
12  * (at your option) any later version.
13  *
14  * This library is distributed in the hope that it will be useful, but
15  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
16  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
17  * License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public
20  * License along with this library; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
22  * USA.
23  *
24  * [Oracle and Java are registered trademarks of Oracle and/or its affiliates.
25  * Other names may be trademarks of their respective owners.]
26  *
27  * ------------------
28  * LegendGraphic.java
29  * ------------------
30  * (C) Copyright 2004-2013, by Object Refinery Limited.
31  *
32  * Original Author:  David Gilbert (for Object Refinery Limited);
33  * Contributor(s):   -;
34  *
35  * Changes
36  * -------
37  * 26-Oct-2004 : Version 1 (DG);
38  * 21-Jan-2005 : Modified return type of RectangleAnchor.coordinates()
39  *               method (DG);
40  * 20-Apr-2005 : Added new draw() method (DG);
41  * 13-May-2005 : Fixed to respect margin, border and padding settings (DG);
42  * 01-Sep-2005 : Implemented PublicCloneable (DG);
43  * ------------- JFREECHART 1.0.x ---------------------------------------------
44  * 13-Dec-2006 : Added fillPaintTransformer attribute, so legend graphics can
45  *               display gradient paint correctly, updated equals() and
46  *               corrected clone() (DG);
47  * 01-Aug-2007 : Updated API docs (DG);
48  * 03-Jul-2013 : Use ParamChecks (DG);
49  *
50  */
51 
52 package org.jfree.chart.title;
53 
54 import java.awt.GradientPaint;
55 import java.awt.Graphics2D;
56 import java.awt.Paint;
57 import java.awt.Shape;
58 import java.awt.Stroke;
59 import java.awt.geom.Point2D;
60 import java.awt.geom.Rectangle2D;
61 import java.io.IOException;
62 import java.io.ObjectInputStream;
63 import java.io.ObjectOutputStream;
64 
65 import org.jfree.chart.block.AbstractBlock;
66 import org.jfree.chart.block.Block;
67 import org.jfree.chart.block.LengthConstraintType;
68 import org.jfree.chart.block.RectangleConstraint;
69 import org.jfree.chart.util.ParamChecks;
70 import org.jfree.io.SerialUtilities;
71 import org.jfree.ui.GradientPaintTransformer;
72 import org.jfree.ui.RectangleAnchor;
73 import org.jfree.ui.Size2D;
74 import org.jfree.ui.StandardGradientPaintTransformer;
75 import org.jfree.util.ObjectUtilities;
76 import org.jfree.util.PaintUtilities;
77 import org.jfree.util.PublicCloneable;
78 import org.jfree.util.ShapeUtilities;
79 
80 /**
81  * The graphical item within a legend item.
82  */
83 public class LegendGraphic extends AbstractBlock
84                            implements Block, PublicCloneable {
85 
86     /** For serialization. */
87     static final long serialVersionUID = -1338791523854985009L;
88 
89     /**
90      * A flag that controls whether or not the shape is visible - see also
91      * lineVisible.
92      */
93     private boolean shapeVisible;
94 
95     /**
96      * The shape to display.  To allow for accurate positioning, the center
97      * of the shape should be at (0, 0).
98      */
99     private transient Shape shape;
100 
101     /**
102      * Defines the location within the block to which the shape will be aligned.
103      */
104     private RectangleAnchor shapeLocation;
105 
106     /**
107      * Defines the point on the shape's bounding rectangle that will be
108      * aligned to the drawing location when the shape is rendered.
109      */
110     private RectangleAnchor shapeAnchor;
111 
112     /** A flag that controls whether or not the shape is filled. */
113     private boolean shapeFilled;
114 
115     /** The fill paint for the shape. */
116     private transient Paint fillPaint;
117 
118     /**
119      * The fill paint transformer (used if the fillPaint is an instance of
120      * GradientPaint).
121      *
122      * @since 1.0.4
123      */
124     private GradientPaintTransformer fillPaintTransformer;
125 
126     /** A flag that controls whether or not the shape outline is visible. */
127     private boolean shapeOutlineVisible;
128 
129     /** The outline paint for the shape. */
130     private transient Paint outlinePaint;
131 
132     /** The outline stroke for the shape. */
133     private transient Stroke outlineStroke;
134 
135     /**
136      * A flag that controls whether or not the line is visible - see also
137      * shapeVisible.
138      */
139     private boolean lineVisible;
140 
141     /** The line. */
142     private transient Shape line;
143 
144     /** The line stroke. */
145     private transient Stroke lineStroke;
146 
147     /** The line paint. */
148     private transient Paint linePaint;
149 
150     /**
151      * Creates a new legend graphic.
152      *
153      * @param shape  the shape (<code>null</code> not permitted).
154      * @param fillPaint  the fill paint (<code>null</code> not permitted).
155      */
LegendGraphic(Shape shape, Paint fillPaint)156     public LegendGraphic(Shape shape, Paint fillPaint) {
157         ParamChecks.nullNotPermitted(shape, "shape");
158         ParamChecks.nullNotPermitted(fillPaint, "fillPaint");
159         this.shapeVisible = true;
160         this.shape = shape;
161         this.shapeAnchor = RectangleAnchor.CENTER;
162         this.shapeLocation = RectangleAnchor.CENTER;
163         this.shapeFilled = true;
164         this.fillPaint = fillPaint;
165         this.fillPaintTransformer = new StandardGradientPaintTransformer();
166         setPadding(2.0, 2.0, 2.0, 2.0);
167     }
168 
169     /**
170      * Returns a flag that controls whether or not the shape
171      * is visible.
172      *
173      * @return A boolean.
174      *
175      * @see #setShapeVisible(boolean)
176      */
isShapeVisible()177     public boolean isShapeVisible() {
178         return this.shapeVisible;
179     }
180 
181     /**
182      * Sets a flag that controls whether or not the shape is
183      * visible.
184      *
185      * @param visible  the flag.
186      *
187      * @see #isShapeVisible()
188      */
setShapeVisible(boolean visible)189     public void setShapeVisible(boolean visible) {
190         this.shapeVisible = visible;
191     }
192 
193     /**
194      * Returns the shape.
195      *
196      * @return The shape.
197      *
198      * @see #setShape(Shape)
199      */
getShape()200     public Shape getShape() {
201         return this.shape;
202     }
203 
204     /**
205      * Sets the shape.
206      *
207      * @param shape  the shape.
208      *
209      * @see #getShape()
210      */
setShape(Shape shape)211     public void setShape(Shape shape) {
212         this.shape = shape;
213     }
214 
215     /**
216      * Returns a flag that controls whether or not the shapes
217      * are filled.
218      *
219      * @return A boolean.
220      *
221      * @see #setShapeFilled(boolean)
222      */
isShapeFilled()223     public boolean isShapeFilled() {
224         return this.shapeFilled;
225     }
226 
227     /**
228      * Sets a flag that controls whether or not the shape is
229      * filled.
230      *
231      * @param filled  the flag.
232      *
233      * @see #isShapeFilled()
234      */
setShapeFilled(boolean filled)235     public void setShapeFilled(boolean filled) {
236         this.shapeFilled = filled;
237     }
238 
239     /**
240      * Returns the paint used to fill the shape.
241      *
242      * @return The fill paint.
243      *
244      * @see #setFillPaint(Paint)
245      */
getFillPaint()246     public Paint getFillPaint() {
247         return this.fillPaint;
248     }
249 
250     /**
251      * Sets the paint used to fill the shape.
252      *
253      * @param paint  the paint.
254      *
255      * @see #getFillPaint()
256      */
setFillPaint(Paint paint)257     public void setFillPaint(Paint paint) {
258         this.fillPaint = paint;
259     }
260 
261     /**
262      * Returns the transformer used when the fill paint is an instance of
263      * <code>GradientPaint</code>.
264      *
265      * @return The transformer (never <code>null</code>).
266      *
267      * @since 1.0.4.
268      *
269      * @see #setFillPaintTransformer(GradientPaintTransformer)
270      */
getFillPaintTransformer()271     public GradientPaintTransformer getFillPaintTransformer() {
272         return this.fillPaintTransformer;
273     }
274 
275     /**
276      * Sets the transformer used when the fill paint is an instance of
277      * <code>GradientPaint</code>.
278      *
279      * @param transformer  the transformer (<code>null</code> not permitted).
280      *
281      * @since 1.0.4
282      *
283      * @see #getFillPaintTransformer()
284      */
setFillPaintTransformer(GradientPaintTransformer transformer)285     public void setFillPaintTransformer(GradientPaintTransformer transformer) {
286         ParamChecks.nullNotPermitted(transformer, "transformer");
287         this.fillPaintTransformer = transformer;
288     }
289 
290     /**
291      * Returns a flag that controls whether the shape outline is visible.
292      *
293      * @return A boolean.
294      *
295      * @see #setShapeOutlineVisible(boolean)
296      */
isShapeOutlineVisible()297     public boolean isShapeOutlineVisible() {
298         return this.shapeOutlineVisible;
299     }
300 
301     /**
302      * Sets a flag that controls whether or not the shape outline
303      * is visible.
304      *
305      * @param visible  the flag.
306      *
307      * @see #isShapeOutlineVisible()
308      */
setShapeOutlineVisible(boolean visible)309     public void setShapeOutlineVisible(boolean visible) {
310         this.shapeOutlineVisible = visible;
311     }
312 
313     /**
314      * Returns the outline paint.
315      *
316      * @return The paint.
317      *
318      * @see #setOutlinePaint(Paint)
319      */
getOutlinePaint()320     public Paint getOutlinePaint() {
321         return this.outlinePaint;
322     }
323 
324     /**
325      * Sets the outline paint.
326      *
327      * @param paint  the paint.
328      *
329      * @see #getOutlinePaint()
330      */
setOutlinePaint(Paint paint)331     public void setOutlinePaint(Paint paint) {
332         this.outlinePaint = paint;
333     }
334 
335     /**
336      * Returns the outline stroke.
337      *
338      * @return The stroke.
339      *
340      * @see #setOutlineStroke(Stroke)
341      */
getOutlineStroke()342     public Stroke getOutlineStroke() {
343         return this.outlineStroke;
344     }
345 
346     /**
347      * Sets the outline stroke.
348      *
349      * @param stroke  the stroke.
350      *
351      * @see #getOutlineStroke()
352      */
setOutlineStroke(Stroke stroke)353     public void setOutlineStroke(Stroke stroke) {
354         this.outlineStroke = stroke;
355     }
356 
357     /**
358      * Returns the shape anchor.
359      *
360      * @return The shape anchor.
361      *
362      * @see #getShapeAnchor()
363      */
getShapeAnchor()364     public RectangleAnchor getShapeAnchor() {
365         return this.shapeAnchor;
366     }
367 
368     /**
369      * Sets the shape anchor.  This defines a point on the shapes bounding
370      * rectangle that will be used to align the shape to a location.
371      *
372      * @param anchor  the anchor (<code>null</code> not permitted).
373      *
374      * @see #setShapeAnchor(RectangleAnchor)
375      */
setShapeAnchor(RectangleAnchor anchor)376     public void setShapeAnchor(RectangleAnchor anchor) {
377         ParamChecks.nullNotPermitted(anchor, "anchor");
378         this.shapeAnchor = anchor;
379     }
380 
381     /**
382      * Returns the shape location.
383      *
384      * @return The shape location.
385      *
386      * @see #setShapeLocation(RectangleAnchor)
387      */
getShapeLocation()388     public RectangleAnchor getShapeLocation() {
389         return this.shapeLocation;
390     }
391 
392     /**
393      * Sets the shape location.  This defines a point within the drawing
394      * area that will be used to align the shape to.
395      *
396      * @param location  the location (<code>null</code> not permitted).
397      *
398      * @see #getShapeLocation()
399      */
setShapeLocation(RectangleAnchor location)400     public void setShapeLocation(RectangleAnchor location) {
401         ParamChecks.nullNotPermitted(location, "location");
402         this.shapeLocation = location;
403     }
404 
405     /**
406      * Returns the flag that controls whether or not the line is visible.
407      *
408      * @return A boolean.
409      *
410      * @see #setLineVisible(boolean)
411      */
isLineVisible()412     public boolean isLineVisible() {
413         return this.lineVisible;
414     }
415 
416     /**
417      * Sets the flag that controls whether or not the line is visible.
418      *
419      * @param visible  the flag.
420      *
421      * @see #isLineVisible()
422      */
setLineVisible(boolean visible)423     public void setLineVisible(boolean visible) {
424         this.lineVisible = visible;
425     }
426 
427     /**
428      * Returns the line centered about (0, 0).
429      *
430      * @return The line.
431      *
432      * @see #setLine(Shape)
433      */
getLine()434     public Shape getLine() {
435         return this.line;
436     }
437 
438     /**
439      * Sets the line.  A Shape is used here, because then you can use Line2D,
440      * GeneralPath or any other Shape to represent the line.
441      *
442      * @param line  the line.
443      *
444      * @see #getLine()
445      */
setLine(Shape line)446     public void setLine(Shape line) {
447         this.line = line;
448     }
449 
450     /**
451      * Returns the line paint.
452      *
453      * @return The paint.
454      *
455      * @see #setLinePaint(Paint)
456      */
getLinePaint()457     public Paint getLinePaint() {
458         return this.linePaint;
459     }
460 
461     /**
462      * Sets the line paint.
463      *
464      * @param paint  the paint.
465      *
466      * @see #getLinePaint()
467      */
setLinePaint(Paint paint)468     public void setLinePaint(Paint paint) {
469         this.linePaint = paint;
470     }
471 
472     /**
473      * Returns the line stroke.
474      *
475      * @return The stroke.
476      *
477      * @see #setLineStroke(Stroke)
478      */
getLineStroke()479     public Stroke getLineStroke() {
480         return this.lineStroke;
481     }
482 
483     /**
484      * Sets the line stroke.
485      *
486      * @param stroke  the stroke.
487      *
488      * @see #getLineStroke()
489      */
setLineStroke(Stroke stroke)490     public void setLineStroke(Stroke stroke) {
491         this.lineStroke = stroke;
492     }
493 
494     /**
495      * Arranges the contents of the block, within the given constraints, and
496      * returns the block size.
497      *
498      * @param g2  the graphics device.
499      * @param constraint  the constraint (<code>null</code> not permitted).
500      *
501      * @return The block size (in Java2D units, never <code>null</code>).
502      */
503     @Override
arrange(Graphics2D g2, RectangleConstraint constraint)504     public Size2D arrange(Graphics2D g2, RectangleConstraint constraint) {
505         RectangleConstraint contentConstraint = toContentConstraint(constraint);
506         LengthConstraintType w = contentConstraint.getWidthConstraintType();
507         LengthConstraintType h = contentConstraint.getHeightConstraintType();
508         Size2D contentSize = null;
509         if (w == LengthConstraintType.NONE) {
510             if (h == LengthConstraintType.NONE) {
511                 contentSize = arrangeNN(g2);
512             }
513             else if (h == LengthConstraintType.RANGE) {
514                 throw new RuntimeException("Not yet implemented.");
515             }
516             else if (h == LengthConstraintType.FIXED) {
517                 throw new RuntimeException("Not yet implemented.");
518             }
519         }
520         else if (w == LengthConstraintType.RANGE) {
521             if (h == LengthConstraintType.NONE) {
522                 throw new RuntimeException("Not yet implemented.");
523             }
524             else if (h == LengthConstraintType.RANGE) {
525                 throw new RuntimeException("Not yet implemented.");
526             }
527             else if (h == LengthConstraintType.FIXED) {
528                 throw new RuntimeException("Not yet implemented.");
529             }
530         }
531         else if (w == LengthConstraintType.FIXED) {
532             if (h == LengthConstraintType.NONE) {
533                 throw new RuntimeException("Not yet implemented.");
534             }
535             else if (h == LengthConstraintType.RANGE) {
536                 throw new RuntimeException("Not yet implemented.");
537             }
538             else if (h == LengthConstraintType.FIXED) {
539                 contentSize = new Size2D(contentConstraint.getWidth(),
540                         contentConstraint.getHeight());
541             }
542         }
543         assert contentSize != null;
544         return new Size2D(calculateTotalWidth(contentSize.getWidth()),
545                 calculateTotalHeight(contentSize.getHeight()));
546     }
547 
548     /**
549      * Performs the layout with no constraint, so the content size is
550      * determined by the bounds of the shape and/or line drawn to represent
551      * the series.
552      *
553      * @param g2  the graphics device.
554      *
555      * @return  The content size.
556      */
arrangeNN(Graphics2D g2)557     protected Size2D arrangeNN(Graphics2D g2) {
558         Rectangle2D contentSize = new Rectangle2D.Double();
559         if (this.line != null) {
560             contentSize.setRect(this.line.getBounds2D());
561         }
562         if (this.shape != null) {
563             contentSize = contentSize.createUnion(this.shape.getBounds2D());
564         }
565         return new Size2D(contentSize.getWidth(), contentSize.getHeight());
566     }
567 
568     /**
569      * Draws the graphic item within the specified area.
570      *
571      * @param g2  the graphics device.
572      * @param area  the area.
573      */
574     @Override
draw(Graphics2D g2, Rectangle2D area)575     public void draw(Graphics2D g2, Rectangle2D area) {
576 
577         area = trimMargin(area);
578         drawBorder(g2, area);
579         area = trimBorder(area);
580         area = trimPadding(area);
581 
582         if (this.lineVisible) {
583             Point2D location = RectangleAnchor.coordinates(area,
584                     this.shapeLocation);
585             Shape aLine = ShapeUtilities.createTranslatedShape(getLine(),
586                     this.shapeAnchor, location.getX(), location.getY());
587             g2.setPaint(this.linePaint);
588             g2.setStroke(this.lineStroke);
589             g2.draw(aLine);
590         }
591 
592         if (this.shapeVisible) {
593             Point2D location = RectangleAnchor.coordinates(area,
594                     this.shapeLocation);
595 
596             Shape s = ShapeUtilities.createTranslatedShape(this.shape,
597                     this.shapeAnchor, location.getX(), location.getY());
598             if (this.shapeFilled) {
599                 Paint p = this.fillPaint;
600                 if (p instanceof GradientPaint) {
601                     GradientPaint gp = (GradientPaint) this.fillPaint;
602                     p = this.fillPaintTransformer.transform(gp, s);
603                 }
604                 g2.setPaint(p);
605                 g2.fill(s);
606             }
607             if (this.shapeOutlineVisible) {
608                 g2.setPaint(this.outlinePaint);
609                 g2.setStroke(this.outlineStroke);
610                 g2.draw(s);
611             }
612         }
613     }
614 
615     /**
616      * Draws the block within the specified area.
617      *
618      * @param g2  the graphics device.
619      * @param area  the area.
620      * @param params  ignored (<code>null</code> permitted).
621      *
622      * @return Always <code>null</code>.
623      */
624     @Override
draw(Graphics2D g2, Rectangle2D area, Object params)625     public Object draw(Graphics2D g2, Rectangle2D area, Object params) {
626         draw(g2, area);
627         return null;
628     }
629 
630     /**
631      * Tests this <code>LegendGraphic</code> instance for equality with an
632      * arbitrary object.
633      *
634      * @param obj  the object (<code>null</code> permitted).
635      *
636      * @return A boolean.
637      */
638     @Override
equals(Object obj)639     public boolean equals(Object obj) {
640         if (!(obj instanceof LegendGraphic)) {
641             return false;
642         }
643         LegendGraphic that = (LegendGraphic) obj;
644         if (this.shapeVisible != that.shapeVisible) {
645             return false;
646         }
647         if (!ShapeUtilities.equal(this.shape, that.shape)) {
648             return false;
649         }
650         if (this.shapeFilled != that.shapeFilled) {
651             return false;
652         }
653         if (!PaintUtilities.equal(this.fillPaint, that.fillPaint)) {
654             return false;
655         }
656         if (!ObjectUtilities.equal(this.fillPaintTransformer,
657                 that.fillPaintTransformer)) {
658             return false;
659         }
660         if (this.shapeOutlineVisible != that.shapeOutlineVisible) {
661             return false;
662         }
663         if (!PaintUtilities.equal(this.outlinePaint, that.outlinePaint)) {
664             return false;
665         }
666         if (!ObjectUtilities.equal(this.outlineStroke, that.outlineStroke)) {
667             return false;
668         }
669         if (this.shapeAnchor != that.shapeAnchor) {
670             return false;
671         }
672         if (this.shapeLocation != that.shapeLocation) {
673             return false;
674         }
675         if (this.lineVisible != that.lineVisible) {
676             return false;
677         }
678         if (!ShapeUtilities.equal(this.line, that.line)) {
679             return false;
680         }
681         if (!PaintUtilities.equal(this.linePaint, that.linePaint)) {
682             return false;
683         }
684         if (!ObjectUtilities.equal(this.lineStroke, that.lineStroke)) {
685             return false;
686         }
687         return super.equals(obj);
688     }
689 
690     /**
691      * Returns a hash code for this instance.
692      *
693      * @return A hash code.
694      */
695     @Override
hashCode()696     public int hashCode() {
697         int result = 193;
698         result = 37 * result + ObjectUtilities.hashCode(this.fillPaint);
699         // FIXME: use other fields too
700         return result;
701     }
702 
703     /**
704      * Returns a clone of this <code>LegendGraphic</code> instance.
705      *
706      * @return A clone of this <code>LegendGraphic</code> instance.
707      *
708      * @throws CloneNotSupportedException if there is a problem cloning.
709      */
710     @Override
clone()711     public Object clone() throws CloneNotSupportedException {
712         LegendGraphic clone = (LegendGraphic) super.clone();
713         clone.shape = ShapeUtilities.clone(this.shape);
714         clone.line = ShapeUtilities.clone(this.line);
715         return clone;
716     }
717 
718     /**
719      * Provides serialization support.
720      *
721      * @param stream  the output stream.
722      *
723      * @throws IOException  if there is an I/O error.
724      */
writeObject(ObjectOutputStream stream)725     private void writeObject(ObjectOutputStream stream) throws IOException {
726         stream.defaultWriteObject();
727         SerialUtilities.writeShape(this.shape, stream);
728         SerialUtilities.writePaint(this.fillPaint, stream);
729         SerialUtilities.writePaint(this.outlinePaint, stream);
730         SerialUtilities.writeStroke(this.outlineStroke, stream);
731         SerialUtilities.writeShape(this.line, stream);
732         SerialUtilities.writePaint(this.linePaint, stream);
733         SerialUtilities.writeStroke(this.lineStroke, stream);
734     }
735 
736     /**
737      * Provides serialization support.
738      *
739      * @param stream  the input stream.
740      *
741      * @throws IOException  if there is an I/O error.
742      * @throws ClassNotFoundException  if there is a classpath problem.
743      */
readObject(ObjectInputStream stream)744     private void readObject(ObjectInputStream stream)
745             throws IOException, ClassNotFoundException {
746         stream.defaultReadObject();
747         this.shape = SerialUtilities.readShape(stream);
748         this.fillPaint = SerialUtilities.readPaint(stream);
749         this.outlinePaint = SerialUtilities.readPaint(stream);
750         this.outlineStroke = SerialUtilities.readStroke(stream);
751         this.line = SerialUtilities.readShape(stream);
752         this.linePaint = SerialUtilities.readPaint(stream);
753         this.lineStroke = SerialUtilities.readStroke(stream);
754     }
755 
756 }
757