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