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