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