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