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