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