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