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 }