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