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