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