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     * LegendTitle.java
029     * ----------------
030     * (C) Copyright 2002-2005, by Object Refinery Limited.
031     *
032     * Original Author:  David Gilbert (for Object Refinery Limited);
033     * Contributor(s):   Pierre-Marie Le Biot;
034     *
035     * $Id: LegendTitle.java,v 1.20.2.5 2005/10/25 20:58:34 mungady Exp $
036     *
037     * Changes
038     * -------
039     * 25-Nov-2004 : First working version (DG);
040     * 11-Jan-2005 : Removed deprecated code in preparation for 1.0.0 release (DG);
041     * 08-Feb-2005 : Updated for changes in RectangleConstraint class (DG);
042     * 11-Feb-2005 : Implemented PublicCloneable (DG);
043     * 23-Feb-2005 : Replaced chart reference with LegendItemSource (DG);
044     * 16-Mar-2005 : Added itemFont attribute (DG);
045     * 17-Mar-2005 : Fixed missing fillShape setting (DG);
046     * 20-Apr-2005 : Added new draw() method (DG);
047     * 03-May-2005 : Modified equals() method to ignore sources (DG);
048     * 13-May-2005 : Added settings for legend item label and graphic padding (DG);
049     * 09-Jun-2005 : Fixed serialization bug (DG);
050     * 01-Sep-2005 : Added itemPaint attribute (PMLB);
051     * 
052     */
053    
054    package org.jfree.chart.title;
055    
056    import java.awt.Color;
057    import java.awt.Font;
058    import java.awt.Graphics2D;
059    import java.awt.Paint;
060    import java.awt.geom.Rectangle2D;
061    import java.io.IOException;
062    import java.io.ObjectInputStream;
063    import java.io.ObjectOutputStream;
064    import java.io.Serializable;
065    
066    import org.jfree.chart.LegendItem;
067    import org.jfree.chart.LegendItemCollection;
068    import org.jfree.chart.LegendItemSource;
069    import org.jfree.chart.block.Arrangement;
070    import org.jfree.chart.block.Block;
071    import org.jfree.chart.block.BlockContainer;
072    import org.jfree.chart.block.BorderArrangement;
073    import org.jfree.chart.block.CenterArrangement;
074    import org.jfree.chart.block.ColumnArrangement;
075    import org.jfree.chart.block.FlowArrangement;
076    import org.jfree.chart.block.LabelBlock;
077    import org.jfree.chart.block.RectangleConstraint;
078    import org.jfree.chart.event.TitleChangeEvent;
079    import org.jfree.io.SerialUtilities;
080    import org.jfree.ui.RectangleAnchor;
081    import org.jfree.ui.RectangleEdge;
082    import org.jfree.ui.RectangleInsets;
083    import org.jfree.ui.Size2D;
084    import org.jfree.util.PaintUtilities;
085    import org.jfree.util.PublicCloneable;
086    
087    /**
088     * A chart title that displays a legend for the data in the chart.
089     * <P>
090     * The title can be populated with legend items manually, or you can assign a
091     * reference to the plot, in which case the legend items will be automatically
092     * created to match the dataset(s).
093     */
094    public class LegendTitle extends Title 
095                             implements Cloneable, PublicCloneable, Serializable {
096    
097        /** For serialization. */
098        private static final long serialVersionUID = 2644010518533854633L;
099        
100        /** The default item font. */
101        public static final Font DEFAULT_ITEM_FONT 
102            = new Font("SansSerif", Font.PLAIN, 12);
103    
104        /** The default item paint. */
105        public static final Paint DEFAULT_ITEM_PAINT = Color.black;
106    
107        /** The sources for legend items. */
108        private LegendItemSource[] sources;
109        
110        /** The background paint (possibly <code>null</code>). */
111        private transient Paint backgroundPaint;
112        
113        /** The edge for the legend item graphic relative to the text. */
114        private RectangleEdge legendItemGraphicEdge;
115        
116        /** The anchor point for the legend item graphic. */
117        private RectangleAnchor legendItemGraphicAnchor;
118        
119        /** The legend item graphic location. */
120        private RectangleAnchor legendItemGraphicLocation;
121        
122        /** The padding for the legend item graphic. */
123        private RectangleInsets legendItemGraphicPadding;
124    
125        /** The item font. */
126        private Font itemFont;
127        
128        /** The item paint. */
129        private transient Paint itemPaint;
130    
131        /** The padding for the item labels. */
132        private RectangleInsets itemLabelPadding;
133    
134        /**
135         * A container that holds and displays the legend items.
136         */
137        private BlockContainer items;
138        
139        private Arrangement hLayout;
140        
141        private Arrangement vLayout;
142        
143        /** 
144         * An optional container for wrapping the legend items (allows for adding
145         * a title or other text to the legend). 
146         */
147        private BlockContainer wrapper;
148    
149        /**
150         * Constructs a new (empty) legend for the specified source.
151         * 
152         * @param source  the source.
153         */
154        public LegendTitle(LegendItemSource source) {
155            this(source, new FlowArrangement(), new ColumnArrangement());
156        }
157        
158        /**
159         * Creates a new legend title with the specified arrangement.
160         * 
161         * @param source  the source.
162         * @param hLayout  the horizontal item arrangement (<code>null</code> not
163         *                 permitted).
164         * @param vLayout  the vertical item arrangement (<code>null</code> not
165         *                 permitted).
166         */
167        public LegendTitle(LegendItemSource source, 
168                           Arrangement hLayout, Arrangement vLayout) {
169            this.sources = new LegendItemSource[] {source};
170            this.items = new BlockContainer(hLayout);
171            this.hLayout = hLayout;
172            this.vLayout = vLayout;
173            this.backgroundPaint = null;  
174            this.legendItemGraphicEdge = RectangleEdge.LEFT;
175            this.legendItemGraphicAnchor = RectangleAnchor.CENTER;
176            this.legendItemGraphicLocation = RectangleAnchor.CENTER;
177            this.legendItemGraphicPadding = new RectangleInsets(2.0, 2.0, 2.0, 2.0);
178            this.itemFont = DEFAULT_ITEM_FONT;
179            this.itemPaint = DEFAULT_ITEM_PAINT;
180            this.itemLabelPadding = new RectangleInsets(2.0, 2.0, 2.0, 2.0);
181        }
182        
183        /**
184         * Returns the legend item sources.
185         * 
186         * @return The sources.
187         */
188        public LegendItemSource[] getSources() {
189            return this.sources;   
190        }
191        
192        /**
193         * Sets the legend item sources and sends a {@link TitleChangeEvent} to
194         * all registered listeners.
195         * 
196         * @param sources  the sources (<code>null</code> not permitted).
197         */
198        public void setSources(LegendItemSource[] sources) {
199            if (sources == null) {
200                throw new IllegalArgumentException("Null 'sources' argument.");   
201            }
202            this.sources = sources;
203            notifyListeners(new TitleChangeEvent(this));
204        }
205    
206        /**
207         * Returns the background paint.
208         * 
209         * @return The background paint (possibly <code>null</code>).
210         */
211        public Paint getBackgroundPaint() {
212            return this.backgroundPaint;   
213        }
214        
215        /**
216         * Sets the background paint for the legend and sends a 
217         * {@link TitleChangeEvent} to all registered listeners.
218         * 
219         * @param paint  the paint (<code>null</code> permitted).
220         */
221        public void setBackgroundPaint(Paint paint) {
222            this.backgroundPaint = paint;   
223            notifyListeners(new TitleChangeEvent(this));
224        }
225        
226        /**
227         * Returns the location of the shape within each legend item. 
228         * 
229         * @return The location (never <code>null</code>).
230         */
231        public RectangleEdge getLegendItemGraphicEdge() {
232            return this.legendItemGraphicEdge;
233        }
234        
235        /**
236         * Sets the location of the shape within each legend item.
237         * 
238         * @param edge  the edge (<code>null</code> not permitted).
239         */
240        public void setLegendItemGraphicEdge(RectangleEdge edge) {
241            if (edge == null) {
242                throw new IllegalArgumentException("Null 'edge' argument.");
243            }
244            this.legendItemGraphicEdge = edge;
245            notifyListeners(new TitleChangeEvent(this));
246        }
247        
248        /**
249         * Returns the legend item graphic anchor.
250         * 
251         * @return The graphic anchor (never <code>null</code>).
252         */
253        public RectangleAnchor getLegendItemGraphicAnchor() {
254            return this.legendItemGraphicAnchor;
255        }
256        
257        /**
258         * Sets the anchor point used for the graphic in each legend item.
259         * 
260         * @param anchor  the anchor point (<code>null</code> not permitted).
261         */
262        public void setLegendItemGraphicAnchor(RectangleAnchor anchor) {
263            if (anchor == null) {
264                throw new IllegalArgumentException("Null 'anchor' point.");
265            }
266            this.legendItemGraphicAnchor = anchor;
267        }
268        
269        /**
270         * Returns the legend item graphic location.
271         * 
272         * @return The location (never <code>null</code>).
273         */
274        public RectangleAnchor getLegendItemGraphicLocation() {
275            return this.legendItemGraphicLocation;
276        }
277        
278        /**
279         * Sets the legend item graphic location.
280         * 
281         * @param anchor  the anchor (<code>null</code> not permitted).
282         */
283        public void setLegendItemGraphicLocation(RectangleAnchor anchor) {
284            this.legendItemGraphicLocation = anchor;
285        }
286        
287        /**
288         * Returns the padding that will be applied to each item graphic.
289         * 
290         * @return The padding (never <code>null</code>).
291         */
292        public RectangleInsets getLegendItemGraphicPadding() {
293            return this.legendItemGraphicPadding;    
294        }
295        
296        /**
297         * Sets the padding that will be applied to each item graphic in the 
298         * legend and sends a {@link TitleChangeEvent} to all registered listeners.
299         * 
300         * @param padding  the padding (<code>null</code> not permitted).
301         */
302        public void setLegendItemGraphicPadding(RectangleInsets padding) {
303            if (padding == null) {
304                throw new IllegalArgumentException("Null 'padding' argument.");   
305            }
306            this.legendItemGraphicPadding = padding;
307            notifyListeners(new TitleChangeEvent(this));
308        }
309        
310        /**
311         * Returns the item font.
312         * 
313         * @return The font (never <code>null</code>).
314         */
315        public Font getItemFont() {
316            return this.itemFont;   
317        }
318        
319        /**
320         * Sets the item font and sends a {@link TitleChangeEvent} to
321         * all registered listeners.
322         * 
323         * @param font  the font (<code>null</code> not permitted).
324         */
325        public void setItemFont(Font font) {
326            if (font == null) {
327                throw new IllegalArgumentException("Null 'font' argument.");   
328            }
329            this.itemFont = font;
330            notifyListeners(new TitleChangeEvent(this));
331        }
332        
333        /**
334         * Returns the item paint.
335         *
336         * @return The paint (never <code>null</code>).
337         */
338        public Paint getItemPaint() {
339            return this.itemPaint;   
340        }
341       
342        /**
343         * Sets the item paint.
344         *
345         * @param paint  the paint (<code>null</code> not permitted).
346         */
347        public void setItemPaint(Paint paint) {
348            if (paint == null) {
349                throw new IllegalArgumentException("Null 'paint' argument.");   
350            }
351            this.itemPaint = paint;
352            notifyListeners(new TitleChangeEvent(this));
353        }
354       
355        /**
356         * Returns the padding used for the items labels.
357         * 
358         * @return The padding (never <code>null</code>).
359         */
360        public RectangleInsets getItemLabelPadding() {
361            return this.itemLabelPadding;   
362        }
363        
364        /**
365         * Sets the padding used for the item labels in the legend.
366         * 
367         * @param padding  the padding (<code>null</code> not permitted).
368         */
369        public void setItemLabelPadding(RectangleInsets padding) {
370            if (padding == null) {
371                throw new IllegalArgumentException("Null 'padding' argument.");   
372            }
373            this.itemLabelPadding = padding;
374            notifyListeners(new TitleChangeEvent(this));
375        }
376        
377        /**
378         * Fetches the latest legend items.
379         */
380        protected void fetchLegendItems() {
381            this.items.clear();
382            RectangleEdge p = getPosition();
383            if (RectangleEdge.isTopOrBottom(p)) {
384                this.items.setArrangement(this.hLayout);   
385            }
386            else {
387                this.items.setArrangement(this.vLayout);   
388            }
389            for (int s = 0; s < this.sources.length; s++) {
390                LegendItemCollection legendItems = this.sources[s].getLegendItems();
391                if (legendItems != null) {
392                    for (int i = 0; i < legendItems.getItemCount(); i++) {
393                        LegendItem item = legendItems.get(i);
394                        Block block = createLegendItemBlock(item);
395                        this.items.add(block);
396                    }
397                }
398            }
399        }
400        
401        /**
402         * Creates a legend item block.
403         * 
404         * @param item  the legend item.
405         * 
406         * @return The block.
407         */
408        protected Block createLegendItemBlock(LegendItem item) {
409            BlockContainer result = null;
410            LegendGraphic lg = new LegendGraphic(
411                item.getShape(), item.getFillPaint()
412            );
413            lg.setShapeFilled(item.isShapeFilled());
414            lg.setLine(item.getLine());
415            lg.setLineStroke(item.getLineStroke());
416            lg.setLinePaint(item.getLinePaint());
417            lg.setLineVisible(item.isLineVisible());
418            lg.setShapeVisible(item.isShapeVisible());
419            lg.setShapeOutlineVisible(item.isShapeOutlineVisible());
420            lg.setOutlinePaint(item.getOutlinePaint());
421            lg.setOutlineStroke(item.getOutlineStroke());
422            lg.setPadding(this.legendItemGraphicPadding);
423    
424            BlockContainer legendItem = new BlockContainer(new BorderArrangement());
425            lg.setShapeAnchor(getLegendItemGraphicAnchor());
426            lg.setShapeLocation(getLegendItemGraphicLocation());
427            legendItem.add(lg, this.legendItemGraphicEdge);
428            LabelBlock labelBlock = new LabelBlock(item.getLabel(), this.itemFont, 
429                    this.itemPaint);
430            labelBlock.setPadding(this.itemLabelPadding);
431            labelBlock.setToolTipText(item.getToolTipText());
432            legendItem.add(labelBlock);
433            
434            result = new BlockContainer(new CenterArrangement());
435            result.add(legendItem);
436            
437            return result;
438        }
439        
440        /**
441         * Returns the container that holds the legend items.
442         * 
443         * @return The container for the legend items.
444         */
445        public BlockContainer getItemContainer() {
446            return this.items;
447        }
448    
449        /**
450         * Arranges the contents of the block, within the given constraints, and 
451         * returns the block size.
452         * 
453         * @param g2  the graphics device.
454         * @param constraint  the constraint (<code>null</code> not permitted).
455         * 
456         * @return The block size (in Java2D units, never <code>null</code>).
457         */
458        public Size2D arrange(Graphics2D g2, RectangleConstraint constraint) {
459            Size2D result = new Size2D();
460            fetchLegendItems();
461            if (this.items.isEmpty()) {
462                return result;   
463            }
464            BlockContainer container = this.wrapper;
465            if (container == null) {
466                container = this.items;
467            }
468            RectangleConstraint c = toContentConstraint(constraint);
469            Size2D size = container.arrange(g2, c);
470            result.height = calculateTotalHeight(size.height);
471            result.width = calculateTotalWidth(size.width);
472            return result;
473        }
474    
475        /**
476         * Draws the title on a Java 2D graphics device (such as the screen or a
477         * printer).
478         *
479         * @param g2  the graphics device.
480         * @param area  the available area for the title.
481         */
482        public void draw(Graphics2D g2, Rectangle2D area) {
483            draw(g2, area, null);
484        }
485    
486        /**
487         * Draws the block within the specified area.
488         * 
489         * @param g2  the graphics device.
490         * @param area  the area.
491         * @param params  ignored (<code>null</code> permitted).
492         * 
493         * @return An {@link org.jfree.chart.block.EntityBlockResult} or 
494         *         <code>null</code>.
495         */
496        public Object draw(Graphics2D g2, Rectangle2D area, Object params) {
497            Rectangle2D target = (Rectangle2D) area.clone();
498            target = trimMargin(target);
499            if (this.backgroundPaint != null) {
500                g2.setPaint(this.backgroundPaint);
501                g2.fill(target);
502            }
503            getBorder().draw(g2, target);
504            getBorder().getInsets().trim(target);
505            BlockContainer container = this.wrapper;
506            if (container == null) {
507                container = this.items; 
508            }
509            target = trimPadding(target);
510            return container.draw(g2, target, params);   
511        }
512    
513        /**
514         * Sets the wrapper container for the legend.
515         * 
516         * @param wrapper  the wrapper container.
517         */
518        public void setWrapper(BlockContainer wrapper) {
519            this.wrapper = wrapper;
520        }
521        
522        /**
523         * Tests this title for equality with an arbitrary object.
524         * 
525         * @param obj  the object (<code>null</code> permitted).
526         * 
527         * @return A boolean.
528         */
529        public boolean equals(Object obj) {
530            if (obj == this) {
531                return true;   
532            }
533            if (!(obj instanceof LegendTitle)) {
534                return false;   
535            }
536            if (!super.equals(obj)) {
537                return false;   
538            }
539            LegendTitle that = (LegendTitle) obj;
540            if (!PaintUtilities.equal(this.backgroundPaint, that.backgroundPaint)) {
541                return false;   
542            }
543            if (this.legendItemGraphicEdge != that.legendItemGraphicEdge) {
544                return false;   
545            }
546            if (this.legendItemGraphicAnchor != that.legendItemGraphicAnchor) {
547                return false;   
548            }
549            if (this.legendItemGraphicLocation != that.legendItemGraphicLocation) {
550                return false;   
551            }
552            if (!this.itemFont.equals(that.itemFont)) {
553                return false;   
554            }
555            if (!this.itemPaint.equals(that.itemPaint)) {
556                return false;   
557            }
558            if (!this.hLayout.equals(that.hLayout)) {
559                return false;   
560            }
561            if (!this.vLayout.equals(that.vLayout)) {
562                return false;   
563            }
564            return true;
565        }
566        
567        /**
568         * Provides serialization support.
569         *
570         * @param stream  the output stream.
571         *
572         * @throws IOException  if there is an I/O error.
573         */
574        private void writeObject(ObjectOutputStream stream) throws IOException {
575            stream.defaultWriteObject();
576            SerialUtilities.writePaint(this.backgroundPaint, stream);
577            SerialUtilities.writePaint(this.itemPaint, stream);
578        }
579    
580        /**
581         * Provides serialization support.
582         *
583         * @param stream  the input stream.
584         *
585         * @throws IOException  if there is an I/O error.
586         * @throws ClassNotFoundException  if there is a classpath problem.
587         */
588        private void readObject(ObjectInputStream stream) 
589            throws IOException, ClassNotFoundException {
590            stream.defaultReadObject();
591            this.backgroundPaint = SerialUtilities.readPaint(stream);
592            this.itemPaint = SerialUtilities.readPaint(stream);
593        }
594    
595    }