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