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