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