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 }