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 * LegendGraphic.java 029 * ------------------ 030 * (C) Copyright 2004-2013, 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.util.ParamChecks; 070import org.jfree.io.SerialUtilities; 071import org.jfree.ui.GradientPaintTransformer; 072import org.jfree.ui.RectangleAnchor; 073import org.jfree.ui.Size2D; 074import org.jfree.ui.StandardGradientPaintTransformer; 075import org.jfree.util.ObjectUtilities; 076import org.jfree.util.PaintUtilities; 077import org.jfree.util.PublicCloneable; 078import org.jfree.util.ShapeUtilities; 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</code> not permitted). 154 * @param fillPaint the fill paint (<code>null</code> not permitted). 155 */ 156 public LegendGraphic(Shape shape, Paint fillPaint) { 157 ParamChecks.nullNotPermitted(shape, "shape"); 158 ParamChecks.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</code>. 264 * 265 * @return The transformer (never <code>null</code>). 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</code>. 278 * 279 * @param transformer the transformer (<code>null</code> not permitted). 280 * 281 * @since 1.0.4 282 * 283 * @see #getFillPaintTransformer() 284 */ 285 public void setFillPaintTransformer(GradientPaintTransformer transformer) { 286 ParamChecks.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</code> not permitted). 373 * 374 * @see #setShapeAnchor(RectangleAnchor) 375 */ 376 public void setShapeAnchor(RectangleAnchor anchor) { 377 ParamChecks.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</code> not permitted). 397 * 398 * @see #getShapeLocation() 399 */ 400 public void setShapeLocation(RectangleAnchor location) { 401 ParamChecks.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</code> not permitted). 500 * 501 * @return The block size (in Java2D units, never <code>null</code>). 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 = RectangleAnchor.coordinates(area, 584 this.shapeLocation); 585 Shape aLine = ShapeUtilities.createTranslatedShape(getLine(), 586 this.shapeAnchor, location.getX(), location.getY()); 587 g2.setPaint(this.linePaint); 588 g2.setStroke(this.lineStroke); 589 g2.draw(aLine); 590 } 591 592 if (this.shapeVisible) { 593 Point2D location = RectangleAnchor.coordinates(area, 594 this.shapeLocation); 595 596 Shape s = ShapeUtilities.createTranslatedShape(this.shape, 597 this.shapeAnchor, location.getX(), location.getY()); 598 if (this.shapeFilled) { 599 Paint p = this.fillPaint; 600 if (p instanceof GradientPaint) { 601 GradientPaint gp = (GradientPaint) this.fillPaint; 602 p = this.fillPaintTransformer.transform(gp, s); 603 } 604 g2.setPaint(p); 605 g2.fill(s); 606 } 607 if (this.shapeOutlineVisible) { 608 g2.setPaint(this.outlinePaint); 609 g2.setStroke(this.outlineStroke); 610 g2.draw(s); 611 } 612 } 613 } 614 615 /** 616 * Draws the block within the specified area. 617 * 618 * @param g2 the graphics device. 619 * @param area the area. 620 * @param params ignored (<code>null</code> permitted). 621 * 622 * @return Always <code>null</code>. 623 */ 624 @Override 625 public Object draw(Graphics2D g2, Rectangle2D area, Object params) { 626 draw(g2, area); 627 return null; 628 } 629 630 /** 631 * Tests this <code>LegendGraphic</code> instance for equality with an 632 * arbitrary object. 633 * 634 * @param obj the object (<code>null</code> permitted). 635 * 636 * @return A boolean. 637 */ 638 @Override 639 public boolean equals(Object obj) { 640 if (!(obj instanceof LegendGraphic)) { 641 return false; 642 } 643 LegendGraphic that = (LegendGraphic) obj; 644 if (this.shapeVisible != that.shapeVisible) { 645 return false; 646 } 647 if (!ShapeUtilities.equal(this.shape, that.shape)) { 648 return false; 649 } 650 if (this.shapeFilled != that.shapeFilled) { 651 return false; 652 } 653 if (!PaintUtilities.equal(this.fillPaint, that.fillPaint)) { 654 return false; 655 } 656 if (!ObjectUtilities.equal(this.fillPaintTransformer, 657 that.fillPaintTransformer)) { 658 return false; 659 } 660 if (this.shapeOutlineVisible != that.shapeOutlineVisible) { 661 return false; 662 } 663 if (!PaintUtilities.equal(this.outlinePaint, that.outlinePaint)) { 664 return false; 665 } 666 if (!ObjectUtilities.equal(this.outlineStroke, that.outlineStroke)) { 667 return false; 668 } 669 if (this.shapeAnchor != that.shapeAnchor) { 670 return false; 671 } 672 if (this.shapeLocation != that.shapeLocation) { 673 return false; 674 } 675 if (this.lineVisible != that.lineVisible) { 676 return false; 677 } 678 if (!ShapeUtilities.equal(this.line, that.line)) { 679 return false; 680 } 681 if (!PaintUtilities.equal(this.linePaint, that.linePaint)) { 682 return false; 683 } 684 if (!ObjectUtilities.equal(this.lineStroke, that.lineStroke)) { 685 return false; 686 } 687 return super.equals(obj); 688 } 689 690 /** 691 * Returns a hash code for this instance. 692 * 693 * @return A hash code. 694 */ 695 @Override 696 public int hashCode() { 697 int result = 193; 698 result = 37 * result + ObjectUtilities.hashCode(this.fillPaint); 699 // FIXME: use other fields too 700 return result; 701 } 702 703 /** 704 * Returns a clone of this <code>LegendGraphic</code> instance. 705 * 706 * @return A clone of this <code>LegendGraphic</code> instance. 707 * 708 * @throws CloneNotSupportedException if there is a problem cloning. 709 */ 710 @Override 711 public Object clone() throws CloneNotSupportedException { 712 LegendGraphic clone = (LegendGraphic) super.clone(); 713 clone.shape = ShapeUtilities.clone(this.shape); 714 clone.line = ShapeUtilities.clone(this.line); 715 return clone; 716 } 717 718 /** 719 * Provides serialization support. 720 * 721 * @param stream the output stream. 722 * 723 * @throws IOException if there is an I/O error. 724 */ 725 private void writeObject(ObjectOutputStream stream) throws IOException { 726 stream.defaultWriteObject(); 727 SerialUtilities.writeShape(this.shape, stream); 728 SerialUtilities.writePaint(this.fillPaint, stream); 729 SerialUtilities.writePaint(this.outlinePaint, stream); 730 SerialUtilities.writeStroke(this.outlineStroke, stream); 731 SerialUtilities.writeShape(this.line, stream); 732 SerialUtilities.writePaint(this.linePaint, stream); 733 SerialUtilities.writeStroke(this.lineStroke, stream); 734 } 735 736 /** 737 * Provides serialization support. 738 * 739 * @param stream the input stream. 740 * 741 * @throws IOException if there is an I/O error. 742 * @throws ClassNotFoundException if there is a classpath problem. 743 */ 744 private void readObject(ObjectInputStream stream) 745 throws IOException, ClassNotFoundException { 746 stream.defaultReadObject(); 747 this.shape = SerialUtilities.readShape(stream); 748 this.fillPaint = SerialUtilities.readPaint(stream); 749 this.outlinePaint = SerialUtilities.readPaint(stream); 750 this.outlineStroke = SerialUtilities.readStroke(stream); 751 this.line = SerialUtilities.readShape(stream); 752 this.linePaint = SerialUtilities.readPaint(stream); 753 this.lineStroke = SerialUtilities.readStroke(stream); 754 } 755 756}