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