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