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