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