001/* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-present, by David Gilbert 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 * LegendGraphic.java 029 * ------------------ 030 * (C) Copyright 2004-present, by David Gilbert. 031 * 032 * Original Author: David Gilbert; 033 * Contributor(s): Tracy Hiltbrand (equals/hashCode comply with EqualsVerifier); 034 * 035 */ 036 037package org.jfree.chart.title; 038 039import java.awt.GradientPaint; 040import java.awt.Graphics2D; 041import java.awt.Paint; 042import java.awt.Shape; 043import java.awt.Stroke; 044import java.awt.geom.Point2D; 045import java.awt.geom.Rectangle2D; 046import java.io.IOException; 047import java.io.ObjectInputStream; 048import java.io.ObjectOutputStream; 049import java.util.Objects; 050 051import org.jfree.chart.block.AbstractBlock; 052import org.jfree.chart.block.Block; 053import org.jfree.chart.block.LengthConstraintType; 054import org.jfree.chart.block.RectangleConstraint; 055import org.jfree.chart.ui.GradientPaintTransformer; 056import org.jfree.chart.ui.RectangleAnchor; 057import org.jfree.chart.ui.Size2D; 058import org.jfree.chart.ui.StandardGradientPaintTransformer; 059import org.jfree.chart.util.PaintUtils; 060import org.jfree.chart.util.Args; 061import org.jfree.chart.util.PublicCloneable; 062import org.jfree.chart.util.SerialUtils; 063import org.jfree.chart.util.ShapeUtils; 064 065/** 066 * The graphical item within a legend item. 067 */ 068public class LegendGraphic extends AbstractBlock 069 implements Block, PublicCloneable { 070 071 /** For serialization. */ 072 static final long serialVersionUID = -1338791523854985009L; 073 074 /** 075 * A flag that controls whether or not the shape is visible - see also 076 * lineVisible. 077 */ 078 private boolean shapeVisible; 079 080 /** 081 * The shape to display. To allow for accurate positioning, the center 082 * of the shape should be at (0, 0). 083 */ 084 private transient Shape shape; 085 086 /** 087 * Defines the location within the block to which the shape will be aligned. 088 */ 089 private RectangleAnchor shapeLocation; 090 091 /** 092 * Defines the point on the shape's bounding rectangle that will be 093 * aligned to the drawing location when the shape is rendered. 094 */ 095 private RectangleAnchor shapeAnchor; 096 097 /** A flag that controls whether or not the shape is filled. */ 098 private boolean shapeFilled; 099 100 /** The fill paint for the shape. */ 101 private transient Paint fillPaint; 102 103 /** 104 * The fill paint transformer (used if the fillPaint is an instance of 105 * GradientPaint). 106 */ 107 private GradientPaintTransformer fillPaintTransformer; 108 109 /** A flag that controls whether or not the shape outline is visible. */ 110 private boolean shapeOutlineVisible; 111 112 /** The outline paint for the shape. */ 113 private transient Paint outlinePaint; 114 115 /** The outline stroke for the shape. */ 116 private transient Stroke outlineStroke; 117 118 /** 119 * A flag that controls whether or not the line is visible - see also 120 * shapeVisible. 121 */ 122 private boolean lineVisible; 123 124 /** The line. */ 125 private transient Shape line; 126 127 /** The line stroke. */ 128 private transient Stroke lineStroke; 129 130 /** The line paint. */ 131 private transient Paint linePaint; 132 133 /** 134 * Creates a new legend graphic. 135 * 136 * @param shape the shape ({@code null} not permitted). 137 * @param fillPaint the fill paint ({@code null} not permitted). 138 */ 139 public LegendGraphic(Shape shape, Paint fillPaint) { 140 Args.nullNotPermitted(shape, "shape"); 141 Args.nullNotPermitted(fillPaint, "fillPaint"); 142 this.shapeVisible = true; 143 this.shape = shape; 144 this.shapeAnchor = RectangleAnchor.CENTER; 145 this.shapeLocation = RectangleAnchor.CENTER; 146 this.shapeFilled = true; 147 this.fillPaint = fillPaint; 148 this.fillPaintTransformer = new StandardGradientPaintTransformer(); 149 setPadding(2.0, 2.0, 2.0, 2.0); 150 } 151 152 /** 153 * Returns a flag that controls whether or not the shape 154 * is visible. 155 * 156 * @return A boolean. 157 * 158 * @see #setShapeVisible(boolean) 159 */ 160 public boolean isShapeVisible() { 161 return this.shapeVisible; 162 } 163 164 /** 165 * Sets a flag that controls whether or not the shape is 166 * visible. 167 * 168 * @param visible the flag. 169 * 170 * @see #isShapeVisible() 171 */ 172 public void setShapeVisible(boolean visible) { 173 this.shapeVisible = visible; 174 } 175 176 /** 177 * Returns the shape. 178 * 179 * @return The shape. 180 * 181 * @see #setShape(Shape) 182 */ 183 public Shape getShape() { 184 return this.shape; 185 } 186 187 /** 188 * Sets the shape. 189 * 190 * @param shape the shape. 191 * 192 * @see #getShape() 193 */ 194 public void setShape(Shape shape) { 195 this.shape = shape; 196 } 197 198 /** 199 * Returns a flag that controls whether or not the shapes 200 * are filled. 201 * 202 * @return A boolean. 203 * 204 * @see #setShapeFilled(boolean) 205 */ 206 public boolean isShapeFilled() { 207 return this.shapeFilled; 208 } 209 210 /** 211 * Sets a flag that controls whether or not the shape is 212 * filled. 213 * 214 * @param filled the flag. 215 * 216 * @see #isShapeFilled() 217 */ 218 public void setShapeFilled(boolean filled) { 219 this.shapeFilled = filled; 220 } 221 222 /** 223 * Returns the paint used to fill the shape. 224 * 225 * @return The fill paint. 226 * 227 * @see #setFillPaint(Paint) 228 */ 229 public Paint getFillPaint() { 230 return this.fillPaint; 231 } 232 233 /** 234 * Sets the paint used to fill the shape. 235 * 236 * @param paint the paint. 237 * 238 * @see #getFillPaint() 239 */ 240 public void setFillPaint(Paint paint) { 241 this.fillPaint = paint; 242 } 243 244 /** 245 * Returns the transformer used when the fill paint is an instance of 246 * {@code GradientPaint}. 247 * 248 * @return The transformer (never {@code null}). 249 * 250 * @see #setFillPaintTransformer(GradientPaintTransformer) 251 */ 252 public GradientPaintTransformer getFillPaintTransformer() { 253 return this.fillPaintTransformer; 254 } 255 256 /** 257 * Sets the transformer used when the fill paint is an instance of 258 * {@code GradientPaint}. 259 * 260 * @param transformer the transformer ({@code null} not permitted). 261 * 262 * @see #getFillPaintTransformer() 263 */ 264 public void setFillPaintTransformer(GradientPaintTransformer transformer) { 265 Args.nullNotPermitted(transformer, "transformer"); 266 this.fillPaintTransformer = transformer; 267 } 268 269 /** 270 * Returns a flag that controls whether the shape outline is visible. 271 * 272 * @return A boolean. 273 * 274 * @see #setShapeOutlineVisible(boolean) 275 */ 276 public boolean isShapeOutlineVisible() { 277 return this.shapeOutlineVisible; 278 } 279 280 /** 281 * Sets a flag that controls whether or not the shape outline 282 * is visible. 283 * 284 * @param visible the flag. 285 * 286 * @see #isShapeOutlineVisible() 287 */ 288 public void setShapeOutlineVisible(boolean visible) { 289 this.shapeOutlineVisible = visible; 290 } 291 292 /** 293 * Returns the outline paint. 294 * 295 * @return The paint. 296 * 297 * @see #setOutlinePaint(Paint) 298 */ 299 public Paint getOutlinePaint() { 300 return this.outlinePaint; 301 } 302 303 /** 304 * Sets the outline paint. 305 * 306 * @param paint the paint. 307 * 308 * @see #getOutlinePaint() 309 */ 310 public void setOutlinePaint(Paint paint) { 311 this.outlinePaint = paint; 312 } 313 314 /** 315 * Returns the outline stroke. 316 * 317 * @return The stroke. 318 * 319 * @see #setOutlineStroke(Stroke) 320 */ 321 public Stroke getOutlineStroke() { 322 return this.outlineStroke; 323 } 324 325 /** 326 * Sets the outline stroke. 327 * 328 * @param stroke the stroke. 329 * 330 * @see #getOutlineStroke() 331 */ 332 public void setOutlineStroke(Stroke stroke) { 333 this.outlineStroke = stroke; 334 } 335 336 /** 337 * Returns the shape anchor. 338 * 339 * @return The shape anchor. 340 * 341 * @see #getShapeAnchor() 342 */ 343 public RectangleAnchor getShapeAnchor() { 344 return this.shapeAnchor; 345 } 346 347 /** 348 * Sets the shape anchor. This defines a point on the shapes bounding 349 * rectangle that will be used to align the shape to a location. 350 * 351 * @param anchor the anchor ({@code null} not permitted). 352 * 353 * @see #setShapeAnchor(RectangleAnchor) 354 */ 355 public void setShapeAnchor(RectangleAnchor anchor) { 356 Args.nullNotPermitted(anchor, "anchor"); 357 this.shapeAnchor = anchor; 358 } 359 360 /** 361 * Returns the shape location. 362 * 363 * @return The shape location. 364 * 365 * @see #setShapeLocation(RectangleAnchor) 366 */ 367 public RectangleAnchor getShapeLocation() { 368 return this.shapeLocation; 369 } 370 371 /** 372 * Sets the shape location. This defines a point within the drawing 373 * area that will be used to align the shape to. 374 * 375 * @param location the location ({@code null} not permitted). 376 * 377 * @see #getShapeLocation() 378 */ 379 public void setShapeLocation(RectangleAnchor location) { 380 Args.nullNotPermitted(location, "location"); 381 this.shapeLocation = location; 382 } 383 384 /** 385 * Returns the flag that controls whether or not the line is visible. 386 * 387 * @return A boolean. 388 * 389 * @see #setLineVisible(boolean) 390 */ 391 public boolean isLineVisible() { 392 return this.lineVisible; 393 } 394 395 /** 396 * Sets the flag that controls whether or not the line is visible. 397 * 398 * @param visible the flag. 399 * 400 * @see #isLineVisible() 401 */ 402 public void setLineVisible(boolean visible) { 403 this.lineVisible = visible; 404 } 405 406 /** 407 * Returns the line centered about (0, 0). 408 * 409 * @return The line. 410 * 411 * @see #setLine(Shape) 412 */ 413 public Shape getLine() { 414 return this.line; 415 } 416 417 /** 418 * Sets the line. A Shape is used here, because then you can use Line2D, 419 * GeneralPath or any other Shape to represent the line. 420 * 421 * @param line the line. 422 * 423 * @see #getLine() 424 */ 425 public void setLine(Shape line) { 426 this.line = line; 427 } 428 429 /** 430 * Returns the line paint. 431 * 432 * @return The paint. 433 * 434 * @see #setLinePaint(Paint) 435 */ 436 public Paint getLinePaint() { 437 return this.linePaint; 438 } 439 440 /** 441 * Sets the line paint. 442 * 443 * @param paint the paint. 444 * 445 * @see #getLinePaint() 446 */ 447 public void setLinePaint(Paint paint) { 448 this.linePaint = paint; 449 } 450 451 /** 452 * Returns the line stroke. 453 * 454 * @return The stroke. 455 * 456 * @see #setLineStroke(Stroke) 457 */ 458 public Stroke getLineStroke() { 459 return this.lineStroke; 460 } 461 462 /** 463 * Sets the line stroke. 464 * 465 * @param stroke the stroke. 466 * 467 * @see #getLineStroke() 468 */ 469 public void setLineStroke(Stroke stroke) { 470 this.lineStroke = stroke; 471 } 472 473 /** 474 * Arranges the contents of the block, within the given constraints, and 475 * returns the block size. 476 * 477 * @param g2 the graphics device. 478 * @param constraint the constraint ({@code null} not permitted). 479 * 480 * @return The block size (in Java2D units, never {@code null}). 481 */ 482 @Override 483 public Size2D arrange(Graphics2D g2, RectangleConstraint constraint) { 484 RectangleConstraint contentConstraint = toContentConstraint(constraint); 485 LengthConstraintType w = contentConstraint.getWidthConstraintType(); 486 LengthConstraintType h = contentConstraint.getHeightConstraintType(); 487 Size2D contentSize = null; 488 if (w == LengthConstraintType.NONE) { 489 if (h == LengthConstraintType.NONE) { 490 contentSize = arrangeNN(g2); 491 } 492 else if (h == LengthConstraintType.RANGE) { 493 throw new RuntimeException("Not yet implemented."); 494 } 495 else if (h == LengthConstraintType.FIXED) { 496 throw new RuntimeException("Not yet implemented."); 497 } 498 } 499 else if (w == LengthConstraintType.RANGE) { 500 if (h == LengthConstraintType.NONE) { 501 throw new RuntimeException("Not yet implemented."); 502 } 503 else if (h == LengthConstraintType.RANGE) { 504 throw new RuntimeException("Not yet implemented."); 505 } 506 else if (h == LengthConstraintType.FIXED) { 507 throw new RuntimeException("Not yet implemented."); 508 } 509 } 510 else if (w == LengthConstraintType.FIXED) { 511 if (h == LengthConstraintType.NONE) { 512 throw new RuntimeException("Not yet implemented."); 513 } 514 else if (h == LengthConstraintType.RANGE) { 515 throw new RuntimeException("Not yet implemented."); 516 } 517 else if (h == LengthConstraintType.FIXED) { 518 contentSize = new Size2D(contentConstraint.getWidth(), 519 contentConstraint.getHeight()); 520 } 521 } 522 assert contentSize != null; 523 return new Size2D(calculateTotalWidth(contentSize.getWidth()), 524 calculateTotalHeight(contentSize.getHeight())); 525 } 526 527 /** 528 * Performs the layout with no constraint, so the content size is 529 * determined by the bounds of the shape and/or line drawn to represent 530 * the series. 531 * 532 * @param g2 the graphics device. 533 * 534 * @return The content size. 535 */ 536 protected Size2D arrangeNN(Graphics2D g2) { 537 Rectangle2D contentSize = new Rectangle2D.Double(); 538 if (this.line != null) { 539 contentSize.setRect(this.line.getBounds2D()); 540 } 541 if (this.shape != null) { 542 contentSize = contentSize.createUnion(this.shape.getBounds2D()); 543 } 544 return new Size2D(contentSize.getWidth(), contentSize.getHeight()); 545 } 546 547 /** 548 * Draws the graphic item within the specified area. 549 * 550 * @param g2 the graphics device. 551 * @param area the area. 552 */ 553 @Override 554 public void draw(Graphics2D g2, Rectangle2D area) { 555 556 area = trimMargin(area); 557 drawBorder(g2, area); 558 area = trimBorder(area); 559 area = trimPadding(area); 560 561 if (this.lineVisible) { 562 Point2D location = this.shapeLocation.getAnchorPoint(area); 563 Shape aLine = ShapeUtils.createTranslatedShape(getLine(), 564 this.shapeAnchor, location.getX(), location.getY()); 565 g2.setPaint(this.linePaint); 566 g2.setStroke(this.lineStroke); 567 g2.draw(aLine); 568 } 569 570 if (this.shapeVisible) { 571 Point2D location = this.shapeLocation.getAnchorPoint(area); 572 573 Shape s = ShapeUtils.createTranslatedShape(this.shape, 574 this.shapeAnchor, location.getX(), location.getY()); 575 if (this.shapeFilled) { 576 Paint p = this.fillPaint; 577 if (p instanceof GradientPaint) { 578 GradientPaint gp = (GradientPaint) this.fillPaint; 579 p = this.fillPaintTransformer.transform(gp, s); 580 } 581 g2.setPaint(p); 582 g2.fill(s); 583 } 584 if (this.shapeOutlineVisible) { 585 g2.setPaint(this.outlinePaint); 586 g2.setStroke(this.outlineStroke); 587 g2.draw(s); 588 } 589 } 590 } 591 592 /** 593 * Draws the block within the specified area. 594 * 595 * @param g2 the graphics device. 596 * @param area the area. 597 * @param params ignored ({@code null} permitted). 598 * 599 * @return Always {@code null}. 600 */ 601 @Override 602 public Object draw(Graphics2D g2, Rectangle2D area, Object params) { 603 draw(g2, area); 604 return null; 605 } 606 607 /** 608 * Tests this {@code LegendGraphic} instance for equality with an 609 * arbitrary object. 610 * 611 * @param obj the object ({@code null} permitted). 612 * 613 * @return A boolean. 614 */ 615 @Override 616 public boolean equals(Object obj) { 617 if (obj == this) { 618 return true; 619 } 620 if (!(obj instanceof LegendGraphic)) { 621 return false; 622 } 623 LegendGraphic that = (LegendGraphic) obj; 624 if (this.shapeVisible != that.shapeVisible) { 625 return false; 626 } 627 if (!ShapeUtils.equal(this.shape, that.shape)) { 628 return false; 629 } 630 if (!PaintUtils.equal(this.fillPaint, that.fillPaint)) { 631 return false; 632 } 633 if (this.shapeFilled != that.shapeFilled) { 634 return false; 635 } 636 if (!Objects.equals(this.fillPaintTransformer, that.fillPaintTransformer)) { 637 return false; 638 } 639 if (this.shapeOutlineVisible != that.shapeOutlineVisible) { 640 return false; 641 } 642 if (!PaintUtils.equal(this.outlinePaint, that.outlinePaint)) { 643 return false; 644 } 645 if (!Objects.equals(this.outlineStroke, that.outlineStroke)) { 646 return false; 647 } 648 if (this.shapeAnchor != that.shapeAnchor) { 649 return false; 650 } 651 if (this.shapeLocation != that.shapeLocation) { 652 return false; 653 } 654 if (this.lineVisible != that.lineVisible) { 655 return false; 656 } 657 if (!ShapeUtils.equal(this.line, that.line)) { 658 return false; 659 } 660 if (!PaintUtils.equal(this.linePaint, that.linePaint)) { 661 return false; 662 } 663 if (!Objects.equals(this.lineStroke, that.lineStroke)) { 664 return false; 665 } 666 if (!that.canEqual(this)) { 667 return false; 668 } 669 return super.equals(obj); 670 } 671 672 /** 673 * Ensures symmetry between super/subclass implementations of equals. For 674 * more detail, see http://jqno.nl/equalsverifier/manual/inheritance. 675 * 676 * @param other Object 677 * 678 * @return true ONLY if the parameter is THIS class type 679 */ 680 @Override 681 public boolean canEqual(Object other) { 682 // fix the "equals not symmetric" problem 683 return (other instanceof LegendGraphic); 684 } 685 686 /** 687 * Returns a hash code for this instance. 688 * 689 * @return A hash code. 690 */ 691 @Override 692 public int hashCode() { 693 int hash = super.hashCode(); // equals calls superclass, hashCode must also 694 hash = 23 * hash + (this.shapeVisible ? 1 : 0); 695 hash = 23 * hash + Objects.hashCode(this.shape); 696 hash = 23 * hash + Objects.hashCode(this.shapeLocation); 697 hash = 23 * hash + Objects.hashCode(this.shapeAnchor); 698 hash = 23 * hash + (this.shapeFilled ? 1 : 0); 699 hash = 23 * hash + Objects.hashCode(this.fillPaint); 700 hash = 23 * hash + Objects.hashCode(this.fillPaintTransformer); 701 hash = 23 * hash + (this.shapeOutlineVisible ? 1 : 0); 702 hash = 23 * hash + Objects.hashCode(this.outlinePaint); 703 hash = 23 * hash + Objects.hashCode(this.outlineStroke); 704 hash = 23 * hash + (this.lineVisible ? 1 : 0); 705 hash = 23 * hash + Objects.hashCode(this.line); 706 hash = 23 * hash + Objects.hashCode(this.lineStroke); 707 hash = 23 * hash + Objects.hashCode(this.linePaint); 708 return hash; 709 } 710 711 /** 712 * Returns a clone of this {@code LegendGraphic} instance. 713 * 714 * @return A clone of this {@code LegendGraphic} instance. 715 * 716 * @throws CloneNotSupportedException if there is a problem cloning. 717 */ 718 @Override 719 public Object clone() throws CloneNotSupportedException { 720 LegendGraphic clone = (LegendGraphic) super.clone(); 721 clone.shape = ShapeUtils.clone(this.shape); 722 clone.line = ShapeUtils.clone(this.line); 723 return clone; 724 } 725 726 /** 727 * Provides serialization support. 728 * 729 * @param stream the output stream. 730 * 731 * @throws IOException if there is an I/O error. 732 */ 733 private void writeObject(ObjectOutputStream stream) throws IOException { 734 stream.defaultWriteObject(); 735 SerialUtils.writeShape(this.shape, stream); 736 SerialUtils.writePaint(this.fillPaint, stream); 737 SerialUtils.writePaint(this.outlinePaint, stream); 738 SerialUtils.writeStroke(this.outlineStroke, stream); 739 SerialUtils.writeShape(this.line, stream); 740 SerialUtils.writePaint(this.linePaint, stream); 741 SerialUtils.writeStroke(this.lineStroke, stream); 742 } 743 744 /** 745 * Provides serialization support. 746 * 747 * @param stream the input stream. 748 * 749 * @throws IOException if there is an I/O error. 750 * @throws ClassNotFoundException if there is a classpath problem. 751 */ 752 private void readObject(ObjectInputStream stream) 753 throws IOException, ClassNotFoundException { 754 stream.defaultReadObject(); 755 this.shape = SerialUtils.readShape(stream); 756 this.fillPaint = SerialUtils.readPaint(stream); 757 this.outlinePaint = SerialUtils.readPaint(stream); 758 this.outlineStroke = SerialUtils.readStroke(stream); 759 this.line = SerialUtils.readShape(stream); 760 this.linePaint = SerialUtils.readPaint(stream); 761 this.lineStroke = SerialUtils.readStroke(stream); 762 } 763 764}