    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    }