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
059package org.jfree.chart.title;
060
061import java.awt.Color;
062import java.awt.Font;
063import java.awt.Graphics2D;
064import java.awt.Paint;
065import java.awt.geom.Rectangle2D;
066import java.io.IOException;
067import java.io.ObjectInputStream;
068import java.io.ObjectOutputStream;
069import java.io.Serializable;
070
071import org.jfree.chart.LegendItem;
072import org.jfree.chart.LegendItemCollection;
073import org.jfree.chart.LegendItemSource;
074import org.jfree.chart.block.Arrangement;
075import org.jfree.chart.block.Block;
076import org.jfree.chart.block.BlockContainer;
077import org.jfree.chart.block.BlockFrame;
078import org.jfree.chart.block.BorderArrangement;
079import org.jfree.chart.block.CenterArrangement;
080import org.jfree.chart.block.ColumnArrangement;
081import org.jfree.chart.block.FlowArrangement;
082import org.jfree.chart.block.LabelBlock;
083import org.jfree.chart.block.RectangleConstraint;
084import org.jfree.chart.event.TitleChangeEvent;
085import org.jfree.io.SerialUtilities;
086import org.jfree.ui.RectangleAnchor;
087import org.jfree.ui.RectangleEdge;
088import org.jfree.ui.RectangleInsets;
089import org.jfree.ui.Size2D;
090import org.jfree.util.PaintUtilities;
091import 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 */
100public 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}