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 }