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