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