001/* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2013, 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-2013, by Object Refinery Limited.
031 *
032 * Original Author:  David Gilbert (for Object Refinery Limited);
033 * Contributor(s):   -;
034 *
035 * Changes
036 * -------
037 * 26-Oct-2004 : Version 1 (DG);
038 * 21-Jan-2005 : Modified return type of RectangleAnchor.coordinates()
039 *               method (DG);
040 * 20-Apr-2005 : Added new draw() method (DG);
041 * 13-May-2005 : Fixed to respect margin, border and padding settings (DG);
042 * 01-Sep-2005 : Implemented PublicCloneable (DG);
043 * ------------- JFREECHART 1.0.x ---------------------------------------------
044 * 13-Dec-2006 : Added fillPaintTransformer attribute, so legend graphics can
045 *               display gradient paint correctly, updated equals() and
046 *               corrected clone() (DG);
047 * 01-Aug-2007 : Updated API docs (DG);
048 * 03-Jul-2013 : Use ParamChecks (DG);
049 *
050 */
051
052package org.jfree.chart.title;
053
054import java.awt.GradientPaint;
055import java.awt.Graphics2D;
056import java.awt.Paint;
057import java.awt.Shape;
058import java.awt.Stroke;
059import java.awt.geom.Point2D;
060import java.awt.geom.Rectangle2D;
061import java.io.IOException;
062import java.io.ObjectInputStream;
063import java.io.ObjectOutputStream;
064
065import org.jfree.chart.block.AbstractBlock;
066import org.jfree.chart.block.Block;
067import org.jfree.chart.block.LengthConstraintType;
068import org.jfree.chart.block.RectangleConstraint;
069import org.jfree.chart.util.ParamChecks;
070import org.jfree.io.SerialUtilities;
071import org.jfree.ui.GradientPaintTransformer;
072import org.jfree.ui.RectangleAnchor;
073import org.jfree.ui.Size2D;
074import org.jfree.ui.StandardGradientPaintTransformer;
075import org.jfree.util.ObjectUtilities;
076import org.jfree.util.PaintUtilities;
077import org.jfree.util.PublicCloneable;
078import org.jfree.util.ShapeUtilities;
079
080/**
081 * The graphical item within a legend item.
082 */
083public class LegendGraphic extends AbstractBlock
084                           implements Block, PublicCloneable {
085
086    /** For serialization. */
087    static final long serialVersionUID = -1338791523854985009L;
088
089    /**
090     * A flag that controls whether or not the shape is visible - see also
091     * lineVisible.
092     */
093    private boolean shapeVisible;
094
095    /**
096     * The shape to display.  To allow for accurate positioning, the center
097     * of the shape should be at (0, 0).
098     */
099    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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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
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     */
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
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
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
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
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
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     */
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     */
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}