001 /* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2006, 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-2006, by Object Refinery Limited. 031 * 032 * Original Author: David Gilbert (for Object Refinery Limited); 033 * Contributor(s): Christian W. Zuckschwerdt; 034 * 035 * $Id: BarRenderer.java,v 1.13.2.9 2006/01/11 11:27:34 mungady Exp $ 036 * 037 * Changes 038 * ------- 039 * 14-Mar-2002 : Version 1 (DG); 040 * 23-May-2002 : Added tooltip generator to renderer (DG); 041 * 29-May-2002 : Moved tooltip generator to abstract super-class (DG); 042 * 25-Jun-2002 : Changed constructor to protected and removed redundant 043 * code (DG); 044 * 26-Jun-2002 : Added axis to initialise method, and record upper and lower 045 * clip values (DG); 046 * 24-Sep-2002 : Added getLegendItem() method (DG); 047 * 09-Oct-2002 : Modified constructor to include URL generator (DG); 048 * 05-Nov-2002 : Base dataset is now TableDataset not CategoryDataset (DG); 049 * 10-Jan-2003 : Moved get/setItemMargin() method up from subclasses (DG); 050 * 17-Jan-2003 : Moved plot classes into a separate package (DG); 051 * 25-Mar-2003 : Implemented Serializable (DG); 052 * 01-May-2003 : Modified clipping to allow for dual axes and datasets (DG); 053 * 12-May-2003 : Merged horizontal and vertical bar renderers (DG); 054 * 12-Jun-2003 : Updates for item labels (DG); 055 * 30-Jul-2003 : Modified entity constructor (CZ); 056 * 02-Sep-2003 : Changed initialise method to fix bug 790407 (DG); 057 * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG); 058 * 07-Oct-2003 : Added renderer state (DG); 059 * 27-Oct-2003 : Merged drawHorizontalItem() and drawVerticalItem() 060 * methods (DG); 061 * 28-Oct-2003 : Added support for gradient paint on bars (DG); 062 * 14-Nov-2003 : Added 'maxBarWidth' attribute (DG); 063 * 10-Feb-2004 : Small changes inside drawItem() method to ease cut-and-paste 064 * overriding (DG); 065 * 19-Mar-2004 : Fixed bug introduced with separation of tool tip and item 066 * label generators. Fixed equals() method (DG); 067 * 11-May-2004 : Fix for null pointer exception (bug id 951127) (DG); 068 * 05-Nov-2004 : Modified drawItem() signature (DG); 069 * 26-Jan-2005 : Provided override for getLegendItem() method (DG); 070 * 20-Apr-2005 : Generate legend labels, tooltips and URLs (DG); 071 * 18-May-2005 : Added configurable base value (DG); 072 * 09-Jun-2005 : Use addItemEntity() method from superclass (DG); 073 * 01-Dec-2005 : Update legend item to use/not use outline (DG); 074 * ------------: JFreeChart 1.0.0 --------------------------------------------- 075 * 06-Dec-2005 : Fixed bug 1374222 (JDK 1.4 specific code) (DG); 076 * 11-Jan-2006 : Fixed bug 1401856 (bad rendering for non-zero base) (DG); 077 * 078 */ 079 080 package org.jfree.chart.renderer.category; 081 082 import java.awt.BasicStroke; 083 import java.awt.Color; 084 import java.awt.Font; 085 import java.awt.GradientPaint; 086 import java.awt.Graphics2D; 087 import java.awt.Paint; 088 import java.awt.Shape; 089 import java.awt.Stroke; 090 import java.awt.geom.Line2D; 091 import java.awt.geom.Point2D; 092 import java.awt.geom.Rectangle2D; 093 import java.io.Serializable; 094 095 import org.jfree.chart.LegendItem; 096 import org.jfree.chart.axis.CategoryAxis; 097 import org.jfree.chart.axis.ValueAxis; 098 import org.jfree.chart.entity.EntityCollection; 099 import org.jfree.chart.event.RendererChangeEvent; 100 import org.jfree.chart.labels.CategoryItemLabelGenerator; 101 import org.jfree.chart.labels.ItemLabelAnchor; 102 import org.jfree.chart.labels.ItemLabelPosition; 103 import org.jfree.chart.plot.CategoryPlot; 104 import org.jfree.chart.plot.PlotOrientation; 105 import org.jfree.chart.plot.PlotRenderingInfo; 106 import org.jfree.data.Range; 107 import org.jfree.data.category.CategoryDataset; 108 import org.jfree.data.general.DatasetUtilities; 109 import org.jfree.text.TextUtilities; 110 import org.jfree.ui.GradientPaintTransformer; 111 import org.jfree.ui.RectangleEdge; 112 import org.jfree.ui.StandardGradientPaintTransformer; 113 import org.jfree.util.ObjectUtilities; 114 import org.jfree.util.PublicCloneable; 115 116 /** 117 * A {@link CategoryItemRenderer} that draws individual data items as bars. 118 */ 119 public class BarRenderer extends AbstractCategoryItemRenderer 120 implements Cloneable, PublicCloneable, Serializable { 121 122 /** For serialization. */ 123 private static final long serialVersionUID = 6000649414965887481L; 124 125 /** The default item margin percentage. */ 126 public static final double DEFAULT_ITEM_MARGIN = 0.20; 127 128 /** 129 * Constant that controls the minimum width before a bar has an outline 130 * drawn. 131 */ 132 public static final double BAR_OUTLINE_WIDTH_THRESHOLD = 3.0; 133 134 /** The margin between items (bars) within a category. */ 135 private double itemMargin; 136 137 /** A flag that controls whether or not bar outlines are drawn. */ 138 private boolean drawBarOutline; 139 140 /** The maximum bar width as a percentage of the available space. */ 141 private double maximumBarWidth; 142 143 /** The minimum bar length (in Java2D units). */ 144 private double minimumBarLength; 145 146 /** 147 * An optional class used to transform gradient paint objects to fit each 148 * bar. 149 */ 150 private GradientPaintTransformer gradientPaintTransformer; 151 152 /** 153 * The fallback position if a positive item label doesn't fit inside the 154 * bar. 155 */ 156 private ItemLabelPosition positiveItemLabelPositionFallback; 157 158 /** 159 * The fallback position if a negative item label doesn't fit inside the 160 * bar. 161 */ 162 private ItemLabelPosition negativeItemLabelPositionFallback; 163 164 /** The upper clip (axis) value for the axis. */ 165 private double upperClip; 166 // TODO: this needs to move into the renderer state 167 168 /** The lower clip (axis) value for the axis. */ 169 private double lowerClip; 170 // TODO: this needs to move into the renderer state 171 172 /** The base value for the bars (defaults to 0.0). */ 173 private double base; 174 175 /** 176 * A flag that controls whether the base value is included in the range 177 * returned by the findRangeBounds() method. 178 */ 179 private boolean includeBaseInRange; 180 181 /** 182 * Creates a new bar renderer with default settings. 183 */ 184 public BarRenderer() { 185 super(); 186 this.base = 0.0; 187 this.includeBaseInRange = true; 188 this.itemMargin = DEFAULT_ITEM_MARGIN; 189 this.drawBarOutline = true; 190 this.maximumBarWidth = 1.0; 191 // 100 percent, so it will not apply unless changed 192 this.positiveItemLabelPositionFallback = null; 193 this.negativeItemLabelPositionFallback = null; 194 this.gradientPaintTransformer = new StandardGradientPaintTransformer(); 195 this.minimumBarLength = 0.0; 196 } 197 198 /** 199 * Returns the base value for the bars. 200 * 201 * @return The base value for the bars. 202 */ 203 public double getBase() { 204 return this.base; 205 } 206 207 /** 208 * Sets the base value for the bars and sends a {@link RendererChangeEvent} 209 * to all registered listeners. 210 * 211 * @param base the new base value. 212 */ 213 public void setBase(double base) { 214 this.base = base; 215 notifyListeners(new RendererChangeEvent(this)); 216 } 217 218 /** 219 * Returns the item margin as a percentage of the available space for all 220 * bars. 221 * 222 * @return The margin percentage (where 0.10 is ten percent). 223 */ 224 public double getItemMargin() { 225 return this.itemMargin; 226 } 227 228 /** 229 * Sets the item margin and sends a {@link RendererChangeEvent} to all 230 * registered listeners. The value is expressed as a percentage of the 231 * available width for plotting all the bars, with the resulting amount to 232 * be distributed between all the bars evenly. 233 * 234 * @param percent the margin (where 0.10 is ten percent). 235 */ 236 public void setItemMargin(double percent) { 237 this.itemMargin = percent; 238 notifyListeners(new RendererChangeEvent(this)); 239 } 240 241 /** 242 * Returns a flag that controls whether or not bar outlines are drawn. 243 * 244 * @return A boolean. 245 */ 246 public boolean isDrawBarOutline() { 247 return this.drawBarOutline; 248 } 249 250 /** 251 * Sets the flag that controls whether or not bar outlines are drawn and 252 * sends a {@link RendererChangeEvent} to all registered listeners. 253 * 254 * @param draw the flag. 255 */ 256 public void setDrawBarOutline(boolean draw) { 257 this.drawBarOutline = draw; 258 notifyListeners(new RendererChangeEvent(this)); 259 } 260 261 /** 262 * Returns the maximum bar width, as a percentage of the available drawing 263 * space. 264 * 265 * @return The maximum bar width. 266 */ 267 public double getMaximumBarWidth() { 268 return this.maximumBarWidth; 269 } 270 271 /** 272 * Sets the maximum bar width, which is specified as a percentage of the 273 * available space for all bars, and sends a {@link RendererChangeEvent} to 274 * all registered listeners. 275 * 276 * @param percent the percent (where 0.05 is five percent). 277 */ 278 public void setMaximumBarWidth(double percent) { 279 this.maximumBarWidth = percent; 280 notifyListeners(new RendererChangeEvent(this)); 281 } 282 283 /** 284 * Returns the minimum bar length (in Java2D units). 285 * 286 * @return The minimum bar length. 287 */ 288 public double getMinimumBarLength() { 289 return this.minimumBarLength; 290 } 291 292 /** 293 * Sets the minimum bar length and sends a {@link RendererChangeEvent} to 294 * all registered listeners. The minimum bar length is specified in Java2D 295 * units, and can be used to prevent bars that represent very small data 296 * values from disappearing when drawn on the screen. 297 * 298 * @param min the minimum bar length (in Java2D units). 299 */ 300 public void setMinimumBarLength(double min) { 301 this.minimumBarLength = min; 302 notifyListeners(new RendererChangeEvent(this)); 303 } 304 305 /** 306 * Returns the gradient paint transformer (an object used to transform 307 * gradient paint objects to fit each bar. 308 * 309 * @return A transformer (<code>null</code> possible). 310 */ 311 public GradientPaintTransformer getGradientPaintTransformer() { 312 return this.gradientPaintTransformer; 313 } 314 315 /** 316 * Sets the gradient paint transformer and sends a 317 * {@link RendererChangeEvent} to all registered listeners. 318 * 319 * @param transformer the transformer (<code>null</code> permitted). 320 */ 321 public void setGradientPaintTransformer( 322 GradientPaintTransformer transformer) { 323 this.gradientPaintTransformer = transformer; 324 notifyListeners(new RendererChangeEvent(this)); 325 } 326 327 /** 328 * Returns the fallback position for positive item labels that don't fit 329 * within a bar. 330 * 331 * @return The fallback position (<code>null</code> possible). 332 */ 333 public ItemLabelPosition getPositiveItemLabelPositionFallback() { 334 return this.positiveItemLabelPositionFallback; 335 } 336 337 /** 338 * Sets the fallback position for positive item labels that don't fit 339 * within a bar, and sends a {@link RendererChangeEvent} to all registered 340 * listeners. 341 * 342 * @param position the position (<code>null</code> permitted). 343 */ 344 public void setPositiveItemLabelPositionFallback( 345 ItemLabelPosition position) { 346 this.positiveItemLabelPositionFallback = position; 347 notifyListeners(new RendererChangeEvent(this)); 348 } 349 350 /** 351 * Returns the fallback position for negative item labels that don't fit 352 * within a bar. 353 * 354 * @return The fallback position (<code>null</code> possible). 355 */ 356 public ItemLabelPosition getNegativeItemLabelPositionFallback() { 357 return this.negativeItemLabelPositionFallback; 358 } 359 360 /** 361 * Sets the fallback position for negative item labels that don't fit 362 * within a bar, and sends a {@link RendererChangeEvent} to all registered 363 * listeners. 364 * 365 * @param position the position (<code>null</code> permitted). 366 */ 367 public void setNegativeItemLabelPositionFallback( 368 ItemLabelPosition position) { 369 this.negativeItemLabelPositionFallback = position; 370 notifyListeners(new RendererChangeEvent(this)); 371 } 372 373 /** 374 * Returns the flag that controls whether or not the base value for the 375 * bars is included in the range calculated by 376 * {@link #findRangeBounds(CategoryDataset)}. 377 * 378 * @return <code>true</code> if the base is included in the range, and 379 * <code>false</code> otherwise. 380 * 381 * @since 1.0.1 382 */ 383 public boolean getIncludeBaseInRange() { 384 return this.includeBaseInRange; 385 } 386 387 /** 388 * Sets the flag that controls whether or not the base value for the bars 389 * is included in the range calculated by 390 * {@link #findRangeBounds(CategoryDataset)}. If the flag is changed, 391 * a {@link RendererChangeEvent} is sent to all registered listeners. 392 * 393 * @param include the new value for the flag. 394 * 395 * @since 1.0.1 396 */ 397 public void setIncludeBaseInRange(boolean include) { 398 if (this.includeBaseInRange != include) { 399 this.includeBaseInRange = include; 400 notifyListeners(new RendererChangeEvent(this)); 401 } 402 } 403 404 /** 405 * Returns the lower clip value. This value is recalculated in the 406 * initialise() method. 407 * 408 * @return The value. 409 */ 410 public double getLowerClip() { 411 // TODO: this attribute should be transferred to the renderer state. 412 return this.lowerClip; 413 } 414 415 /** 416 * Returns the upper clip value. This value is recalculated in the 417 * initialise() method. 418 * 419 * @return The value. 420 */ 421 public double getUpperClip() { 422 // TODO: this attribute should be transferred to the renderer state. 423 return this.upperClip; 424 } 425 426 /** 427 * Initialises the renderer and returns a state object that will be passed 428 * to subsequent calls to the drawItem method. This method gets called 429 * once at the start of the process of drawing a chart. 430 * 431 * @param g2 the graphics device. 432 * @param dataArea the area in which the data is to be plotted. 433 * @param plot the plot. 434 * @param rendererIndex the renderer index. 435 * @param info collects chart rendering information for return to caller. 436 * 437 * @return The renderer state. 438 */ 439 public CategoryItemRendererState initialise(Graphics2D g2, 440 Rectangle2D dataArea, 441 CategoryPlot plot, 442 int rendererIndex, 443 PlotRenderingInfo info) { 444 445 CategoryItemRendererState state = super.initialise(g2, dataArea, plot, 446 rendererIndex, info); 447 448 // get the clipping values... 449 ValueAxis rangeAxis = getRangeAxis(plot, rendererIndex); 450 this.lowerClip = rangeAxis.getRange().getLowerBound(); 451 this.upperClip = rangeAxis.getRange().getUpperBound(); 452 453 // calculate the bar width 454 calculateBarWidth(plot, dataArea, rendererIndex, state); 455 456 return state; 457 458 } 459 460 /** 461 * Calculates the bar width and stores it in the renderer state. 462 * 463 * @param plot the plot. 464 * @param dataArea the data area. 465 * @param rendererIndex the renderer index. 466 * @param state the renderer state. 467 */ 468 protected void calculateBarWidth(CategoryPlot plot, 469 Rectangle2D dataArea, 470 int rendererIndex, 471 CategoryItemRendererState state) { 472 473 CategoryAxis domainAxis = getDomainAxis(plot, rendererIndex); 474 CategoryDataset dataset = plot.getDataset(rendererIndex); 475 if (dataset != null) { 476 int columns = dataset.getColumnCount(); 477 int rows = dataset.getRowCount(); 478 double space = 0.0; 479 PlotOrientation orientation = plot.getOrientation(); 480 if (orientation == PlotOrientation.HORIZONTAL) { 481 space = dataArea.getHeight(); 482 } 483 else if (orientation == PlotOrientation.VERTICAL) { 484 space = dataArea.getWidth(); 485 } 486 double maxWidth = space * getMaximumBarWidth(); 487 double categoryMargin = 0.0; 488 double currentItemMargin = 0.0; 489 if (columns > 1) { 490 categoryMargin = domainAxis.getCategoryMargin(); 491 } 492 if (rows > 1) { 493 currentItemMargin = getItemMargin(); 494 } 495 double used = space * (1 - domainAxis.getLowerMargin() 496 - domainAxis.getUpperMargin() 497 - categoryMargin - currentItemMargin); 498 if ((rows * columns) > 0) { 499 state.setBarWidth(Math.min(used / (rows * columns), maxWidth)); 500 } 501 else { 502 state.setBarWidth(Math.min(used, maxWidth)); 503 } 504 } 505 } 506 507 /** 508 * Calculates the coordinate of the first "side" of a bar. This will be 509 * the minimum x-coordinate for a vertical bar, and the minimum 510 * y-coordinate for a horizontal bar. 511 * 512 * @param plot the plot. 513 * @param orientation the plot orientation. 514 * @param dataArea the data area. 515 * @param domainAxis the domain axis. 516 * @param state the renderer state (has the bar width precalculated). 517 * @param row the row index. 518 * @param column the column index. 519 * 520 * @return The coordinate. 521 */ 522 protected double calculateBarW0(CategoryPlot plot, 523 PlotOrientation orientation, 524 Rectangle2D dataArea, 525 CategoryAxis domainAxis, 526 CategoryItemRendererState state, 527 int row, 528 int column) { 529 // calculate bar width... 530 double space = 0.0; 531 if (orientation == PlotOrientation.HORIZONTAL) { 532 space = dataArea.getHeight(); 533 } 534 else { 535 space = dataArea.getWidth(); 536 } 537 double barW0 = domainAxis.getCategoryStart(column, getColumnCount(), 538 dataArea, plot.getDomainAxisEdge()); 539 int seriesCount = getRowCount(); 540 int categoryCount = getColumnCount(); 541 if (seriesCount > 1) { 542 double seriesGap = space * getItemMargin() 543 / (categoryCount * (seriesCount - 1)); 544 double seriesW = calculateSeriesWidth(space, domainAxis, 545 categoryCount, seriesCount); 546 barW0 = barW0 + row * (seriesW + seriesGap) 547 + (seriesW / 2.0) - (state.getBarWidth() / 2.0); 548 } 549 else { 550 barW0 = domainAxis.getCategoryMiddle(column, getColumnCount(), 551 dataArea, plot.getDomainAxisEdge()) - state.getBarWidth() 552 / 2.0; 553 } 554 return barW0; 555 } 556 557 /** 558 * Calculates the coordinates for the length of a single bar. 559 * 560 * @param value the value represented by the bar. 561 * 562 * @return The coordinates for each end of the bar (or <code>null</code> if 563 * the bar is not visible for the current axis range). 564 */ 565 protected double[] calculateBarL0L1(double value) { 566 double lclip = getLowerClip(); 567 double uclip = getUpperClip(); 568 double barLow = Math.min(this.base, value); 569 double barHigh = Math.max(this.base, value); 570 if (barHigh <= lclip) { // bar is not visible 571 return null; 572 } 573 if (barLow >= uclip) { // bar is not visible 574 return null; 575 } 576 barLow = Math.max(barLow, lclip); 577 barHigh = Math.min(barHigh, uclip); 578 return new double[] {barLow, barHigh}; 579 } 580 581 /** 582 * Returns the range of values the renderer requires to display all the 583 * items from the specified dataset. This takes into account the range 584 * of values in the dataset, plus the flag that determines whether or not 585 * the base value for the bars should be included in the range. 586 * 587 * @param dataset the dataset (<code>null</code> permitted). 588 * 589 * @return The range (or <code>null</code> if the dataset is 590 * <code>null</code> or empty). 591 */ 592 public Range findRangeBounds(CategoryDataset dataset) { 593 Range result = DatasetUtilities.findRangeBounds(dataset); 594 if (result != null) { 595 if (this.includeBaseInRange) { 596 result = Range.expandToInclude(result, this.base); 597 } 598 } 599 return result; 600 } 601 602 /** 603 * Returns a legend item for a series. 604 * 605 * @param datasetIndex the dataset index (zero-based). 606 * @param series the series index (zero-based). 607 * 608 * @return The legend item. 609 */ 610 public LegendItem getLegendItem(int datasetIndex, int series) { 611 612 CategoryPlot cp = getPlot(); 613 if (cp == null) { 614 return null; 615 } 616 617 CategoryDataset dataset; 618 dataset = cp.getDataset(datasetIndex); 619 String label = getLegendItemLabelGenerator().generateLabel(dataset, 620 series); 621 String description = label; 622 String toolTipText = null; 623 if (getLegendItemToolTipGenerator() != null) { 624 toolTipText = getLegendItemToolTipGenerator().generateLabel( 625 dataset, series); 626 } 627 String urlText = null; 628 if (getLegendItemURLGenerator() != null) { 629 urlText = getLegendItemURLGenerator().generateLabel(dataset, 630 series); 631 } 632 Shape shape = new Rectangle2D.Double(-4.0, -4.0, 8.0, 8.0); 633 Paint paint = getSeriesPaint(series); 634 Paint outlinePaint = getSeriesOutlinePaint(series); 635 Stroke outlineStroke = getSeriesOutlineStroke(series); 636 637 return new LegendItem(label, description, toolTipText, urlText, 638 true, shape, true, paint, 639 isDrawBarOutline(), outlinePaint, outlineStroke, 640 false, new Line2D.Float(), new BasicStroke(1.0f), 641 Color.black); 642 } 643 644 /** 645 * Draws the bar for a single (series, category) data item. 646 * 647 * @param g2 the graphics device. 648 * @param state the renderer state. 649 * @param dataArea the data area. 650 * @param plot the plot. 651 * @param domainAxis the domain axis. 652 * @param rangeAxis the range axis. 653 * @param dataset the dataset. 654 * @param row the row index (zero-based). 655 * @param column the column index (zero-based). 656 * @param pass the pass index. 657 */ 658 public void drawItem(Graphics2D g2, 659 CategoryItemRendererState state, 660 Rectangle2D dataArea, 661 CategoryPlot plot, 662 CategoryAxis domainAxis, 663 ValueAxis rangeAxis, 664 CategoryDataset dataset, 665 int row, 666 int column, 667 int pass) { 668 669 // nothing is drawn for null values... 670 Number dataValue = dataset.getValue(row, column); 671 if (dataValue == null) { 672 return; 673 } 674 675 double value = dataValue.doubleValue(); 676 677 PlotOrientation orientation = plot.getOrientation(); 678 double barW0 = calculateBarW0(plot, orientation, dataArea, domainAxis, 679 state, row, column); 680 double[] barL0L1 = calculateBarL0L1(value); 681 if (barL0L1 == null) { 682 return; // the bar is not visible 683 } 684 685 RectangleEdge edge = plot.getRangeAxisEdge(); 686 double transL0 = rangeAxis.valueToJava2D(barL0L1[0], dataArea, edge); 687 double transL1 = rangeAxis.valueToJava2D(barL0L1[1], dataArea, edge); 688 double barL0 = Math.min(transL0, transL1); 689 double barLength = Math.max(Math.abs(transL1 - transL0), 690 getMinimumBarLength()); 691 692 // draw the bar... 693 Rectangle2D bar = null; 694 if (orientation == PlotOrientation.HORIZONTAL) { 695 bar = new Rectangle2D.Double(barL0, barW0, barLength, 696 state.getBarWidth()); 697 } 698 else { 699 bar = new Rectangle2D.Double(barW0, barL0, state.getBarWidth(), 700 barLength); 701 } 702 Paint itemPaint = getItemPaint(row, column); 703 GradientPaintTransformer t = getGradientPaintTransformer(); 704 if (t != null && itemPaint instanceof GradientPaint) { 705 itemPaint = t.transform((GradientPaint) itemPaint, bar); 706 } 707 g2.setPaint(itemPaint); 708 g2.fill(bar); 709 710 // draw the outline... 711 if (isDrawBarOutline() 712 && state.getBarWidth() > BAR_OUTLINE_WIDTH_THRESHOLD) { 713 Stroke stroke = getItemOutlineStroke(row, column); 714 Paint paint = getItemOutlinePaint(row, column); 715 if (stroke != null && paint != null) { 716 g2.setStroke(stroke); 717 g2.setPaint(paint); 718 g2.draw(bar); 719 } 720 } 721 722 CategoryItemLabelGenerator generator 723 = getItemLabelGenerator(row, column); 724 if (generator != null && isItemLabelVisible(row, column)) { 725 drawItemLabel(g2, dataset, row, column, plot, generator, bar, 726 (value < 0.0)); 727 } 728 729 // add an item entity, if this information is being collected 730 EntityCollection entities = state.getEntityCollection(); 731 if (entities != null) { 732 addItemEntity(entities, dataset, row, column, bar); 733 } 734 735 } 736 737 /** 738 * Calculates the available space for each series. 739 * 740 * @param space the space along the entire axis (in Java2D units). 741 * @param axis the category axis. 742 * @param categories the number of categories. 743 * @param series the number of series. 744 * 745 * @return The width of one series. 746 */ 747 protected double calculateSeriesWidth(double space, CategoryAxis axis, 748 int categories, int series) { 749 double factor = 1.0 - getItemMargin() - axis.getLowerMargin() 750 - axis.getUpperMargin(); 751 if (categories > 1) { 752 factor = factor - axis.getCategoryMargin(); 753 } 754 return (space * factor) / (categories * series); 755 } 756 757 /** 758 * Draws an item label. This method is overridden so that the bar can be 759 * used to calculate the label anchor point. 760 * 761 * @param g2 the graphics device. 762 * @param data the dataset. 763 * @param row the row. 764 * @param column the column. 765 * @param plot the plot. 766 * @param generator the label generator. 767 * @param bar the bar. 768 * @param negative a flag indicating a negative value. 769 */ 770 protected void drawItemLabel(Graphics2D g2, 771 CategoryDataset data, 772 int row, 773 int column, 774 CategoryPlot plot, 775 CategoryItemLabelGenerator generator, 776 Rectangle2D bar, 777 boolean negative) { 778 779 String label = generator.generateLabel(data, row, column); 780 if (label == null) { 781 return; // nothing to do 782 } 783 784 Font labelFont = getItemLabelFont(row, column); 785 g2.setFont(labelFont); 786 Paint paint = getItemLabelPaint(row, column); 787 g2.setPaint(paint); 788 789 // find out where to place the label... 790 ItemLabelPosition position = null; 791 if (!negative) { 792 position = getPositiveItemLabelPosition(row, column); 793 } 794 else { 795 position = getNegativeItemLabelPosition(row, column); 796 } 797 798 // work out the label anchor point... 799 Point2D anchorPoint = calculateLabelAnchorPoint( 800 position.getItemLabelAnchor(), bar, plot.getOrientation()); 801 802 if (isInternalAnchor(position.getItemLabelAnchor())) { 803 Shape bounds = TextUtilities.calculateRotatedStringBounds(label, 804 g2, (float) anchorPoint.getX(), (float) anchorPoint.getY(), 805 position.getTextAnchor(), position.getAngle(), 806 position.getRotationAnchor()); 807 808 if (bounds != null) { 809 if (!bar.contains(bounds.getBounds2D())) { 810 if (!negative) { 811 position = getPositiveItemLabelPositionFallback(); 812 } 813 else { 814 position = getNegativeItemLabelPositionFallback(); 815 } 816 if (position != null) { 817 anchorPoint = calculateLabelAnchorPoint( 818 position.getItemLabelAnchor(), bar, 819 plot.getOrientation()); 820 } 821 } 822 } 823 824 } 825 826 if (position != null) { 827 TextUtilities.drawRotatedString(label, g2, 828 (float) anchorPoint.getX(), (float) anchorPoint.getY(), 829 position.getTextAnchor(), position.getAngle(), 830 position.getRotationAnchor()); 831 } 832 } 833 834 /** 835 * Calculates the item label anchor point. 836 * 837 * @param anchor the anchor. 838 * @param bar the bar. 839 * @param orientation the plot orientation. 840 * 841 * @return The anchor point. 842 */ 843 private Point2D calculateLabelAnchorPoint(ItemLabelAnchor anchor, 844 Rectangle2D bar, 845 PlotOrientation orientation) { 846 847 Point2D result = null; 848 double offset = getItemLabelAnchorOffset(); 849 double x0 = bar.getX() - offset; 850 double x1 = bar.getX(); 851 double x2 = bar.getX() + offset; 852 double x3 = bar.getCenterX(); 853 double x4 = bar.getMaxX() - offset; 854 double x5 = bar.getMaxX(); 855 double x6 = bar.getMaxX() + offset; 856 857 double y0 = bar.getMaxY() + offset; 858 double y1 = bar.getMaxY(); 859 double y2 = bar.getMaxY() - offset; 860 double y3 = bar.getCenterY(); 861 double y4 = bar.getMinY() + offset; 862 double y5 = bar.getMinY(); 863 double y6 = bar.getMinY() - offset; 864 865 if (anchor == ItemLabelAnchor.CENTER) { 866 result = new Point2D.Double(x3, y3); 867 } 868 else if (anchor == ItemLabelAnchor.INSIDE1) { 869 result = new Point2D.Double(x4, y4); 870 } 871 else if (anchor == ItemLabelAnchor.INSIDE2) { 872 result = new Point2D.Double(x4, y4); 873 } 874 else if (anchor == ItemLabelAnchor.INSIDE3) { 875 result = new Point2D.Double(x4, y3); 876 } 877 else if (anchor == ItemLabelAnchor.INSIDE4) { 878 result = new Point2D.Double(x4, y2); 879 } 880 else if (anchor == ItemLabelAnchor.INSIDE5) { 881 result = new Point2D.Double(x4, y2); 882 } 883 else if (anchor == ItemLabelAnchor.INSIDE6) { 884 result = new Point2D.Double(x3, y2); 885 } 886 else if (anchor == ItemLabelAnchor.INSIDE7) { 887 result = new Point2D.Double(x2, y2); 888 } 889 else if (anchor == ItemLabelAnchor.INSIDE8) { 890 result = new Point2D.Double(x2, y2); 891 } 892 else if (anchor == ItemLabelAnchor.INSIDE9) { 893 result = new Point2D.Double(x2, y3); 894 } 895 else if (anchor == ItemLabelAnchor.INSIDE10) { 896 result = new Point2D.Double(x2, y4); 897 } 898 else if (anchor == ItemLabelAnchor.INSIDE11) { 899 result = new Point2D.Double(x2, y4); 900 } 901 else if (anchor == ItemLabelAnchor.INSIDE12) { 902 result = new Point2D.Double(x3, y4); 903 } 904 else if (anchor == ItemLabelAnchor.OUTSIDE1) { 905 result = new Point2D.Double(x5, y6); 906 } 907 else if (anchor == ItemLabelAnchor.OUTSIDE2) { 908 result = new Point2D.Double(x6, y5); 909 } 910 else if (anchor == ItemLabelAnchor.OUTSIDE3) { 911 result = new Point2D.Double(x6, y3); 912 } 913 else if (anchor == ItemLabelAnchor.OUTSIDE4) { 914 result = new Point2D.Double(x6, y1); 915 } 916 else if (anchor == ItemLabelAnchor.OUTSIDE5) { 917 result = new Point2D.Double(x5, y0); 918 } 919 else if (anchor == ItemLabelAnchor.OUTSIDE6) { 920 result = new Point2D.Double(x3, y0); 921 } 922 else if (anchor == ItemLabelAnchor.OUTSIDE7) { 923 result = new Point2D.Double(x1, y0); 924 } 925 else if (anchor == ItemLabelAnchor.OUTSIDE8) { 926 result = new Point2D.Double(x0, y1); 927 } 928 else if (anchor == ItemLabelAnchor.OUTSIDE9) { 929 result = new Point2D.Double(x0, y3); 930 } 931 else if (anchor == ItemLabelAnchor.OUTSIDE10) { 932 result = new Point2D.Double(x0, y5); 933 } 934 else if (anchor == ItemLabelAnchor.OUTSIDE11) { 935 result = new Point2D.Double(x1, y6); 936 } 937 else if (anchor == ItemLabelAnchor.OUTSIDE12) { 938 result = new Point2D.Double(x3, y6); 939 } 940 941 return result; 942 943 } 944 945 /** 946 * Returns <code>true</code> if the specified anchor point is inside a bar. 947 * 948 * @param anchor the anchor point. 949 * 950 * @return A boolean. 951 */ 952 private boolean isInternalAnchor(ItemLabelAnchor anchor) { 953 return anchor == ItemLabelAnchor.CENTER 954 || anchor == ItemLabelAnchor.INSIDE1 955 || anchor == ItemLabelAnchor.INSIDE2 956 || anchor == ItemLabelAnchor.INSIDE3 957 || anchor == ItemLabelAnchor.INSIDE4 958 || anchor == ItemLabelAnchor.INSIDE5 959 || anchor == ItemLabelAnchor.INSIDE6 960 || anchor == ItemLabelAnchor.INSIDE7 961 || anchor == ItemLabelAnchor.INSIDE8 962 || anchor == ItemLabelAnchor.INSIDE9 963 || anchor == ItemLabelAnchor.INSIDE10 964 || anchor == ItemLabelAnchor.INSIDE11 965 || anchor == ItemLabelAnchor.INSIDE12; 966 } 967 968 /** 969 * Tests this instance for equality with an arbitrary object. 970 * 971 * @param obj the object (<code>null</code> permitted). 972 * 973 * @return A boolean. 974 */ 975 public boolean equals(Object obj) { 976 977 if (obj == this) { 978 return true; 979 } 980 if (!(obj instanceof BarRenderer)) { 981 return false; 982 } 983 if (!super.equals(obj)) { 984 return false; 985 } 986 BarRenderer that = (BarRenderer) obj; 987 if (this.base != that.base) { 988 return false; 989 } 990 if (this.itemMargin != that.itemMargin) { 991 return false; 992 } 993 if (this.drawBarOutline != that.drawBarOutline) { 994 return false; 995 } 996 if (this.maximumBarWidth != that.maximumBarWidth) { 997 return false; 998 } 999 if (this.minimumBarLength != that.minimumBarLength) { 1000 return false; 1001 } 1002 if (!ObjectUtilities.equal(this.gradientPaintTransformer, 1003 that.gradientPaintTransformer)) { 1004 return false; 1005 } 1006 if (!ObjectUtilities.equal(this.positiveItemLabelPositionFallback, 1007 that.positiveItemLabelPositionFallback)) { 1008 return false; 1009 } 1010 if (!ObjectUtilities.equal(this.negativeItemLabelPositionFallback, 1011 that.negativeItemLabelPositionFallback)) { 1012 return false; 1013 } 1014 return true; 1015 1016 } 1017 1018 }