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