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 * PaintScaleLegend.java 029 * --------------------- 030 * (C) Copyright 2007, 2008, by Object Refinery Limited. 031 * 032 * Original Author: David Gilbert (for Object Refinery Limited); 033 * Contributor(s): -; 034 * 035 * Changes 036 * ------- 037 * 22-Jan-2007 : Version 1 (DG); 038 * 18-Jun-2008 : Fixed bug drawing scale with log axis (DG); 039 * 040 */ 041 042 package org.jfree.chart.title; 043 044 import java.awt.BasicStroke; 045 import java.awt.Color; 046 import java.awt.Graphics2D; 047 import java.awt.Paint; 048 import java.awt.Stroke; 049 import java.awt.geom.Rectangle2D; 050 import java.io.IOException; 051 import java.io.ObjectInputStream; 052 import java.io.ObjectOutputStream; 053 054 import org.jfree.chart.axis.AxisLocation; 055 import org.jfree.chart.axis.AxisSpace; 056 import org.jfree.chart.axis.ValueAxis; 057 import org.jfree.chart.block.LengthConstraintType; 058 import org.jfree.chart.block.RectangleConstraint; 059 import org.jfree.chart.event.TitleChangeEvent; 060 import org.jfree.chart.plot.Plot; 061 import org.jfree.chart.plot.PlotOrientation; 062 import org.jfree.chart.renderer.PaintScale; 063 import org.jfree.data.Range; 064 import org.jfree.io.SerialUtilities; 065 import org.jfree.ui.RectangleEdge; 066 import org.jfree.ui.Size2D; 067 import org.jfree.util.PaintUtilities; 068 import org.jfree.util.PublicCloneable; 069 070 /** 071 * A legend that shows a range of values and their associated colors, driven 072 * by an underlying {@link PaintScale} implementation. 073 * 074 * @since 1.0.4 075 */ 076 public class PaintScaleLegend extends Title implements PublicCloneable { 077 078 /** For serialization. */ 079 static final long serialVersionUID = -1365146490993227503L; 080 081 /** The paint scale (never <code>null</code>). */ 082 private PaintScale scale; 083 084 /** The value axis (never <code>null</code>). */ 085 private ValueAxis axis; 086 087 /** 088 * The axis location (handles both orientations, never 089 * <code>null</code>). 090 */ 091 private AxisLocation axisLocation; 092 093 /** The offset between the axis and the paint strip (in Java2D units). */ 094 private double axisOffset; 095 096 /** The thickness of the paint strip (in Java2D units). */ 097 private double stripWidth; 098 099 /** 100 * A flag that controls whether or not an outline is drawn around the 101 * paint strip. 102 */ 103 private boolean stripOutlineVisible; 104 105 /** The paint used to draw an outline around the paint strip. */ 106 private transient Paint stripOutlinePaint; 107 108 /** The stroke used to draw an outline around the paint strip. */ 109 private transient Stroke stripOutlineStroke; 110 111 /** The background paint (never <code>null</code>). */ 112 private transient Paint backgroundPaint; 113 114 /** 115 * The number of subdivisions for the scale when rendering. 116 * 117 * @since 1.0.11 118 */ 119 private int subdivisions; 120 121 /** 122 * Creates a new instance. 123 * 124 * @param scale the scale (<code>null</code> not permitted). 125 * @param axis the axis (<code>null</code> not permitted). 126 */ 127 public PaintScaleLegend(PaintScale scale, ValueAxis axis) { 128 if (axis == null) { 129 throw new IllegalArgumentException("Null 'axis' argument."); 130 } 131 this.scale = scale; 132 this.axis = axis; 133 this.axisLocation = AxisLocation.BOTTOM_OR_LEFT; 134 this.axisOffset = 0.0; 135 this.axis.setRange(scale.getLowerBound(), scale.getUpperBound()); 136 this.stripWidth = 15.0; 137 this.stripOutlineVisible = false; 138 this.stripOutlinePaint = Color.gray; 139 this.stripOutlineStroke = new BasicStroke(0.5f); 140 this.backgroundPaint = Color.white; 141 this.subdivisions = 100; 142 } 143 144 /** 145 * Returns the scale used to convert values to colors. 146 * 147 * @return The scale (never <code>null</code>). 148 * 149 * @see #setScale(PaintScale) 150 */ 151 public PaintScale getScale() { 152 return this.scale; 153 } 154 155 /** 156 * Sets the scale and sends a {@link TitleChangeEvent} to all registered 157 * listeners. 158 * 159 * @param scale the scale (<code>null</code> not permitted). 160 * 161 * @see #getScale() 162 */ 163 public void setScale(PaintScale scale) { 164 if (scale == null) { 165 throw new IllegalArgumentException("Null 'scale' argument."); 166 } 167 this.scale = scale; 168 notifyListeners(new TitleChangeEvent(this)); 169 } 170 171 /** 172 * Returns the axis for the paint scale. 173 * 174 * @return The axis (never <code>null</code>). 175 * 176 * @see #setAxis(ValueAxis) 177 */ 178 public ValueAxis getAxis() { 179 return this.axis; 180 } 181 182 /** 183 * Sets the axis for the paint scale and sends a {@link TitleChangeEvent} 184 * to all registered listeners. 185 * 186 * @param axis the axis (<code>null</code> not permitted). 187 * 188 * @see #getAxis() 189 */ 190 public void setAxis(ValueAxis axis) { 191 if (axis == null) { 192 throw new IllegalArgumentException("Null 'axis' argument."); 193 } 194 this.axis = axis; 195 notifyListeners(new TitleChangeEvent(this)); 196 } 197 198 /** 199 * Returns the axis location. 200 * 201 * @return The axis location (never <code>null</code>). 202 * 203 * @see #setAxisLocation(AxisLocation) 204 */ 205 public AxisLocation getAxisLocation() { 206 return this.axisLocation; 207 } 208 209 /** 210 * Sets the axis location and sends a {@link TitleChangeEvent} to all 211 * registered listeners. 212 * 213 * @param location the location (<code>null</code> not permitted). 214 * 215 * @see #getAxisLocation() 216 */ 217 public void setAxisLocation(AxisLocation location) { 218 if (location == null) { 219 throw new IllegalArgumentException("Null 'location' argument."); 220 } 221 this.axisLocation = location; 222 notifyListeners(new TitleChangeEvent(this)); 223 } 224 225 /** 226 * Returns the offset between the axis and the paint strip. 227 * 228 * @return The offset between the axis and the paint strip. 229 * 230 * @see #setAxisOffset(double) 231 */ 232 public double getAxisOffset() { 233 return this.axisOffset; 234 } 235 236 /** 237 * Sets the offset between the axis and the paint strip and sends a 238 * {@link TitleChangeEvent} to all registered listeners. 239 * 240 * @param offset the offset. 241 */ 242 public void setAxisOffset(double offset) { 243 this.axisOffset = offset; 244 notifyListeners(new TitleChangeEvent(this)); 245 } 246 247 /** 248 * Returns the width of the paint strip, in Java2D units. 249 * 250 * @return The width of the paint strip. 251 * 252 * @see #setStripWidth(double) 253 */ 254 public double getStripWidth() { 255 return this.stripWidth; 256 } 257 258 /** 259 * Sets the width of the paint strip and sends a {@link TitleChangeEvent} 260 * to all registered listeners. 261 * 262 * @param width the width. 263 * 264 * @see #getStripWidth() 265 */ 266 public void setStripWidth(double width) { 267 this.stripWidth = width; 268 notifyListeners(new TitleChangeEvent(this)); 269 } 270 271 /** 272 * Returns the flag that controls whether or not an outline is drawn 273 * around the paint strip. 274 * 275 * @return A boolean. 276 * 277 * @see #setStripOutlineVisible(boolean) 278 */ 279 public boolean isStripOutlineVisible() { 280 return this.stripOutlineVisible; 281 } 282 283 /** 284 * Sets the flag that controls whether or not an outline is drawn around 285 * the paint strip, and sends a {@link TitleChangeEvent} to all registered 286 * listeners. 287 * 288 * @param visible the flag. 289 * 290 * @see #isStripOutlineVisible() 291 */ 292 public void setStripOutlineVisible(boolean visible) { 293 this.stripOutlineVisible = visible; 294 notifyListeners(new TitleChangeEvent(this)); 295 } 296 297 /** 298 * Returns the paint used to draw the outline of the paint strip. 299 * 300 * @return The paint (never <code>null</code>). 301 * 302 * @see #setStripOutlinePaint(Paint) 303 */ 304 public Paint getStripOutlinePaint() { 305 return this.stripOutlinePaint; 306 } 307 308 /** 309 * Sets the paint used to draw the outline of the paint strip, and sends 310 * a {@link TitleChangeEvent} to all registered listeners. 311 * 312 * @param paint the paint (<code>null</code> not permitted). 313 * 314 * @see #getStripOutlinePaint() 315 */ 316 public void setStripOutlinePaint(Paint paint) { 317 if (paint == null) { 318 throw new IllegalArgumentException("Null 'paint' argument."); 319 } 320 this.stripOutlinePaint = paint; 321 notifyListeners(new TitleChangeEvent(this)); 322 } 323 324 /** 325 * Returns the stroke used to draw the outline around the paint strip. 326 * 327 * @return The stroke (never <code>null</code>). 328 * 329 * @see #setStripOutlineStroke(Stroke) 330 */ 331 public Stroke getStripOutlineStroke() { 332 return this.stripOutlineStroke; 333 } 334 335 /** 336 * Sets the stroke used to draw the outline around the paint strip and 337 * sends a {@link TitleChangeEvent} to all registered listeners. 338 * 339 * @param stroke the stroke (<code>null</code> not permitted). 340 * 341 * @see #getStripOutlineStroke() 342 */ 343 public void setStripOutlineStroke(Stroke stroke) { 344 if (stroke == null) { 345 throw new IllegalArgumentException("Null 'stroke' argument."); 346 } 347 this.stripOutlineStroke = stroke; 348 notifyListeners(new TitleChangeEvent(this)); 349 } 350 351 /** 352 * Returns the background paint. 353 * 354 * @return The background paint. 355 */ 356 public Paint getBackgroundPaint() { 357 return this.backgroundPaint; 358 } 359 360 /** 361 * Sets the background paint and sends a {@link TitleChangeEvent} to all 362 * registered listeners. 363 * 364 * @param paint the paint (<code>null</code> permitted). 365 */ 366 public void setBackgroundPaint(Paint paint) { 367 this.backgroundPaint = paint; 368 notifyListeners(new TitleChangeEvent(this)); 369 } 370 371 /** 372 * Returns the number of subdivisions used to draw the scale. 373 * 374 * @return The subdivision count. 375 * 376 * @since 1.0.11 377 */ 378 public int getSubdivisionCount() { 379 return this.subdivisions; 380 } 381 382 /** 383 * Sets the subdivision count and sends a {@link TitleChangeEvent} to 384 * all registered listeners. 385 * 386 * @param count the count. 387 * 388 * @since 1.0.11 389 */ 390 public void setSubdivisionCount(int count) { 391 if (count <= 0) { 392 throw new IllegalArgumentException("Requires 'count' > 0."); 393 } 394 this.subdivisions = count; 395 notifyListeners(new TitleChangeEvent(this)); 396 } 397 398 /** 399 * Arranges the contents of the block, within the given constraints, and 400 * returns the block size. 401 * 402 * @param g2 the graphics device. 403 * @param constraint the constraint (<code>null</code> not permitted). 404 * 405 * @return The block size (in Java2D units, never <code>null</code>). 406 */ 407 public Size2D arrange(Graphics2D g2, RectangleConstraint constraint) { 408 RectangleConstraint cc = toContentConstraint(constraint); 409 LengthConstraintType w = cc.getWidthConstraintType(); 410 LengthConstraintType h = cc.getHeightConstraintType(); 411 Size2D contentSize = null; 412 if (w == LengthConstraintType.NONE) { 413 if (h == LengthConstraintType.NONE) { 414 contentSize = new Size2D(getWidth(), getHeight()); 415 } 416 else if (h == LengthConstraintType.RANGE) { 417 throw new RuntimeException("Not yet implemented."); 418 } 419 else if (h == LengthConstraintType.FIXED) { 420 throw new RuntimeException("Not yet implemented."); 421 } 422 } 423 else if (w == LengthConstraintType.RANGE) { 424 if (h == LengthConstraintType.NONE) { 425 throw new RuntimeException("Not yet implemented."); 426 } 427 else if (h == LengthConstraintType.RANGE) { 428 contentSize = arrangeRR(g2, cc.getWidthRange(), 429 cc.getHeightRange()); 430 } 431 else if (h == LengthConstraintType.FIXED) { 432 throw new RuntimeException("Not yet implemented."); 433 } 434 } 435 else if (w == LengthConstraintType.FIXED) { 436 if (h == LengthConstraintType.NONE) { 437 throw new RuntimeException("Not yet implemented."); 438 } 439 else if (h == LengthConstraintType.RANGE) { 440 throw new RuntimeException("Not yet implemented."); 441 } 442 else if (h == LengthConstraintType.FIXED) { 443 throw new RuntimeException("Not yet implemented."); 444 } 445 } 446 return new Size2D(calculateTotalWidth(contentSize.getWidth()), 447 calculateTotalHeight(contentSize.getHeight())); 448 } 449 450 /** 451 * Returns the content size for the title. This will reflect the fact that 452 * a text title positioned on the left or right of a chart will be rotated 453 * 90 degrees. 454 * 455 * @param g2 the graphics device. 456 * @param widthRange the width range. 457 * @param heightRange the height range. 458 * 459 * @return The content size. 460 */ 461 protected Size2D arrangeRR(Graphics2D g2, Range widthRange, 462 Range heightRange) { 463 464 RectangleEdge position = getPosition(); 465 if (position == RectangleEdge.TOP || position == RectangleEdge.BOTTOM) { 466 467 468 float maxWidth = (float) widthRange.getUpperBound(); 469 470 // determine the space required for the axis 471 AxisSpace space = this.axis.reserveSpace(g2, null, 472 new Rectangle2D.Double(0, 0, maxWidth, 100), 473 RectangleEdge.BOTTOM, null); 474 475 return new Size2D(maxWidth, this.stripWidth + this.axisOffset 476 + space.getTop() + space.getBottom()); 477 } 478 else if (position == RectangleEdge.LEFT || position 479 == RectangleEdge.RIGHT) { 480 float maxHeight = (float) heightRange.getUpperBound(); 481 AxisSpace space = this.axis.reserveSpace(g2, null, 482 new Rectangle2D.Double(0, 0, 100, maxHeight), 483 RectangleEdge.RIGHT, null); 484 return new Size2D(this.stripWidth + this.axisOffset 485 + space.getLeft() + space.getRight(), maxHeight); 486 } 487 else { 488 throw new RuntimeException("Unrecognised position."); 489 } 490 } 491 492 /** 493 * Draws the legend within the specified area. 494 * 495 * @param g2 the graphics target (<code>null</code> not permitted). 496 * @param area the drawing area (<code>null</code> not permitted). 497 */ 498 public void draw(Graphics2D g2, Rectangle2D area) { 499 draw(g2, area, null); 500 } 501 502 /** 503 * Draws the legend within the specified area. 504 * 505 * @param g2 the graphics target (<code>null</code> not permitted). 506 * @param area the drawing area (<code>null</code> not permitted). 507 * @param params drawing parameters (ignored here). 508 * 509 * @return <code>null</code>. 510 */ 511 public Object draw(Graphics2D g2, Rectangle2D area, Object params) { 512 513 Rectangle2D target = (Rectangle2D) area.clone(); 514 target = trimMargin(target); 515 if (this.backgroundPaint != null) { 516 g2.setPaint(this.backgroundPaint); 517 g2.fill(target); 518 } 519 getFrame().draw(g2, target); 520 getFrame().getInsets().trim(target); 521 target = trimPadding(target); 522 double base = this.axis.getLowerBound(); 523 double increment = this.axis.getRange().getLength() / this.subdivisions; 524 Rectangle2D r = new Rectangle2D.Double(); 525 526 527 if (RectangleEdge.isTopOrBottom(getPosition())) { 528 RectangleEdge axisEdge = Plot.resolveRangeAxisLocation( 529 this.axisLocation, PlotOrientation.HORIZONTAL); 530 if (axisEdge == RectangleEdge.TOP) { 531 for (int i = 0; i < this.subdivisions; i++) { 532 double v = base + (i * increment); 533 Paint p = this.scale.getPaint(v); 534 double vv0 = this.axis.valueToJava2D(v, target, 535 RectangleEdge.TOP); 536 double vv1 = this.axis.valueToJava2D(v + increment, target, 537 RectangleEdge.TOP); 538 double ww = Math.abs(vv1 - vv0); 539 r.setRect(Math.min(vv0, vv1), target.getMaxY() 540 - this.stripWidth, ww, this.stripWidth); 541 g2.setPaint(p); 542 g2.fill(r); 543 } 544 g2.setPaint(this.stripOutlinePaint); 545 g2.setStroke(this.stripOutlineStroke); 546 g2.draw(new Rectangle2D.Double(target.getMinX(), 547 target.getMaxY() - this.stripWidth, target.getWidth(), 548 this.stripWidth)); 549 this.axis.draw(g2, target.getMaxY() - this.stripWidth 550 - this.axisOffset, target, target, RectangleEdge.TOP, 551 null); 552 } 553 else if (axisEdge == RectangleEdge.BOTTOM) { 554 for (int i = 0; i < this.subdivisions; i++) { 555 double v = base + (i * increment); 556 Paint p = this.scale.getPaint(v); 557 double vv0 = this.axis.valueToJava2D(v, target, 558 RectangleEdge.BOTTOM); 559 double vv1 = this.axis.valueToJava2D(v + increment, target, 560 RectangleEdge.BOTTOM); 561 double ww = Math.abs(vv1 - vv0); 562 r.setRect(Math.min(vv0, vv1), target.getMinY(), ww, 563 this.stripWidth); 564 g2.setPaint(p); 565 g2.fill(r); 566 } 567 g2.setPaint(this.stripOutlinePaint); 568 g2.setStroke(this.stripOutlineStroke); 569 g2.draw(new Rectangle2D.Double(target.getMinX(), 570 target.getMinY(), target.getWidth(), this.stripWidth)); 571 this.axis.draw(g2, target.getMinY() + this.stripWidth 572 + this.axisOffset, target, target, 573 RectangleEdge.BOTTOM, null); 574 } 575 } 576 else { 577 RectangleEdge axisEdge = Plot.resolveRangeAxisLocation( 578 this.axisLocation, PlotOrientation.VERTICAL); 579 if (axisEdge == RectangleEdge.LEFT) { 580 for (int i = 0; i < this.subdivisions; i++) { 581 double v = base + (i * increment); 582 Paint p = this.scale.getPaint(v); 583 double vv0 = this.axis.valueToJava2D(v, target, 584 RectangleEdge.LEFT); 585 double vv1 = this.axis.valueToJava2D(v + increment, target, 586 RectangleEdge.LEFT); 587 double hh = Math.abs(vv1 - vv0); 588 r.setRect(target.getMaxX() - this.stripWidth, 589 Math.min(vv0, vv1), this.stripWidth, hh); 590 g2.setPaint(p); 591 g2.fill(r); 592 } 593 g2.setPaint(this.stripOutlinePaint); 594 g2.setStroke(this.stripOutlineStroke); 595 g2.draw(new Rectangle2D.Double(target.getMaxX() 596 - this.stripWidth, target.getMinY(), this.stripWidth, 597 target.getHeight())); 598 this.axis.draw(g2, target.getMaxX() - this.stripWidth 599 - this.axisOffset, target, target, RectangleEdge.LEFT, 600 null); 601 } 602 else if (axisEdge == RectangleEdge.RIGHT) { 603 for (int i = 0; i < this.subdivisions; i++) { 604 double v = base + (i * increment); 605 Paint p = this.scale.getPaint(v); 606 double vv0 = this.axis.valueToJava2D(v, target, 607 RectangleEdge.LEFT); 608 double vv1 = this.axis.valueToJava2D(v + increment, target, 609 RectangleEdge.LEFT); 610 double hh = Math.abs(vv1 - vv0); 611 r.setRect(target.getMinX(), Math.min(vv0, vv1), 612 this.stripWidth, hh); 613 g2.setPaint(p); 614 g2.fill(r); 615 } 616 g2.setPaint(this.stripOutlinePaint); 617 g2.setStroke(this.stripOutlineStroke); 618 g2.draw(new Rectangle2D.Double(target.getMinX(), 619 target.getMinY(), this.stripWidth, target.getHeight())); 620 this.axis.draw(g2, target.getMinX() + this.stripWidth 621 + this.axisOffset, target, target, RectangleEdge.RIGHT, 622 null); 623 } 624 } 625 return null; 626 } 627 628 /** 629 * Tests this legend for equality with an arbitrary object. 630 * 631 * @param obj the object (<code>null</code> permitted). 632 * 633 * @return A boolean. 634 */ 635 public boolean equals(Object obj) { 636 if (!(obj instanceof PaintScaleLegend)) { 637 return false; 638 } 639 PaintScaleLegend that = (PaintScaleLegend) obj; 640 if (!this.scale.equals(that.scale)) { 641 return false; 642 } 643 if (!this.axis.equals(that.axis)) { 644 return false; 645 } 646 if (!this.axisLocation.equals(that.axisLocation)) { 647 return false; 648 } 649 if (this.axisOffset != that.axisOffset) { 650 return false; 651 } 652 if (this.stripWidth != that.stripWidth) { 653 return false; 654 } 655 if (this.stripOutlineVisible != that.stripOutlineVisible) { 656 return false; 657 } 658 if (!PaintUtilities.equal(this.stripOutlinePaint, 659 that.stripOutlinePaint)) { 660 return false; 661 } 662 if (!this.stripOutlineStroke.equals(that.stripOutlineStroke)) { 663 return false; 664 } 665 if (!PaintUtilities.equal(this.backgroundPaint, that.backgroundPaint)) { 666 return false; 667 } 668 if (this.subdivisions != that.subdivisions) { 669 return false; 670 } 671 return super.equals(obj); 672 } 673 674 /** 675 * Provides serialization support. 676 * 677 * @param stream the output stream. 678 * 679 * @throws IOException if there is an I/O error. 680 */ 681 private void writeObject(ObjectOutputStream stream) throws IOException { 682 stream.defaultWriteObject(); 683 SerialUtilities.writePaint(this.backgroundPaint, stream); 684 SerialUtilities.writePaint(this.stripOutlinePaint, stream); 685 SerialUtilities.writeStroke(this.stripOutlineStroke, 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 stream.defaultReadObject(); 699 this.backgroundPaint = SerialUtilities.readPaint(stream); 700 this.stripOutlinePaint = SerialUtilities.readPaint(stream); 701 this.stripOutlineStroke = SerialUtilities.readStroke(stream); 702 } 703 704 }