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