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