Source for org.jfree.chart.renderer.category.BarRenderer

   1: /* ===========================================================
   2:  * JFreeChart : a free chart library for the Java(tm) platform
   3:  * ===========================================================
   4:  *
   5:  * (C) Copyright 2000-2007, by Object Refinery Limited and Contributors.
   6:  *
   7:  * Project Info:  http://www.jfree.org/jfreechart/index.html
   8:  *
   9:  * This library is free software; you can redistribute it and/or modify it 
  10:  * under the terms of the GNU Lesser General Public License as published by 
  11:  * the Free Software Foundation; either version 2.1 of the License, or 
  12:  * (at your option) any later version.
  13:  *
  14:  * This library is distributed in the hope that it will be useful, but 
  15:  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
  16:  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 
  17:  * License for more details.
  18:  *
  19:  * You should have received a copy of the GNU Lesser General Public
  20:  * License along with this library; if not, write to the Free Software
  21:  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, 
  22:  * USA.  
  23:  *
  24:  * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 
  25:  * in the United States and other countries.]
  26:  *
  27:  * ----------------
  28:  * BarRenderer.java
  29:  * ----------------
  30:  * (C) Copyright 2002-2007, by Object Refinery Limited.
  31:  *
  32:  * Original Author:  David Gilbert (for Object Refinery Limited);
  33:  * Contributor(s):   Christian W. Zuckschwerdt;
  34:  *
  35:  * $Id: BarRenderer.java,v 1.13.2.13 2007/01/09 15:57:02 mungady Exp $
  36:  *
  37:  * Changes
  38:  * -------
  39:  * 14-Mar-2002 : Version 1 (DG);
  40:  * 23-May-2002 : Added tooltip generator to renderer (DG);
  41:  * 29-May-2002 : Moved tooltip generator to abstract super-class (DG);
  42:  * 25-Jun-2002 : Changed constructor to protected and removed redundant 
  43:  *               code (DG);
  44:  * 26-Jun-2002 : Added axis to initialise method, and record upper and lower 
  45:  *               clip values (DG);
  46:  * 24-Sep-2002 : Added getLegendItem() method (DG);
  47:  * 09-Oct-2002 : Modified constructor to include URL generator (DG);
  48:  * 05-Nov-2002 : Base dataset is now TableDataset not CategoryDataset (DG);
  49:  * 10-Jan-2003 : Moved get/setItemMargin() method up from subclasses (DG);
  50:  * 17-Jan-2003 : Moved plot classes into a separate package (DG);
  51:  * 25-Mar-2003 : Implemented Serializable (DG);
  52:  * 01-May-2003 : Modified clipping to allow for dual axes and datasets (DG);
  53:  * 12-May-2003 : Merged horizontal and vertical bar renderers (DG);
  54:  * 12-Jun-2003 : Updates for item labels (DG);
  55:  * 30-Jul-2003 : Modified entity constructor (CZ);
  56:  * 02-Sep-2003 : Changed initialise method to fix bug 790407 (DG);
  57:  * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG);
  58:  * 07-Oct-2003 : Added renderer state (DG);
  59:  * 27-Oct-2003 : Merged drawHorizontalItem() and drawVerticalItem() 
  60:  *               methods (DG);
  61:  * 28-Oct-2003 : Added support for gradient paint on bars (DG);
  62:  * 14-Nov-2003 : Added 'maxBarWidth' attribute (DG);
  63:  * 10-Feb-2004 : Small changes inside drawItem() method to ease cut-and-paste 
  64:  *               overriding (DG);
  65:  * 19-Mar-2004 : Fixed bug introduced with separation of tool tip and item 
  66:  *               label generators.  Fixed equals() method (DG);
  67:  * 11-May-2004 : Fix for null pointer exception (bug id 951127) (DG);
  68:  * 05-Nov-2004 : Modified drawItem() signature (DG);
  69:  * 26-Jan-2005 : Provided override for getLegendItem() method (DG);
  70:  * 20-Apr-2005 : Generate legend labels, tooltips and URLs (DG);
  71:  * 18-May-2005 : Added configurable base value (DG);
  72:  * 09-Jun-2005 : Use addItemEntity() method from superclass (DG);
  73:  * 01-Dec-2005 : Update legend item to use/not use outline (DG);
  74:  * ------------: JFreeChart 1.0.x ---------------------------------------------
  75:  * 06-Dec-2005 : Fixed bug 1374222 (JDK 1.4 specific code) (DG);
  76:  * 11-Jan-2006 : Fixed bug 1401856 (bad rendering for non-zero base) (DG);
  77:  * 04-Aug-2006 : Fixed bug 1467706 (missing item labels for zero value 
  78:  *               bars) (DG);
  79:  * 04-Dec-2006 : Fixed bug in rendering to non-primary axis (DG);
  80:  * 13-Dec-2006 : Add support for GradientPaint display in legend items (DG);
  81:  * 
  82:  */
  83: 
  84: package org.jfree.chart.renderer.category;
  85: 
  86: import java.awt.BasicStroke;
  87: import java.awt.Color;
  88: import java.awt.Font;
  89: import java.awt.GradientPaint;
  90: import java.awt.Graphics2D;
  91: import java.awt.Paint;
  92: import java.awt.Shape;
  93: import java.awt.Stroke;
  94: import java.awt.geom.Line2D;
  95: import java.awt.geom.Point2D;
  96: import java.awt.geom.Rectangle2D;
  97: import java.io.Serializable;
  98: 
  99: 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: }