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 * BarRenderer.java 029 * ---------------- 030 * (C) Copyright 2002-2008, by Object Refinery Limited. 031 * 032 * Original Author: David Gilbert (for Object Refinery Limited); 033 * Contributor(s): Christian W. Zuckschwerdt; 034 * 035 * Changes 036 * ------- 037 * 14-Mar-2002 : Version 1 (DG); 038 * 23-May-2002 : Added tooltip generator to renderer (DG); 039 * 29-May-2002 : Moved tooltip generator to abstract super-class (DG); 040 * 25-Jun-2002 : Changed constructor to protected and removed redundant 041 * code (DG); 042 * 26-Jun-2002 : Added axis to initialise method, and record upper and lower 043 * clip values (DG); 044 * 24-Sep-2002 : Added getLegendItem() method (DG); 045 * 09-Oct-2002 : Modified constructor to include URL generator (DG); 046 * 05-Nov-2002 : Base dataset is now TableDataset not CategoryDataset (DG); 047 * 10-Jan-2003 : Moved get/setItemMargin() method up from subclasses (DG); 048 * 17-Jan-2003 : Moved plot classes into a separate package (DG); 049 * 25-Mar-2003 : Implemented Serializable (DG); 050 * 01-May-2003 : Modified clipping to allow for dual axes and datasets (DG); 051 * 12-May-2003 : Merged horizontal and vertical bar renderers (DG); 052 * 12-Jun-2003 : Updates for item labels (DG); 053 * 30-Jul-2003 : Modified entity constructor (CZ); 054 * 02-Sep-2003 : Changed initialise method to fix bug 790407 (DG); 055 * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG); 056 * 07-Oct-2003 : Added renderer state (DG); 057 * 27-Oct-2003 : Merged drawHorizontalItem() and drawVerticalItem() 058 * methods (DG); 059 * 28-Oct-2003 : Added support for gradient paint on bars (DG); 060 * 14-Nov-2003 : Added 'maxBarWidth' attribute (DG); 061 * 10-Feb-2004 : Small changes inside drawItem() method to ease cut-and-paste 062 * overriding (DG); 063 * 19-Mar-2004 : Fixed bug introduced with separation of tool tip and item 064 * label generators. Fixed equals() method (DG); 065 * 11-May-2004 : Fix for null pointer exception (bug id 951127) (DG); 066 * 05-Nov-2004 : Modified drawItem() signature (DG); 067 * 26-Jan-2005 : Provided override for getLegendItem() method (DG); 068 * 20-Apr-2005 : Generate legend labels, tooltips and URLs (DG); 069 * 18-May-2005 : Added configurable base value (DG); 070 * 09-Jun-2005 : Use addItemEntity() method from superclass (DG); 071 * 01-Dec-2005 : Update legend item to use/not use outline (DG); 072 * ------------: JFreeChart 1.0.x --------------------------------------------- 073 * 06-Dec-2005 : Fixed bug 1374222 (JDK 1.4 specific code) (DG); 074 * 11-Jan-2006 : Fixed bug 1401856 (bad rendering for non-zero base) (DG); 075 * 04-Aug-2006 : Fixed bug 1467706 (missing item labels for zero value 076 * bars) (DG); 077 * 04-Dec-2006 : Fixed bug in rendering to non-primary axis (DG); 078 * 13-Dec-2006 : Add support for GradientPaint display in legend items (DG); 079 * 20-Apr-2007 : Updated getLegendItem() for renderer change (DG); 080 * 11-May-2007 : Check for visibility in getLegendItem() (DG); 081 * 17-May-2007 : Set datasetIndex and seriesIndex in getLegendItem() (DG); 082 * 18-May-2007 : Set dataset and seriesKey for LegendItem (DG); 083 * 07-May-2008 : If minimumBarLength is > 0.0, extend the non-base end of the 084 * bar (DG); 085 * 17-Jun-2008 : Apply legend shape, font and paint attributes (DG); 086 * 24-Jun-2008 : Added barPainter mechanism (DG); 087 * 26-Jun-2008 : Added crosshair support (DG); 088 * 13-Aug-2008 : Added shadowPaint attribute (DG); 089 * 090 */ 091 092 package org.jfree.chart.renderer.category; 093 094 import java.awt.BasicStroke; 095 import java.awt.Color; 096 import java.awt.Font; 097 import java.awt.Graphics2D; 098 import java.awt.Paint; 099 import java.awt.Shape; 100 import java.awt.Stroke; 101 import java.awt.geom.Line2D; 102 import java.awt.geom.Point2D; 103 import java.awt.geom.Rectangle2D; 104 import java.io.IOException; 105 import java.io.ObjectInputStream; 106 import java.io.ObjectOutputStream; 107 import java.io.Serializable; 108 109 import org.jfree.chart.LegendItem; 110 import org.jfree.chart.axis.CategoryAxis; 111 import org.jfree.chart.axis.ValueAxis; 112 import org.jfree.chart.entity.EntityCollection; 113 import org.jfree.chart.event.RendererChangeEvent; 114 import org.jfree.chart.labels.CategoryItemLabelGenerator; 115 import org.jfree.chart.labels.ItemLabelAnchor; 116 import org.jfree.chart.labels.ItemLabelPosition; 117 import org.jfree.chart.plot.CategoryPlot; 118 import org.jfree.chart.plot.PlotOrientation; 119 import org.jfree.chart.plot.PlotRenderingInfo; 120 import org.jfree.data.Range; 121 import org.jfree.data.category.CategoryDataset; 122 import org.jfree.data.general.DatasetUtilities; 123 import org.jfree.io.SerialUtilities; 124 import org.jfree.text.TextUtilities; 125 import org.jfree.ui.GradientPaintTransformer; 126 import org.jfree.ui.RectangleEdge; 127 import org.jfree.ui.StandardGradientPaintTransformer; 128 import org.jfree.util.ObjectUtilities; 129 import org.jfree.util.PaintUtilities; 130 import org.jfree.util.PublicCloneable; 131 132 /** 133 * A {@link CategoryItemRenderer} that draws individual data items as bars. 134 * The example shown here is generated by the <code>BarChartDemo1.java</code> 135 * program included in the JFreeChart Demo Collection: 136 * <br><br> 137 * <img src="../../../../../images/BarRendererSample.png" 138 * alt="BarRendererSample.png" /> 139 */ 140 public class BarRenderer extends AbstractCategoryItemRenderer 141 implements Cloneable, PublicCloneable, Serializable { 142 143 /** For serialization. */ 144 private static final long serialVersionUID = 6000649414965887481L; 145 146 /** The default item margin percentage. */ 147 public static final double DEFAULT_ITEM_MARGIN = 0.20; 148 149 /** 150 * Constant that controls the minimum width before a bar has an outline 151 * drawn. 152 */ 153 public static final double BAR_OUTLINE_WIDTH_THRESHOLD = 3.0; 154 155 /** 156 * The default bar painter assigned to each new instance of this renderer. 157 * 158 * @since 1.0.11 159 */ 160 private static BarPainter defaultBarPainter = new GradientBarPainter(); 161 162 /** 163 * Returns the default bar painter. 164 * 165 * @return The default bar painter. 166 * 167 * @since 1.0.11 168 */ 169 public static BarPainter getDefaultBarPainter() { 170 return BarRenderer.defaultBarPainter; 171 } 172 173 /** 174 * Sets the default bar painter. 175 * 176 * @param painter the painter (<code>null</code> not permitted). 177 * 178 * @since 1.0.11 179 */ 180 public static void setDefaultBarPainter(BarPainter painter) { 181 if (painter == null) { 182 throw new IllegalArgumentException("Null 'painter' argument."); 183 } 184 BarRenderer.defaultBarPainter = painter; 185 } 186 187 /** The margin between items (bars) within a category. */ 188 private double itemMargin; 189 190 /** A flag that controls whether or not bar outlines are drawn. */ 191 private boolean drawBarOutline; 192 193 /** The maximum bar width as a percentage of the available space. */ 194 private double maximumBarWidth; 195 196 /** The minimum bar length (in Java2D units). */ 197 private double minimumBarLength; 198 199 /** 200 * An optional class used to transform gradient paint objects to fit each 201 * bar. 202 */ 203 private GradientPaintTransformer gradientPaintTransformer; 204 205 /** 206 * The fallback position if a positive item label doesn't fit inside the 207 * bar. 208 */ 209 private ItemLabelPosition positiveItemLabelPositionFallback; 210 211 /** 212 * The fallback position if a negative item label doesn't fit inside the 213 * bar. 214 */ 215 private ItemLabelPosition negativeItemLabelPositionFallback; 216 217 /** The upper clip (axis) value for the axis. */ 218 private double upperClip; 219 // TODO: this needs to move into the renderer state 220 221 /** The lower clip (axis) value for the axis. */ 222 private double lowerClip; 223 // TODO: this needs to move into the renderer state 224 225 /** The base value for the bars (defaults to 0.0). */ 226 private double base; 227 228 /** 229 * A flag that controls whether the base value is included in the range 230 * returned by the findRangeBounds() method. 231 */ 232 private boolean includeBaseInRange; 233 234 /** 235 * The bar painter (never <code>null</code>). 236 * 237 * @since 1.0.11 238 */ 239 private BarPainter barPainter; 240 241 /** 242 * The flag that controls whether or not shadows are drawn for the bars. 243 * 244 * @since 1.0.11 245 */ 246 private boolean shadowsVisible; 247 248 /** 249 * The shadow paint. 250 * 251 * @since 1.0.11 252 */ 253 private transient Paint shadowPaint; 254 255 /** 256 * The x-offset for the shadow effect. 257 * 258 * @since 1.0.11 259 */ 260 private double shadowXOffset; 261 262 /** 263 * The y-offset for the shadow effect. 264 * 265 * @since 1.0.11 266 */ 267 private double shadowYOffset; 268 269 /** 270 * Creates a new bar renderer with default settings. 271 */ 272 public BarRenderer() { 273 super(); 274 this.base = 0.0; 275 this.includeBaseInRange = true; 276 this.itemMargin = DEFAULT_ITEM_MARGIN; 277 this.drawBarOutline = false; 278 this.maximumBarWidth = 1.0; 279 // 100 percent, so it will not apply unless changed 280 this.positiveItemLabelPositionFallback = null; 281 this.negativeItemLabelPositionFallback = null; 282 this.gradientPaintTransformer = new StandardGradientPaintTransformer(); 283 this.minimumBarLength = 0.0; 284 setBaseLegendShape(new Rectangle2D.Double(-4.0, -4.0, 8.0, 8.0)); 285 this.barPainter = getDefaultBarPainter(); 286 this.shadowsVisible = true; 287 this.shadowPaint = Color.gray; 288 this.shadowXOffset = 4.0; 289 this.shadowYOffset = 4.0; 290 } 291 292 /** 293 * Returns the base value for the bars. The default value is 294 * <code>0.0</code>. 295 * 296 * @return The base value for the bars. 297 * 298 * @see #setBase(double) 299 */ 300 public double getBase() { 301 return this.base; 302 } 303 304 /** 305 * Sets the base value for the bars and sends a {@link RendererChangeEvent} 306 * to all registered listeners. 307 * 308 * @param base the new base value. 309 * 310 * @see #getBase() 311 */ 312 public void setBase(double base) { 313 this.base = base; 314 fireChangeEvent(); 315 } 316 317 /** 318 * Returns the item margin as a percentage of the available space for all 319 * bars. 320 * 321 * @return The margin percentage (where 0.10 is ten percent). 322 * 323 * @see #setItemMargin(double) 324 */ 325 public double getItemMargin() { 326 return this.itemMargin; 327 } 328 329 /** 330 * Sets the item margin and sends a {@link RendererChangeEvent} to all 331 * registered listeners. The value is expressed as a percentage of the 332 * available width for plotting all the bars, with the resulting amount to 333 * be distributed between all the bars evenly. 334 * 335 * @param percent the margin (where 0.10 is ten percent). 336 * 337 * @see #getItemMargin() 338 */ 339 public void setItemMargin(double percent) { 340 this.itemMargin = percent; 341 fireChangeEvent(); 342 } 343 344 /** 345 * Returns a flag that controls whether or not bar outlines are drawn. 346 * 347 * @return A boolean. 348 * 349 * @see #setDrawBarOutline(boolean) 350 */ 351 public boolean isDrawBarOutline() { 352 return this.drawBarOutline; 353 } 354 355 /** 356 * Sets the flag that controls whether or not bar outlines are drawn and 357 * sends a {@link RendererChangeEvent} to all registered listeners. 358 * 359 * @param draw the flag. 360 * 361 * @see #isDrawBarOutline() 362 */ 363 public void setDrawBarOutline(boolean draw) { 364 this.drawBarOutline = draw; 365 fireChangeEvent(); 366 } 367 368 /** 369 * Returns the maximum bar width, as a percentage of the available drawing 370 * space. 371 * 372 * @return The maximum bar width. 373 * 374 * @see #setMaximumBarWidth(double) 375 */ 376 public double getMaximumBarWidth() { 377 return this.maximumBarWidth; 378 } 379 380 /** 381 * Sets the maximum bar width, which is specified as a percentage of the 382 * available space for all bars, and sends a {@link RendererChangeEvent} to 383 * all registered listeners. 384 * 385 * @param percent the percent (where 0.05 is five percent). 386 * 387 * @see #getMaximumBarWidth() 388 */ 389 public void setMaximumBarWidth(double percent) { 390 this.maximumBarWidth = percent; 391 fireChangeEvent(); 392 } 393 394 /** 395 * Returns the minimum bar length (in Java2D units). The default value is 396 * 0.0. 397 * 398 * @return The minimum bar length. 399 * 400 * @see #setMinimumBarLength(double) 401 */ 402 public double getMinimumBarLength() { 403 return this.minimumBarLength; 404 } 405 406 /** 407 * Sets the minimum bar length and sends a {@link RendererChangeEvent} to 408 * all registered listeners. The minimum bar length is specified in Java2D 409 * units, and can be used to prevent bars that represent very small data 410 * values from disappearing when drawn on the screen. Typically you would 411 * set this to (say) 0.5 or 1.0 Java 2D units. Use this attribute with 412 * caution, however, because setting it to a non-zero value will 413 * artificially increase the length of bars representing small values, 414 * which may misrepresent your data. 415 * 416 * @param min the minimum bar length (in Java2D units, must be >= 0.0). 417 * 418 * @see #getMinimumBarLength() 419 */ 420 public void setMinimumBarLength(double min) { 421 if (min < 0.0) { 422 throw new IllegalArgumentException("Requires 'min' >= 0.0"); 423 } 424 this.minimumBarLength = min; 425 fireChangeEvent(); 426 } 427 428 /** 429 * Returns the gradient paint transformer (an object used to transform 430 * gradient paint objects to fit each bar). 431 * 432 * @return A transformer (<code>null</code> possible). 433 * 434 * @see #setGradientPaintTransformer(GradientPaintTransformer) 435 */ 436 public GradientPaintTransformer getGradientPaintTransformer() { 437 return this.gradientPaintTransformer; 438 } 439 440 /** 441 * Sets the gradient paint transformer and sends a 442 * {@link RendererChangeEvent} to all registered listeners. 443 * 444 * @param transformer the transformer (<code>null</code> permitted). 445 * 446 * @see #getGradientPaintTransformer() 447 */ 448 public void setGradientPaintTransformer( 449 GradientPaintTransformer transformer) { 450 this.gradientPaintTransformer = transformer; 451 fireChangeEvent(); 452 } 453 454 /** 455 * Returns the fallback position for positive item labels that don't fit 456 * within a bar. 457 * 458 * @return The fallback position (<code>null</code> possible). 459 * 460 * @see #setPositiveItemLabelPositionFallback(ItemLabelPosition) 461 */ 462 public ItemLabelPosition getPositiveItemLabelPositionFallback() { 463 return this.positiveItemLabelPositionFallback; 464 } 465 466 /** 467 * Sets the fallback position for positive item labels that don't fit 468 * within a bar, and sends a {@link RendererChangeEvent} to all registered 469 * listeners. 470 * 471 * @param position the position (<code>null</code> permitted). 472 * 473 * @see #getPositiveItemLabelPositionFallback() 474 */ 475 public void setPositiveItemLabelPositionFallback( 476 ItemLabelPosition position) { 477 this.positiveItemLabelPositionFallback = position; 478 fireChangeEvent(); 479 } 480 481 /** 482 * Returns the fallback position for negative item labels that don't fit 483 * within a bar. 484 * 485 * @return The fallback position (<code>null</code> possible). 486 * 487 * @see #setPositiveItemLabelPositionFallback(ItemLabelPosition) 488 */ 489 public ItemLabelPosition getNegativeItemLabelPositionFallback() { 490 return this.negativeItemLabelPositionFallback; 491 } 492 493 /** 494 * Sets the fallback position for negative item labels that don't fit 495 * within a bar, and sends a {@link RendererChangeEvent} to all registered 496 * listeners. 497 * 498 * @param position the position (<code>null</code> permitted). 499 * 500 * @see #getNegativeItemLabelPositionFallback() 501 */ 502 public void setNegativeItemLabelPositionFallback( 503 ItemLabelPosition position) { 504 this.negativeItemLabelPositionFallback = position; 505 fireChangeEvent(); 506 } 507 508 /** 509 * Returns the flag that controls whether or not the base value for the 510 * bars is included in the range calculated by 511 * {@link #findRangeBounds(CategoryDataset)}. 512 * 513 * @return <code>true</code> if the base is included in the range, and 514 * <code>false</code> otherwise. 515 * 516 * @since 1.0.1 517 * 518 * @see #setIncludeBaseInRange(boolean) 519 */ 520 public boolean getIncludeBaseInRange() { 521 return this.includeBaseInRange; 522 } 523 524 /** 525 * Sets the flag that controls whether or not the base value for the bars 526 * is included in the range calculated by 527 * {@link #findRangeBounds(CategoryDataset)}. If the flag is changed, 528 * a {@link RendererChangeEvent} is sent to all registered listeners. 529 * 530 * @param include the new value for the flag. 531 * 532 * @since 1.0.1 533 * 534 * @see #getIncludeBaseInRange() 535 */ 536 public void setIncludeBaseInRange(boolean include) { 537 if (this.includeBaseInRange != include) { 538 this.includeBaseInRange = include; 539 fireChangeEvent(); 540 } 541 } 542 543 /** 544 * Returns the bar painter. 545 * 546 * @return The bar painter (never <code>null</code>). 547 * 548 * @see #setBarPainter(BarPainter) 549 * 550 * @since 1.0.11 551 */ 552 public BarPainter getBarPainter() { 553 return this.barPainter; 554 } 555 556 /** 557 * Sets the bar painter for this renderer and sends a 558 * {@link RendererChangeEvent} to all registered listeners. 559 * 560 * @param painter the painter (<code>null</code> not permitted). 561 * 562 * @see #getBarPainter() 563 * 564 * @since 1.0.11 565 */ 566 public void setBarPainter(BarPainter painter) { 567 if (painter == null) { 568 throw new IllegalArgumentException("Null 'painter' argument."); 569 } 570 this.barPainter = painter; 571 fireChangeEvent(); 572 } 573 574 /** 575 * Returns the flag that controls whether or not shadows are drawn for 576 * the bars. 577 * 578 * @return A boolean. 579 * 580 * @since 1.0.11 581 */ 582 public boolean getShadowsVisible() { 583 return this.shadowsVisible; 584 } 585 586 /** 587 * Sets the flag that controls whether or not shadows are 588 * drawn by the renderer. 589 * 590 * @param visible the new flag value. 591 * 592 * @since 1.0.11 593 */ 594 public void setShadowVisible(boolean visible) { 595 this.shadowsVisible = visible; 596 fireChangeEvent(); 597 } 598 599 /** 600 * Returns the shadow paint. 601 * 602 * @return The shadow paint. 603 * 604 * @see #setShadowPaint(Paint) 605 * 606 * @since 1.0.11 607 */ 608 public Paint getShadowPaint() { 609 return this.shadowPaint; 610 } 611 612 /** 613 * Sets the shadow paint and sends a {@link RendererChangeEvent} to all 614 * registered listeners. 615 * 616 * @param paint the paint (<code>null</code> not permitted). 617 * 618 * @see #getShadowPaint() 619 * 620 * @since 1.0.11 621 */ 622 public void setShadowPaint(Paint paint) { 623 if (paint == null) { 624 throw new IllegalArgumentException("Null 'paint' argument."); 625 } 626 this.shadowPaint = paint; 627 fireChangeEvent(); 628 } 629 630 /** 631 * Returns the shadow x-offset. 632 * 633 * @return The shadow x-offset. 634 * 635 * @since 1.0.11 636 */ 637 public double getShadowXOffset() { 638 return this.shadowXOffset; 639 } 640 641 /** 642 * Sets the x-offset for the bar shadow and sends a 643 * {@link RendererChangeEvent} to all registered listeners. 644 * 645 * @param offset the offset. 646 * 647 * @since 1.0.11 648 */ 649 public void setShadowXOffset(double offset) { 650 this.shadowXOffset = offset; 651 fireChangeEvent(); 652 } 653 654 /** 655 * Returns the shadow y-offset. 656 * 657 * @return The shadow y-offset. 658 * 659 * @since 1.0.11 660 */ 661 public double getShadowYOffset() { 662 return this.shadowYOffset; 663 } 664 665 /** 666 * Sets the y-offset for the bar shadow and sends a 667 * {@link RendererChangeEvent} to all registered listeners. 668 * 669 * @param offset the offset. 670 * 671 * @since 1.0.11 672 */ 673 public void setShadowYOffset(double offset) { 674 this.shadowYOffset = offset; 675 fireChangeEvent(); 676 } 677 678 /** 679 * Returns the lower clip value. This value is recalculated in the 680 * initialise() method. 681 * 682 * @return The value. 683 */ 684 public double getLowerClip() { 685 // TODO: this attribute should be transferred to the renderer state. 686 return this.lowerClip; 687 } 688 689 /** 690 * Returns the upper clip value. This value is recalculated in the 691 * initialise() method. 692 * 693 * @return The value. 694 */ 695 public double getUpperClip() { 696 // TODO: this attribute should be transferred to the renderer state. 697 return this.upperClip; 698 } 699 700 /** 701 * Initialises the renderer and returns a state object that will be passed 702 * to subsequent calls to the drawItem method. This method gets called 703 * once at the start of the process of drawing a chart. 704 * 705 * @param g2 the graphics device. 706 * @param dataArea the area in which the data is to be plotted. 707 * @param plot the plot. 708 * @param rendererIndex the renderer index. 709 * @param info collects chart rendering information for return to caller. 710 * 711 * @return The renderer state. 712 */ 713 public CategoryItemRendererState initialise(Graphics2D g2, 714 Rectangle2D dataArea, 715 CategoryPlot plot, 716 int rendererIndex, 717 PlotRenderingInfo info) { 718 719 CategoryItemRendererState state = super.initialise(g2, dataArea, plot, 720 rendererIndex, info); 721 722 // get the clipping values... 723 ValueAxis rangeAxis = plot.getRangeAxisForDataset(rendererIndex); 724 this.lowerClip = rangeAxis.getRange().getLowerBound(); 725 this.upperClip = rangeAxis.getRange().getUpperBound(); 726 727 // calculate the bar width 728 calculateBarWidth(plot, dataArea, rendererIndex, state); 729 730 return state; 731 732 } 733 734 /** 735 * Calculates the bar width and stores it in the renderer state. 736 * 737 * @param plot the plot. 738 * @param dataArea the data area. 739 * @param rendererIndex the renderer index. 740 * @param state the renderer state. 741 */ 742 protected void calculateBarWidth(CategoryPlot plot, 743 Rectangle2D dataArea, 744 int rendererIndex, 745 CategoryItemRendererState state) { 746 747 CategoryAxis domainAxis = getDomainAxis(plot, rendererIndex); 748 CategoryDataset dataset = plot.getDataset(rendererIndex); 749 if (dataset != null) { 750 int columns = dataset.getColumnCount(); 751 int rows = dataset.getRowCount(); 752 double space = 0.0; 753 PlotOrientation orientation = plot.getOrientation(); 754 if (orientation == PlotOrientation.HORIZONTAL) { 755 space = dataArea.getHeight(); 756 } 757 else if (orientation == PlotOrientation.VERTICAL) { 758 space = dataArea.getWidth(); 759 } 760 double maxWidth = space * getMaximumBarWidth(); 761 double categoryMargin = 0.0; 762 double currentItemMargin = 0.0; 763 if (columns > 1) { 764 categoryMargin = domainAxis.getCategoryMargin(); 765 } 766 if (rows > 1) { 767 currentItemMargin = getItemMargin(); 768 } 769 double used = space * (1 - domainAxis.getLowerMargin() 770 - domainAxis.getUpperMargin() 771 - categoryMargin - currentItemMargin); 772 if ((rows * columns) > 0) { 773 state.setBarWidth(Math.min(used / (rows * columns), maxWidth)); 774 } 775 else { 776 state.setBarWidth(Math.min(used, maxWidth)); 777 } 778 } 779 } 780 781 /** 782 * Calculates the coordinate of the first "side" of a bar. This will be 783 * the minimum x-coordinate for a vertical bar, and the minimum 784 * y-coordinate for a horizontal bar. 785 * 786 * @param plot the plot. 787 * @param orientation the plot orientation. 788 * @param dataArea the data area. 789 * @param domainAxis the domain axis. 790 * @param state the renderer state (has the bar width precalculated). 791 * @param row the row index. 792 * @param column the column index. 793 * 794 * @return The coordinate. 795 */ 796 protected double calculateBarW0(CategoryPlot plot, 797 PlotOrientation orientation, 798 Rectangle2D dataArea, 799 CategoryAxis domainAxis, 800 CategoryItemRendererState state, 801 int row, 802 int column) { 803 // calculate bar width... 804 double space = 0.0; 805 if (orientation == PlotOrientation.HORIZONTAL) { 806 space = dataArea.getHeight(); 807 } 808 else { 809 space = dataArea.getWidth(); 810 } 811 double barW0 = domainAxis.getCategoryStart(column, getColumnCount(), 812 dataArea, plot.getDomainAxisEdge()); 813 int seriesCount = getRowCount(); 814 int categoryCount = getColumnCount(); 815 if (seriesCount > 1) { 816 double seriesGap = space * getItemMargin() 817 / (categoryCount * (seriesCount - 1)); 818 double seriesW = calculateSeriesWidth(space, domainAxis, 819 categoryCount, seriesCount); 820 barW0 = barW0 + row * (seriesW + seriesGap) 821 + (seriesW / 2.0) - (state.getBarWidth() / 2.0); 822 } 823 else { 824 barW0 = domainAxis.getCategoryMiddle(column, getColumnCount(), 825 dataArea, plot.getDomainAxisEdge()) - state.getBarWidth() 826 / 2.0; 827 } 828 return barW0; 829 } 830 831 /** 832 * Calculates the coordinates for the length of a single bar. 833 * 834 * @param value the value represented by the bar. 835 * 836 * @return The coordinates for each end of the bar (or <code>null</code> if 837 * the bar is not visible for the current axis range). 838 */ 839 protected double[] calculateBarL0L1(double value) { 840 double lclip = getLowerClip(); 841 double uclip = getUpperClip(); 842 double barLow = Math.min(this.base, value); 843 double barHigh = Math.max(this.base, value); 844 if (barHigh < lclip) { // bar is not visible 845 return null; 846 } 847 if (barLow > uclip) { // bar is not visible 848 return null; 849 } 850 barLow = Math.max(barLow, lclip); 851 barHigh = Math.min(barHigh, uclip); 852 return new double[] {barLow, barHigh}; 853 } 854 855 /** 856 * Returns the range of values the renderer requires to display all the 857 * items from the specified dataset. This takes into account the range 858 * of values in the dataset, plus the flag that determines whether or not 859 * the base value for the bars should be included in the range. 860 * 861 * @param dataset the dataset (<code>null</code> permitted). 862 * 863 * @return The range (or <code>null</code> if the dataset is 864 * <code>null</code> or empty). 865 */ 866 public Range findRangeBounds(CategoryDataset dataset) { 867 Range result = DatasetUtilities.findRangeBounds(dataset); 868 if (result != null) { 869 if (this.includeBaseInRange) { 870 result = Range.expandToInclude(result, this.base); 871 } 872 } 873 return result; 874 } 875 876 /** 877 * Returns a legend item for a series. 878 * 879 * @param datasetIndex the dataset index (zero-based). 880 * @param series the series index (zero-based). 881 * 882 * @return The legend item (possibly <code>null</code>). 883 */ 884 public LegendItem getLegendItem(int datasetIndex, int series) { 885 886 CategoryPlot cp = getPlot(); 887 if (cp == null) { 888 return null; 889 } 890 891 // check that a legend item needs to be displayed... 892 if (!isSeriesVisible(series) || !isSeriesVisibleInLegend(series)) { 893 return null; 894 } 895 896 CategoryDataset dataset = cp.getDataset(datasetIndex); 897 String label = getLegendItemLabelGenerator().generateLabel(dataset, 898 series); 899 String description = label; 900 String toolTipText = null; 901 if (getLegendItemToolTipGenerator() != null) { 902 toolTipText = getLegendItemToolTipGenerator().generateLabel( 903 dataset, series); 904 } 905 String urlText = null; 906 if (getLegendItemURLGenerator() != null) { 907 urlText = getLegendItemURLGenerator().generateLabel(dataset, 908 series); 909 } 910 Shape shape = lookupLegendShape(series); 911 Paint paint = lookupSeriesPaint(series); 912 Paint outlinePaint = lookupSeriesOutlinePaint(series); 913 Stroke outlineStroke = lookupSeriesOutlineStroke(series); 914 915 LegendItem result = new LegendItem(label, description, toolTipText, 916 urlText, true, shape, true, paint, isDrawBarOutline(), 917 outlinePaint, outlineStroke, false, new Line2D.Float(), 918 new BasicStroke(1.0f), Color.black); 919 result.setLabelFont(lookupLegendTextFont(series)); 920 Paint labelPaint = lookupLegendTextPaint(series); 921 if (labelPaint != null) { 922 result.setLabelPaint(labelPaint); 923 } 924 result.setDataset(dataset); 925 result.setDatasetIndex(datasetIndex); 926 result.setSeriesKey(dataset.getRowKey(series)); 927 result.setSeriesIndex(series); 928 if (this.gradientPaintTransformer != null) { 929 result.setFillPaintTransformer(this.gradientPaintTransformer); 930 } 931 return result; 932 } 933 934 /** 935 * Draws the bar for a single (series, category) data item. 936 * 937 * @param g2 the graphics device. 938 * @param state the renderer state. 939 * @param dataArea the data area. 940 * @param plot the plot. 941 * @param domainAxis the domain axis. 942 * @param rangeAxis the range axis. 943 * @param dataset the dataset. 944 * @param row the row index (zero-based). 945 * @param column the column index (zero-based). 946 * @param pass the pass index. 947 */ 948 public void drawItem(Graphics2D g2, 949 CategoryItemRendererState state, 950 Rectangle2D dataArea, 951 CategoryPlot plot, 952 CategoryAxis domainAxis, 953 ValueAxis rangeAxis, 954 CategoryDataset dataset, 955 int row, 956 int column, 957 int pass) { 958 959 // nothing is drawn for null values... 960 Number dataValue = dataset.getValue(row, column); 961 if (dataValue == null) { 962 return; 963 } 964 965 final double value = dataValue.doubleValue(); 966 PlotOrientation orientation = plot.getOrientation(); 967 double barW0 = calculateBarW0(plot, orientation, dataArea, domainAxis, 968 state, row, column); 969 double[] barL0L1 = calculateBarL0L1(value); 970 if (barL0L1 == null) { 971 return; // the bar is not visible 972 } 973 974 RectangleEdge edge = plot.getRangeAxisEdge(); 975 double transL0 = rangeAxis.valueToJava2D(barL0L1[0], dataArea, edge); 976 double transL1 = rangeAxis.valueToJava2D(barL0L1[1], dataArea, edge); 977 978 // in the following code, barL0 is (in Java2D coordinates) the LEFT 979 // end of the bar for a horizontal bar chart, and the TOP end of the 980 // bar for a vertical bar chart. Whether this is the BASE of the bar 981 // or not depends also on (a) whether the data value is 'negative' 982 // relative to the base value and (b) whether or not the range axis is 983 // inverted. This only matters if/when we apply the minimumBarLength 984 // attribute, because we should extend the non-base end of the bar 985 boolean positive = (value >= this.base); 986 boolean inverted = rangeAxis.isInverted(); 987 double barL0 = Math.min(transL0, transL1); 988 double barLength = Math.abs(transL1 - transL0); 989 double barLengthAdj = 0.0; 990 if (barLength > 0.0 && barLength < getMinimumBarLength()) { 991 barLengthAdj = getMinimumBarLength() - barLength; 992 } 993 double barL0Adj = 0.0; 994 RectangleEdge barBase; 995 if (orientation == PlotOrientation.HORIZONTAL) { 996 if (positive && inverted || !positive && !inverted) { 997 barL0Adj = barLengthAdj; 998 barBase = RectangleEdge.RIGHT; 999 } 1000 else { 1001 barBase = RectangleEdge.LEFT; 1002 } 1003 } 1004 else { 1005 if (positive && !inverted || !positive && inverted) { 1006 barL0Adj = barLengthAdj; 1007 barBase = RectangleEdge.BOTTOM; 1008 } 1009 else { 1010 barBase = RectangleEdge.TOP; 1011 } 1012 } 1013 1014 // draw the bar... 1015 Rectangle2D bar = null; 1016 if (orientation == PlotOrientation.HORIZONTAL) { 1017 bar = new Rectangle2D.Double(barL0 - barL0Adj, barW0, 1018 barLength + barLengthAdj, state.getBarWidth()); 1019 } 1020 else { 1021 bar = new Rectangle2D.Double(barW0, barL0 - barL0Adj, 1022 state.getBarWidth(), barLength + barLengthAdj); 1023 } 1024 if (getShadowsVisible()) { 1025 this.barPainter.paintBarShadow(g2, this, row, column, bar, barBase, 1026 true); 1027 } 1028 this.barPainter.paintBar(g2, this, row, column, bar, barBase); 1029 1030 CategoryItemLabelGenerator generator = getItemLabelGenerator(row, 1031 column); 1032 if (generator != null && isItemLabelVisible(row, column)) { 1033 drawItemLabel(g2, dataset, row, column, plot, generator, bar, 1034 (value < 0.0)); 1035 } 1036 1037 // submit the current data point as a crosshair candidate 1038 int datasetIndex = plot.indexOf(dataset); 1039 updateCrosshairValues(state.getCrosshairState(), 1040 dataset.getRowKey(row), dataset.getColumnKey(column), value, 1041 datasetIndex, barW0, barL0, orientation); 1042 1043 // add an item entity, if this information is being collected 1044 EntityCollection entities = state.getEntityCollection(); 1045 if (entities != null) { 1046 addItemEntity(entities, dataset, row, column, bar); 1047 } 1048 1049 } 1050 1051 /** 1052 * Calculates the available space for each series. 1053 * 1054 * @param space the space along the entire axis (in Java2D units). 1055 * @param axis the category axis. 1056 * @param categories the number of categories. 1057 * @param series the number of series. 1058 * 1059 * @return The width of one series. 1060 */ 1061 protected double calculateSeriesWidth(double space, CategoryAxis axis, 1062 int categories, int series) { 1063 double factor = 1.0 - getItemMargin() - axis.getLowerMargin() 1064 - axis.getUpperMargin(); 1065 if (categories > 1) { 1066 factor = factor - axis.getCategoryMargin(); 1067 } 1068 return (space * factor) / (categories * series); 1069 } 1070 1071 /** 1072 * Draws an item label. This method is overridden so that the bar can be 1073 * used to calculate the label anchor point. 1074 * 1075 * @param g2 the graphics device. 1076 * @param data the dataset. 1077 * @param row the row. 1078 * @param column the column. 1079 * @param plot the plot. 1080 * @param generator the label generator. 1081 * @param bar the bar. 1082 * @param negative a flag indicating a negative value. 1083 */ 1084 protected void drawItemLabel(Graphics2D g2, 1085 CategoryDataset data, 1086 int row, 1087 int column, 1088 CategoryPlot plot, 1089 CategoryItemLabelGenerator generator, 1090 Rectangle2D bar, 1091 boolean negative) { 1092 1093 String label = generator.generateLabel(data, row, column); 1094 if (label == null) { 1095 return; // nothing to do 1096 } 1097 1098 Font labelFont = getItemLabelFont(row, column); 1099 g2.setFont(labelFont); 1100 Paint paint = getItemLabelPaint(row, column); 1101 g2.setPaint(paint); 1102 1103 // find out where to place the label... 1104 ItemLabelPosition position = null; 1105 if (!negative) { 1106 position = getPositiveItemLabelPosition(row, column); 1107 } 1108 else { 1109 position = getNegativeItemLabelPosition(row, column); 1110 } 1111 1112 // work out the label anchor point... 1113 Point2D anchorPoint = calculateLabelAnchorPoint( 1114 position.getItemLabelAnchor(), bar, plot.getOrientation()); 1115 1116 if (isInternalAnchor(position.getItemLabelAnchor())) { 1117 Shape bounds = TextUtilities.calculateRotatedStringBounds(label, 1118 g2, (float) anchorPoint.getX(), (float) anchorPoint.getY(), 1119 position.getTextAnchor(), position.getAngle(), 1120 position.getRotationAnchor()); 1121 1122 if (bounds != null) { 1123 if (!bar.contains(bounds.getBounds2D())) { 1124 if (!negative) { 1125 position = getPositiveItemLabelPositionFallback(); 1126 } 1127 else { 1128 position = getNegativeItemLabelPositionFallback(); 1129 } 1130 if (position != null) { 1131 anchorPoint = calculateLabelAnchorPoint( 1132 position.getItemLabelAnchor(), bar, 1133 plot.getOrientation()); 1134 } 1135 } 1136 } 1137 1138 } 1139 1140 if (position != null) { 1141 TextUtilities.drawRotatedString(label, g2, 1142 (float) anchorPoint.getX(), (float) anchorPoint.getY(), 1143 position.getTextAnchor(), position.getAngle(), 1144 position.getRotationAnchor()); 1145 } 1146 } 1147 1148 /** 1149 * Calculates the item label anchor point. 1150 * 1151 * @param anchor the anchor. 1152 * @param bar the bar. 1153 * @param orientation the plot orientation. 1154 * 1155 * @return The anchor point. 1156 */ 1157 private Point2D calculateLabelAnchorPoint(ItemLabelAnchor anchor, 1158 Rectangle2D bar, 1159 PlotOrientation orientation) { 1160 1161 Point2D result = null; 1162 double offset = getItemLabelAnchorOffset(); 1163 double x0 = bar.getX() - offset; 1164 double x1 = bar.getX(); 1165 double x2 = bar.getX() + offset; 1166 double x3 = bar.getCenterX(); 1167 double x4 = bar.getMaxX() - offset; 1168 double x5 = bar.getMaxX(); 1169 double x6 = bar.getMaxX() + offset; 1170 1171 double y0 = bar.getMaxY() + offset; 1172 double y1 = bar.getMaxY(); 1173 double y2 = bar.getMaxY() - offset; 1174 double y3 = bar.getCenterY(); 1175 double y4 = bar.getMinY() + offset; 1176 double y5 = bar.getMinY(); 1177 double y6 = bar.getMinY() - offset; 1178 1179 if (anchor == ItemLabelAnchor.CENTER) { 1180 result = new Point2D.Double(x3, y3); 1181 } 1182 else if (anchor == ItemLabelAnchor.INSIDE1) { 1183 result = new Point2D.Double(x4, y4); 1184 } 1185 else if (anchor == ItemLabelAnchor.INSIDE2) { 1186 result = new Point2D.Double(x4, y4); 1187 } 1188 else if (anchor == ItemLabelAnchor.INSIDE3) { 1189 result = new Point2D.Double(x4, y3); 1190 } 1191 else if (anchor == ItemLabelAnchor.INSIDE4) { 1192 result = new Point2D.Double(x4, y2); 1193 } 1194 else if (anchor == ItemLabelAnchor.INSIDE5) { 1195 result = new Point2D.Double(x4, y2); 1196 } 1197 else if (anchor == ItemLabelAnchor.INSIDE6) { 1198 result = new Point2D.Double(x3, y2); 1199 } 1200 else if (anchor == ItemLabelAnchor.INSIDE7) { 1201 result = new Point2D.Double(x2, y2); 1202 } 1203 else if (anchor == ItemLabelAnchor.INSIDE8) { 1204 result = new Point2D.Double(x2, y2); 1205 } 1206 else if (anchor == ItemLabelAnchor.INSIDE9) { 1207 result = new Point2D.Double(x2, y3); 1208 } 1209 else if (anchor == ItemLabelAnchor.INSIDE10) { 1210 result = new Point2D.Double(x2, y4); 1211 } 1212 else if (anchor == ItemLabelAnchor.INSIDE11) { 1213 result = new Point2D.Double(x2, y4); 1214 } 1215 else if (anchor == ItemLabelAnchor.INSIDE12) { 1216 result = new Point2D.Double(x3, y4); 1217 } 1218 else if (anchor == ItemLabelAnchor.OUTSIDE1) { 1219 result = new Point2D.Double(x5, y6); 1220 } 1221 else if (anchor == ItemLabelAnchor.OUTSIDE2) { 1222 result = new Point2D.Double(x6, y5); 1223 } 1224 else if (anchor == ItemLabelAnchor.OUTSIDE3) { 1225 result = new Point2D.Double(x6, y3); 1226 } 1227 else if (anchor == ItemLabelAnchor.OUTSIDE4) { 1228 result = new Point2D.Double(x6, y1); 1229 } 1230 else if (anchor == ItemLabelAnchor.OUTSIDE5) { 1231 result = new Point2D.Double(x5, y0); 1232 } 1233 else if (anchor == ItemLabelAnchor.OUTSIDE6) { 1234 result = new Point2D.Double(x3, y0); 1235 } 1236 else if (anchor == ItemLabelAnchor.OUTSIDE7) { 1237 result = new Point2D.Double(x1, y0); 1238 } 1239 else if (anchor == ItemLabelAnchor.OUTSIDE8) { 1240 result = new Point2D.Double(x0, y1); 1241 } 1242 else if (anchor == ItemLabelAnchor.OUTSIDE9) { 1243 result = new Point2D.Double(x0, y3); 1244 } 1245 else if (anchor == ItemLabelAnchor.OUTSIDE10) { 1246 result = new Point2D.Double(x0, y5); 1247 } 1248 else if (anchor == ItemLabelAnchor.OUTSIDE11) { 1249 result = new Point2D.Double(x1, y6); 1250 } 1251 else if (anchor == ItemLabelAnchor.OUTSIDE12) { 1252 result = new Point2D.Double(x3, y6); 1253 } 1254 1255 return result; 1256 1257 } 1258 1259 /** 1260 * Returns <code>true</code> if the specified anchor point is inside a bar. 1261 * 1262 * @param anchor the anchor point. 1263 * 1264 * @return A boolean. 1265 */ 1266 private boolean isInternalAnchor(ItemLabelAnchor anchor) { 1267 return anchor == ItemLabelAnchor.CENTER 1268 || anchor == ItemLabelAnchor.INSIDE1 1269 || anchor == ItemLabelAnchor.INSIDE2 1270 || anchor == ItemLabelAnchor.INSIDE3 1271 || anchor == ItemLabelAnchor.INSIDE4 1272 || anchor == ItemLabelAnchor.INSIDE5 1273 || anchor == ItemLabelAnchor.INSIDE6 1274 || anchor == ItemLabelAnchor.INSIDE7 1275 || anchor == ItemLabelAnchor.INSIDE8 1276 || anchor == ItemLabelAnchor.INSIDE9 1277 || anchor == ItemLabelAnchor.INSIDE10 1278 || anchor == ItemLabelAnchor.INSIDE11 1279 || anchor == ItemLabelAnchor.INSIDE12; 1280 } 1281 1282 /** 1283 * Tests this instance for equality with an arbitrary object. 1284 * 1285 * @param obj the object (<code>null</code> permitted). 1286 * 1287 * @return A boolean. 1288 */ 1289 public boolean equals(Object obj) { 1290 if (obj == this) { 1291 return true; 1292 } 1293 if (!(obj instanceof BarRenderer)) { 1294 return false; 1295 } 1296 BarRenderer that = (BarRenderer) obj; 1297 if (this.base != that.base) { 1298 return false; 1299 } 1300 if (this.itemMargin != that.itemMargin) { 1301 return false; 1302 } 1303 if (this.drawBarOutline != that.drawBarOutline) { 1304 return false; 1305 } 1306 if (this.maximumBarWidth != that.maximumBarWidth) { 1307 return false; 1308 } 1309 if (this.minimumBarLength != that.minimumBarLength) { 1310 return false; 1311 } 1312 if (!ObjectUtilities.equal(this.gradientPaintTransformer, 1313 that.gradientPaintTransformer)) { 1314 return false; 1315 } 1316 if (!ObjectUtilities.equal(this.positiveItemLabelPositionFallback, 1317 that.positiveItemLabelPositionFallback)) { 1318 return false; 1319 } 1320 if (!ObjectUtilities.equal(this.negativeItemLabelPositionFallback, 1321 that.negativeItemLabelPositionFallback)) { 1322 return false; 1323 } 1324 if (!this.barPainter.equals(that.barPainter)) { 1325 return false; 1326 } 1327 if (this.shadowsVisible != that.shadowsVisible) { 1328 return false; 1329 } 1330 if (!PaintUtilities.equal(this.shadowPaint, that.shadowPaint)) { 1331 return false; 1332 } 1333 if (this.shadowXOffset != that.shadowXOffset) { 1334 return false; 1335 } 1336 if (this.shadowYOffset != that.shadowYOffset) { 1337 return false; 1338 } 1339 return super.equals(obj); 1340 } 1341 1342 /** 1343 * Provides serialization support. 1344 * 1345 * @param stream the output stream. 1346 * 1347 * @throws IOException if there is an I/O error. 1348 */ 1349 private void writeObject(ObjectOutputStream stream) throws IOException { 1350 stream.defaultWriteObject(); 1351 SerialUtilities.writePaint(this.shadowPaint, stream); 1352 } 1353 1354 /** 1355 * Provides serialization support. 1356 * 1357 * @param stream the input stream. 1358 * 1359 * @throws IOException if there is an I/O error. 1360 * @throws ClassNotFoundException if there is a classpath problem. 1361 */ 1362 private void readObject(ObjectInputStream stream) 1363 throws IOException, ClassNotFoundException { 1364 stream.defaultReadObject(); 1365 this.shadowPaint = SerialUtilities.readPaint(stream); 1366 } 1367 1368 }