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