001/* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2013, 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 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. 
025 * Other names may be trademarks of their respective owners.]
026 *
027 * ----------------
028 * LegendTitle.java
029 * ----------------
030 * (C) Copyright 2002-2013, by Object Refinery Limited.
031 *
032 * Original Author:  David Gilbert (for Object Refinery Limited);
033 * Contributor(s):   Pierre-Marie Le Biot;
034 *
035 * Changes
036 * -------
037 * 25-Nov-2004 : First working version (DG);
038 * 11-Jan-2005 : Removed deprecated code in preparation for 1.0.0 release (DG);
039 * 08-Feb-2005 : Updated for changes in RectangleConstraint class (DG);
040 * 11-Feb-2005 : Implemented PublicCloneable (DG);
041 * 23-Feb-2005 : Replaced chart reference with LegendItemSource (DG);
042 * 16-Mar-2005 : Added itemFont attribute (DG);
043 * 17-Mar-2005 : Fixed missing fillShape setting (DG);
044 * 20-Apr-2005 : Added new draw() method (DG);
045 * 03-May-2005 : Modified equals() method to ignore sources (DG);
046 * 13-May-2005 : Added settings for legend item label and graphic padding (DG);
047 * 09-Jun-2005 : Fixed serialization bug (DG);
048 * 01-Sep-2005 : Added itemPaint attribute (PMLB);
049 * ------------- JFREECHART 1.0.x ---------------------------------------------
050 * 20-Jul-2006 : Use new LegendItemBlockContainer to restore support for
051 *               LegendItemEntities (DG);
052 * 06-Oct-2006 : Add tooltip and URL text to legend item (DG);
053 * 13-Dec-2006 : Added support for GradientPaint in legend items (DG);
054 * 16-Mar-2007 : Updated border drawing for changes in AbstractBlock (DG);
055 * 18-May-2007 : Pass seriesKey and dataset to legend item block (DG);
056 * 15-Aug-2008 : Added getWrapper() method (DG);
057 * 19-Mar-2009 : Added entity support - see patch 2603321 by Peter Kolb (DG);
058 * 11-Mar-2012 : Added sort-order support - patch 3500621 by Simon Kaczor (MH);
059 * 03-Jul-2013 : Use ParamChecks (DG);
060 * 
061 */
062
063package org.jfree.chart.title;
064
065import java.awt.Color;
066import java.awt.Font;
067import java.awt.Graphics2D;
068import java.awt.Paint;
069import java.awt.geom.Rectangle2D;
070import java.io.IOException;
071import java.io.ObjectInputStream;
072import java.io.ObjectOutputStream;
073import java.io.Serializable;
074
075import org.jfree.chart.LegendItem;
076import org.jfree.chart.LegendItemCollection;
077import org.jfree.chart.LegendItemSource;
078import org.jfree.chart.block.Arrangement;
079import org.jfree.chart.block.Block;
080import org.jfree.chart.block.BlockContainer;
081import org.jfree.chart.block.BlockFrame;
082import org.jfree.chart.block.BlockResult;
083import org.jfree.chart.block.BorderArrangement;
084import org.jfree.chart.block.CenterArrangement;
085import org.jfree.chart.block.ColumnArrangement;
086import org.jfree.chart.block.EntityBlockParams;
087import org.jfree.chart.block.FlowArrangement;
088import org.jfree.chart.block.LabelBlock;
089import org.jfree.chart.block.RectangleConstraint;
090import org.jfree.chart.entity.EntityCollection;
091import org.jfree.chart.entity.StandardEntityCollection;
092import org.jfree.chart.entity.TitleEntity;
093import org.jfree.chart.event.TitleChangeEvent;
094import org.jfree.chart.util.ParamChecks;
095import org.jfree.io.SerialUtilities;
096import org.jfree.ui.RectangleAnchor;
097import org.jfree.ui.RectangleEdge;
098import org.jfree.ui.RectangleInsets;
099import org.jfree.ui.Size2D;
100import org.jfree.util.PaintUtilities;
101import org.jfree.util.PublicCloneable;
102import org.jfree.util.SortOrder;
103
104/**
105 * A chart title that displays a legend for the data in the chart.
106 * <P>
107 * The title can be populated with legend items manually, or you can assign a
108 * reference to the plot, in which case the legend items will be automatically
109 * created to match the dataset(s).
110 */
111public class LegendTitle extends Title
112        implements Cloneable, PublicCloneable, Serializable {
113
114    /** For serialization. */
115    private static final long serialVersionUID = 2644010518533854633L;
116
117    /** The default item font. */
118    public static final Font DEFAULT_ITEM_FONT
119            = new Font("SansSerif", Font.PLAIN, 12);
120
121    /** The default item paint. */
122    public static final Paint DEFAULT_ITEM_PAINT = Color.black;
123
124    /** The sources for legend items. */
125    private LegendItemSource[] sources;
126
127    /** The background paint (possibly <code>null</code>). */
128    private transient Paint backgroundPaint;
129
130    /** The edge for the legend item graphic relative to the text. */
131    private RectangleEdge legendItemGraphicEdge;
132
133    /** The anchor point for the legend item graphic. */
134    private RectangleAnchor legendItemGraphicAnchor;
135
136    /** The legend item graphic location. */
137    private RectangleAnchor legendItemGraphicLocation;
138
139    /** The padding for the legend item graphic. */
140    private RectangleInsets legendItemGraphicPadding;
141
142    /** The item font. */
143    private Font itemFont;
144
145    /** The item paint. */
146    private transient Paint itemPaint;
147
148    /** The padding for the item labels. */
149    private RectangleInsets itemLabelPadding;
150
151    /**
152     * A container that holds and displays the legend items.
153     */
154    private BlockContainer items;
155
156    /**
157     * The layout for the legend when it is positioned at the top or bottom
158     * of the chart.
159     */
160    private Arrangement hLayout;
161
162    /**
163     * The layout for the legend when it is positioned at the left or right
164     * of the chart.
165     */
166    private Arrangement vLayout;
167
168    /**
169     * An optional container for wrapping the legend items (allows for adding
170     * a title or other text to the legend).
171     */
172    private BlockContainer wrapper;
173
174    /**
175     * Whether to render legend items in ascending or descending order.
176     * @since 1.0.15
177     */
178    private SortOrder sortOrder;
179
180    /**
181     * Constructs a new (empty) legend for the specified source.
182     *
183     * @param source  the source.
184     */
185    public LegendTitle(LegendItemSource source) {
186        this(source, new FlowArrangement(), new ColumnArrangement());
187    }
188
189    /**
190     * Creates a new legend title with the specified arrangement.
191     *
192     * @param source  the source.
193     * @param hLayout  the horizontal item arrangement (<code>null</code> not
194     *                 permitted).
195     * @param vLayout  the vertical item arrangement (<code>null</code> not
196     *                 permitted).
197     */
198    public LegendTitle(LegendItemSource source,
199                       Arrangement hLayout, Arrangement vLayout) {
200        this.sources = new LegendItemSource[] {source};
201        this.items = new BlockContainer(hLayout);
202        this.hLayout = hLayout;
203        this.vLayout = vLayout;
204        this.backgroundPaint = null;
205        this.legendItemGraphicEdge = RectangleEdge.LEFT;
206        this.legendItemGraphicAnchor = RectangleAnchor.CENTER;
207        this.legendItemGraphicLocation = RectangleAnchor.CENTER;
208        this.legendItemGraphicPadding = new RectangleInsets(2.0, 2.0, 2.0, 2.0);
209        this.itemFont = DEFAULT_ITEM_FONT;
210        this.itemPaint = DEFAULT_ITEM_PAINT;
211        this.itemLabelPadding = new RectangleInsets(2.0, 2.0, 2.0, 2.0);
212        this.sortOrder = SortOrder.ASCENDING;
213    }
214
215    /**
216     * Returns the legend item sources.
217     *
218     * @return The sources.
219     */
220    public LegendItemSource[] getSources() {
221        return this.sources;
222    }
223
224    /**
225     * Sets the legend item sources and sends a {@link TitleChangeEvent} to
226     * all registered listeners.
227     *
228     * @param sources  the sources (<code>null</code> not permitted).
229     */
230    public void setSources(LegendItemSource[] sources) {
231        ParamChecks.nullNotPermitted(sources, "sources");
232        this.sources = sources;
233        notifyListeners(new TitleChangeEvent(this));
234    }
235
236    /**
237     * Returns the background paint.
238     *
239     * @return The background paint (possibly <code>null</code>).
240     */
241    public Paint getBackgroundPaint() {
242        return this.backgroundPaint;
243    }
244
245    /**
246     * Sets the background paint for the legend and sends a
247     * {@link TitleChangeEvent} to all registered listeners.
248     *
249     * @param paint  the paint (<code>null</code> permitted).
250     */
251    public void setBackgroundPaint(Paint paint) {
252        this.backgroundPaint = paint;
253        notifyListeners(new TitleChangeEvent(this));
254    }
255
256    /**
257     * Returns the location of the shape within each legend item.
258     *
259     * @return The location (never <code>null</code>).
260     */
261    public RectangleEdge getLegendItemGraphicEdge() {
262        return this.legendItemGraphicEdge;
263    }
264
265    /**
266     * Sets the location of the shape within each legend item.
267     *
268     * @param edge  the edge (<code>null</code> not permitted).
269     */
270    public void setLegendItemGraphicEdge(RectangleEdge edge) {
271        ParamChecks.nullNotPermitted(edge, "edge");
272        this.legendItemGraphicEdge = edge;
273        notifyListeners(new TitleChangeEvent(this));
274    }
275
276    /**
277     * Returns the legend item graphic anchor.
278     *
279     * @return The graphic anchor (never <code>null</code>).
280     */
281    public RectangleAnchor getLegendItemGraphicAnchor() {
282        return this.legendItemGraphicAnchor;
283    }
284
285    /**
286     * Sets the anchor point used for the graphic in each legend item.
287     *
288     * @param anchor  the anchor point (<code>null</code> not permitted).
289     */
290    public void setLegendItemGraphicAnchor(RectangleAnchor anchor) {
291        ParamChecks.nullNotPermitted(anchor, "anchor");
292        this.legendItemGraphicAnchor = anchor;
293    }
294
295    /**
296     * Returns the legend item graphic location.
297     *
298     * @return The location (never <code>null</code>).
299     */
300    public RectangleAnchor getLegendItemGraphicLocation() {
301        return this.legendItemGraphicLocation;
302    }
303
304    /**
305     * Sets the legend item graphic location.
306     *
307     * @param anchor  the anchor (<code>null</code> not permitted).
308     */
309    public void setLegendItemGraphicLocation(RectangleAnchor anchor) {
310        this.legendItemGraphicLocation = anchor;
311    }
312
313    /**
314     * Returns the padding that will be applied to each item graphic.
315     *
316     * @return The padding (never <code>null</code>).
317     */
318    public RectangleInsets getLegendItemGraphicPadding() {
319        return this.legendItemGraphicPadding;
320    }
321
322    /**
323     * Sets the padding that will be applied to each item graphic in the
324     * legend and sends a {@link TitleChangeEvent} to all registered listeners.
325     *
326     * @param padding  the padding (<code>null</code> not permitted).
327     */
328    public void setLegendItemGraphicPadding(RectangleInsets padding) {
329        ParamChecks.nullNotPermitted(padding, "padding");
330        this.legendItemGraphicPadding = padding;
331        notifyListeners(new TitleChangeEvent(this));
332    }
333
334    /**
335     * Returns the item font.
336     *
337     * @return The font (never <code>null</code>).
338     */
339    public Font getItemFont() {
340        return this.itemFont;
341    }
342
343    /**
344     * Sets the item font and sends a {@link TitleChangeEvent} to
345     * all registered listeners.
346     *
347     * @param font  the font (<code>null</code> not permitted).
348     */
349    public void setItemFont(Font font) {
350        ParamChecks.nullNotPermitted(font, "font");
351        this.itemFont = font;
352        notifyListeners(new TitleChangeEvent(this));
353    }
354
355    /**
356     * Returns the item paint.
357     *
358     * @return The paint (never <code>null</code>).
359     */
360    public Paint getItemPaint() {
361        return this.itemPaint;
362    }
363
364    /**
365     * Sets the item paint.
366     *
367     * @param paint  the paint (<code>null</code> not permitted).
368     */
369    public void setItemPaint(Paint paint) {
370        ParamChecks.nullNotPermitted(paint, "paint");
371        this.itemPaint = paint;
372        notifyListeners(new TitleChangeEvent(this));
373    }
374
375    /**
376     * Returns the padding used for the items labels.
377     *
378     * @return The padding (never <code>null</code>).
379     */
380    public RectangleInsets getItemLabelPadding() {
381        return this.itemLabelPadding;
382    }
383
384    /**
385     * Sets the padding used for the item labels in the legend.
386     *
387     * @param padding  the padding (<code>null</code> not permitted).
388     */
389    public void setItemLabelPadding(RectangleInsets padding) {
390        ParamChecks.nullNotPermitted(padding, "padding");
391        this.itemLabelPadding = padding;
392        notifyListeners(new TitleChangeEvent(this));
393    }
394
395    /**
396     * Gets the order used to display legend items.
397     * 
398     * @return The order (never <code>null</code>).
399     * @since 1.0.15
400     */
401    public SortOrder getSortOrder() {
402        return this.sortOrder;
403    }
404
405    /**
406     * Sets the order used to display legend items.
407     * 
408     * @param order Specifies ascending or descending order (<code>null</code>
409     *              not permitted).
410     * @since 1.0.15
411     */
412    public void setSortOrder(SortOrder order) {
413        ParamChecks.nullNotPermitted(order, "order");
414        this.sortOrder = order;
415        notifyListeners(new TitleChangeEvent(this));
416    }
417
418    /**
419     * Fetches the latest legend items.
420     */
421    protected void fetchLegendItems() {
422        this.items.clear();
423        RectangleEdge p = getPosition();
424        if (RectangleEdge.isTopOrBottom(p)) {
425            this.items.setArrangement(this.hLayout);
426        }
427        else {
428            this.items.setArrangement(this.vLayout);
429        }
430
431        if (this.sortOrder.equals(SortOrder.ASCENDING)) {
432            for (int s = 0; s < this.sources.length; s++) {
433                LegendItemCollection legendItems =
434                    this.sources[s].getLegendItems();
435                if (legendItems != null) {
436                    for (int i = 0; i < legendItems.getItemCount(); i++) {
437                        addItemBlock(legendItems.get(i));
438                    }
439                }
440            }
441        }
442        else {
443            for (int s = this.sources.length - 1; s >= 0; s--) {
444                LegendItemCollection legendItems =
445                    this.sources[s].getLegendItems();
446                if (legendItems != null) {
447                    for (int i = legendItems.getItemCount()-1; i >= 0; i--) {
448                        addItemBlock(legendItems.get(i));
449                    }
450                }
451            }
452        }
453    }
454
455    private void addItemBlock(LegendItem item) {
456        Block block = createLegendItemBlock(item);
457        this.items.add(block);
458    }
459
460    /**
461     * Creates a legend item block.
462     *
463     * @param item  the legend item.
464     *
465     * @return The block.
466     */
467    protected Block createLegendItemBlock(LegendItem item) {
468        BlockContainer result;
469        LegendGraphic lg = new LegendGraphic(item.getShape(),
470                item.getFillPaint());
471        lg.setFillPaintTransformer(item.getFillPaintTransformer());
472        lg.setShapeFilled(item.isShapeFilled());
473        lg.setLine(item.getLine());
474        lg.setLineStroke(item.getLineStroke());
475        lg.setLinePaint(item.getLinePaint());
476        lg.setLineVisible(item.isLineVisible());
477        lg.setShapeVisible(item.isShapeVisible());
478        lg.setShapeOutlineVisible(item.isShapeOutlineVisible());
479        lg.setOutlinePaint(item.getOutlinePaint());
480        lg.setOutlineStroke(item.getOutlineStroke());
481        lg.setPadding(this.legendItemGraphicPadding);
482
483        LegendItemBlockContainer legendItem = new LegendItemBlockContainer(
484                new BorderArrangement(), item.getDataset(),
485                item.getSeriesKey());
486        lg.setShapeAnchor(getLegendItemGraphicAnchor());
487        lg.setShapeLocation(getLegendItemGraphicLocation());
488        legendItem.add(lg, this.legendItemGraphicEdge);
489        Font textFont = item.getLabelFont();
490        if (textFont == null) {
491            textFont = this.itemFont;
492        }
493        Paint textPaint = item.getLabelPaint();
494        if (textPaint == null) {
495            textPaint = this.itemPaint;
496        }
497        LabelBlock labelBlock = new LabelBlock(item.getLabel(), textFont,
498                textPaint);
499        labelBlock.setPadding(this.itemLabelPadding);
500        legendItem.add(labelBlock);
501        legendItem.setToolTipText(item.getToolTipText());
502        legendItem.setURLText(item.getURLText());
503
504        result = new BlockContainer(new CenterArrangement());
505        result.add(legendItem);
506
507        return result;
508    }
509
510    /**
511     * Returns the container that holds the legend items.
512     *
513     * @return The container for the legend items.
514     */
515    public BlockContainer getItemContainer() {
516        return this.items;
517    }
518
519    /**
520     * Arranges the contents of the block, within the given constraints, and
521     * returns the block size.
522     *
523     * @param g2  the graphics device.
524     * @param constraint  the constraint (<code>null</code> not permitted).
525     *
526     * @return The block size (in Java2D units, never <code>null</code>).
527     */
528    @Override
529    public Size2D arrange(Graphics2D g2, RectangleConstraint constraint) {
530        Size2D result = new Size2D();
531        fetchLegendItems();
532        if (this.items.isEmpty()) {
533            return result;
534        }
535        BlockContainer container = this.wrapper;
536        if (container == null) {
537            container = this.items;
538        }
539        RectangleConstraint c = toContentConstraint(constraint);
540        Size2D size = container.arrange(g2, c);
541        result.height = calculateTotalHeight(size.height);
542        result.width = calculateTotalWidth(size.width);
543        return result;
544    }
545
546    /**
547     * Draws the title on a Java 2D graphics device (such as the screen or a
548     * printer).
549     *
550     * @param g2  the graphics device.
551     * @param area  the available area for the title.
552     */
553    @Override
554    public void draw(Graphics2D g2, Rectangle2D area) {
555        draw(g2, area, null);
556    }
557
558    /**
559     * Draws the block within the specified area.
560     *
561     * @param g2  the graphics device.
562     * @param area  the area.
563     * @param params  ignored (<code>null</code> permitted).
564     *
565     * @return An {@link org.jfree.chart.block.EntityBlockResult} or
566     *         <code>null</code>.
567     */
568    @Override
569    public Object draw(Graphics2D g2, Rectangle2D area, Object params) {
570        Rectangle2D target = (Rectangle2D) area.clone();
571        Rectangle2D hotspot = (Rectangle2D) area.clone();
572        StandardEntityCollection sec = null;
573        if (params instanceof EntityBlockParams
574                && ((EntityBlockParams) params).getGenerateEntities()) {
575            sec = new StandardEntityCollection();
576            sec.add(new TitleEntity(hotspot, this));
577        }
578        target = trimMargin(target);
579        if (this.backgroundPaint != null) {
580            g2.setPaint(this.backgroundPaint);
581            g2.fill(target);
582        }
583        BlockFrame border = getFrame();
584        border.draw(g2, target);
585        border.getInsets().trim(target);
586        BlockContainer container = this.wrapper;
587        if (container == null) {
588            container = this.items;
589        }
590        target = trimPadding(target);
591        Object val = container.draw(g2, target, params);
592        if (val instanceof BlockResult) {
593            EntityCollection ec = ((BlockResult) val).getEntityCollection();
594            if (ec != null && sec != null) {
595                sec.addAll(ec);
596                ((BlockResult) val).setEntityCollection(sec);
597            }
598        }
599        return val;
600    }
601
602    /**
603     * Returns the wrapper container, if any.
604     *
605     * @return The wrapper container (possibly <code>null</code>).
606     *
607     * @since 1.0.11
608     */
609    public BlockContainer getWrapper() {
610        return this.wrapper;
611    }
612
613    /**
614     * Sets the wrapper container for the legend.
615     *
616     * @param wrapper  the wrapper container.
617     */
618    public void setWrapper(BlockContainer wrapper) {
619        this.wrapper = wrapper;
620    }
621
622    /**
623     * Tests this title for equality with an arbitrary object.
624     *
625     * @param obj  the object (<code>null</code> permitted).
626     *
627     * @return A boolean.
628     */
629    @Override
630    public boolean equals(Object obj) {
631        if (obj == this) {
632            return true;
633        }
634        if (!(obj instanceof LegendTitle)) {
635            return false;
636        }
637        if (!super.equals(obj)) {
638            return false;
639        }
640        LegendTitle that = (LegendTitle) obj;
641        if (!PaintUtilities.equal(this.backgroundPaint, that.backgroundPaint)) {
642            return false;
643        }
644        if (this.legendItemGraphicEdge != that.legendItemGraphicEdge) {
645            return false;
646        }
647        if (this.legendItemGraphicAnchor != that.legendItemGraphicAnchor) {
648            return false;
649        }
650        if (this.legendItemGraphicLocation != that.legendItemGraphicLocation) {
651            return false;
652        }
653        if (!this.itemFont.equals(that.itemFont)) {
654            return false;
655        }
656        if (!this.itemPaint.equals(that.itemPaint)) {
657            return false;
658        }
659        if (!this.hLayout.equals(that.hLayout)) {
660            return false;
661        }
662        if (!this.vLayout.equals(that.vLayout)) {
663            return false;
664        }
665        if (!this.sortOrder.equals(that.sortOrder)) {
666            return false;
667        }
668        return true;
669    }
670
671    /**
672     * Provides serialization support.
673     *
674     * @param stream  the output stream.
675     *
676     * @throws IOException  if there is an I/O error.
677     */
678    private void writeObject(ObjectOutputStream stream) throws IOException {
679        stream.defaultWriteObject();
680        SerialUtilities.writePaint(this.backgroundPaint, stream);
681        SerialUtilities.writePaint(this.itemPaint, stream);
682    }
683
684    /**
685     * Provides serialization support.
686     *
687     * @param stream  the input stream.
688     *
689     * @throws IOException  if there is an I/O error.
690     * @throws ClassNotFoundException  if there is a classpath problem.
691     */
692    private void readObject(ObjectInputStream stream)
693        throws IOException, ClassNotFoundException {
694        stream.defaultReadObject();
695        this.backgroundPaint = SerialUtilities.readPaint(stream);
696        this.itemPaint = SerialUtilities.readPaint(stream);
697    }
698
699}