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