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