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