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