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