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