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