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