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