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