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

   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:  * BarRenderer3D.java
  29:  * ------------------
  30:  * (C) Copyright 2001-2007, by Serge V. Grachov and Contributors.
  31:  *
  32:  * Original Author:  Serge V. Grachov;
  33:  * Contributor(s):   David Gilbert (for Object Refinery Limited);
  34:  *                   Tin Luu;
  35:  *                   Milo Simpson;
  36:  *                   Richard Atkinson;
  37:  *                   Rich Unger;
  38:  *                   Christian W. Zuckschwerdt;
  39:  *
  40:  * Changes
  41:  * -------
  42:  * 31-Oct-2001 : First version, contributed by Serge V. Grachov (DG);
  43:  * 15-Nov-2001 : Modified to allow for null data values (DG);
  44:  * 13-Dec-2001 : Added tooltips (DG);
  45:  * 16-Jan-2002 : Added fix for single category or single series datasets, 
  46:  *               pointed out by Taoufik Romdhane (DG);
  47:  * 24-May-2002 : Incorporated tooltips into chart entities (DG);
  48:  * 11-Jun-2002 : Added check for (permitted) null info object, bug and fix 
  49:  *               reported by David Basten.  Also updated Javadocs. (DG);
  50:  * 19-Jun-2002 : Added code to draw labels on bars (TL);
  51:  * 26-Jun-2002 : Added bar clipping to avoid PRExceptions (DG);
  52:  * 05-Aug-2002 : Small modification to drawCategoryItem method to support URLs 
  53:  *               for HTML image maps (RA);
  54:  * 06-Aug-2002 : Value labels now use number formatter, thanks to Milo 
  55:  *               Simpson (DG);
  56:  * 08-Aug-2002 : Applied fixed in bug id 592218 (DG);
  57:  * 20-Sep-2002 : Added fix for categoryPaint by Rich Unger, and fixed errors 
  58:  *               reported by Checkstyle (DG);
  59:  * 24-Oct-2002 : Amendments for changes in CategoryDataset interface and 
  60:  *               CategoryToolTipGenerator interface (DG);
  61:  * 05-Nov-2002 : Replaced references to CategoryDataset with TableDataset (DG);
  62:  * 06-Nov-2002 : Moved to the com.jrefinery.chart.renderer package (DG);
  63:  * 28-Jan-2003 : Added an attribute to control the shading of the left and 
  64:  *               bottom walls in the plot background (DG);
  65:  * 25-Mar-2003 : Implemented Serializable (DG);
  66:  * 10-Apr-2003 : Removed category paint usage (DG);
  67:  * 13-May-2003 : Renamed VerticalBarRenderer3D --> BarRenderer3D and merged with
  68:  *               HorizontalBarRenderer3D (DG);
  69:  * 30-Jul-2003 : Modified entity constructor (CZ);
  70:  * 19-Aug-2003 : Implemented Cloneable and PublicCloneable (DG);
  71:  * 07-Oct-2003 : Added renderer state (DG);
  72:  * 08-Oct-2003 : Removed clipping (replaced with flag in CategoryPlot to 
  73:  *               control order in which the data items are processed) (DG);
  74:  * 20-Oct-2003 : Fixed bug (outline stroke not being used for bar 
  75:  *               outlines) (DG);
  76:  * 21-Oct-2003 : Bar width moved into CategoryItemRendererState (DG);
  77:  * 24-Nov-2003 : Fixed bug 846324 (item labels not showing) (DG);
  78:  * 27-Nov-2003 : Added code to respect maxBarWidth setting (DG);
  79:  * 02-Feb-2004 : Fixed bug where 'drawBarOutline' flag is not respected (DG);
  80:  * 10-Feb-2004 : Small change to drawItem() method to make cut-and-paste 
  81:  *               overriding easier (DG);
  82:  * 04-Oct-2004 : Fixed bug with item label positioning when plot alignment is 
  83:  *               horizontal (DG);
  84:  * 05-Nov-2004 : Modified drawItem() signature (DG);
  85:  * 20-Apr-2005 : Renamed CategoryLabelGenerator 
  86:  *               --> CategoryItemLabelGenerator (DG);
  87:  * 25-Apr-2005 : Override initialise() method to fix bug 1189642 (DG);
  88:  * 09-Jun-2005 : Use addEntityItem from super class (DG);
  89:  * ------------- JFREECHART 1.0.x ---------------------------------------------
  90:  * 07-Dec-2006 : Implemented equals() override (DG);
  91:  * 17-Jan-2007 : Fixed bug in drawDomainGridline() method (DG);
  92:  * 03-Apr-2007 : Fixed bugs in drawBackground() method (DG);
  93:  * 16-Oct-2007 : Fixed bug in range marker drawing (DG);
  94:  * 
  95:  */
  96: 
  97: package org.jfree.chart.renderer.category;
  98: 
  99: import java.awt.AlphaComposite;
 100: import java.awt.Color;
 101: import java.awt.Composite;
 102: import java.awt.Font;
 103: import java.awt.Graphics2D;
 104: import java.awt.Image;
 105: import java.awt.Paint;
 106: import java.awt.Stroke;
 107: import java.awt.geom.GeneralPath;
 108: import java.awt.geom.Line2D;
 109: import java.awt.geom.Point2D;
 110: import java.awt.geom.Rectangle2D;
 111: import java.io.IOException;
 112: import java.io.ObjectInputStream;
 113: import java.io.ObjectOutputStream;
 114: import java.io.Serializable;
 115: 
 116: import org.jfree.chart.Effect3D;
 117: import org.jfree.chart.axis.CategoryAxis;
 118: import org.jfree.chart.axis.ValueAxis;
 119: import org.jfree.chart.entity.EntityCollection;
 120: import org.jfree.chart.event.RendererChangeEvent;
 121: import org.jfree.chart.labels.CategoryItemLabelGenerator;
 122: import org.jfree.chart.labels.ItemLabelAnchor;
 123: import org.jfree.chart.labels.ItemLabelPosition;
 124: import org.jfree.chart.plot.CategoryPlot;
 125: import org.jfree.chart.plot.Marker;
 126: import org.jfree.chart.plot.Plot;
 127: import org.jfree.chart.plot.PlotOrientation;
 128: import org.jfree.chart.plot.PlotRenderingInfo;
 129: import org.jfree.chart.plot.ValueMarker;
 130: import org.jfree.data.Range;
 131: import org.jfree.data.category.CategoryDataset;
 132: import org.jfree.io.SerialUtilities;
 133: import org.jfree.text.TextUtilities;
 134: import org.jfree.ui.LengthAdjustmentType;
 135: import org.jfree.ui.RectangleAnchor;
 136: import org.jfree.ui.RectangleEdge;
 137: import org.jfree.ui.TextAnchor;
 138: import org.jfree.util.PaintUtilities;
 139: import org.jfree.util.PublicCloneable;
 140: 
 141: /**
 142:  * A renderer for bars with a 3D effect, for use with the 
 143:  * {@link org.jfree.chart.plot.CategoryPlot} class.
 144:  */
 145: public class BarRenderer3D extends BarRenderer 
 146:                            implements Effect3D, Cloneable, PublicCloneable, 
 147:                                       Serializable {
 148: 
 149:     /** For serialization. */
 150:     private static final long serialVersionUID = 7686976503536003636L;
 151:     
 152:     /** The default x-offset for the 3D effect. */
 153:     public static final double DEFAULT_X_OFFSET = 12.0;
 154: 
 155:     /** The default y-offset for the 3D effect. */
 156:     public static final double DEFAULT_Y_OFFSET = 8.0;
 157: 
 158:     /** The default wall paint. */
 159:     public static final Paint DEFAULT_WALL_PAINT = new Color(0xDD, 0xDD, 0xDD);
 160: 
 161:     /** The size of x-offset for the 3D effect. */
 162:     private double xOffset;
 163: 
 164:     /** The size of y-offset for the 3D effect. */
 165:     private double yOffset;
 166: 
 167:     /** The paint used to shade the left and lower 3D wall. */
 168:     private transient Paint wallPaint;
 169: 
 170:     /**
 171:      * Default constructor, creates a renderer with a default '3D effect'.
 172:      */
 173:     public BarRenderer3D() {
 174:         this(DEFAULT_X_OFFSET, DEFAULT_Y_OFFSET);
 175:     }
 176: 
 177:     /**
 178:      * Constructs a new renderer with the specified '3D effect'.
 179:      *
 180:      * @param xOffset  the x-offset for the 3D effect.
 181:      * @param yOffset  the y-offset for the 3D effect.
 182:      */
 183:     public BarRenderer3D(double xOffset, double yOffset) {
 184: 
 185:         super();
 186:         this.xOffset = xOffset;
 187:         this.yOffset = yOffset;
 188:         this.wallPaint = DEFAULT_WALL_PAINT;
 189:         // set the default item label positions
 190:         ItemLabelPosition p1 = new ItemLabelPosition(ItemLabelAnchor.INSIDE12, 
 191:                 TextAnchor.TOP_CENTER);
 192:         setBasePositiveItemLabelPosition(p1);
 193:         ItemLabelPosition p2 = new ItemLabelPosition(ItemLabelAnchor.INSIDE12, 
 194:                 TextAnchor.TOP_CENTER);
 195:         setBaseNegativeItemLabelPosition(p2);
 196: 
 197:     }
 198: 
 199:     /**
 200:      * Returns the x-offset for the 3D effect.
 201:      *
 202:      * @return The 3D effect.
 203:      * 
 204:      * @see #getYOffset()
 205:      */
 206:     public double getXOffset() {
 207:         return this.xOffset;
 208:     }
 209: 
 210:     /**
 211:      * Returns the y-offset for the 3D effect.
 212:      *
 213:      * @return The 3D effect.
 214:      */
 215:     public double getYOffset() {
 216:         return this.yOffset;
 217:     }
 218: 
 219:     /**
 220:      * Returns the paint used to highlight the left and bottom wall in the plot
 221:      * background.
 222:      *
 223:      * @return The paint.
 224:      * 
 225:      * @see #setWallPaint(Paint)
 226:      */
 227:     public Paint getWallPaint() {
 228:         return this.wallPaint;
 229:     }
 230: 
 231:     /**
 232:      * Sets the paint used to hightlight the left and bottom walls in the plot
 233:      * background, and sends a {@link RendererChangeEvent} to all registered
 234:      * listeners.
 235:      *
 236:      * @param paint  the paint (<code>null</code> not permitted).
 237:      * 
 238:      * @see #getWallPaint()
 239:      */
 240:     public void setWallPaint(Paint paint) {
 241:         if (paint == null) {
 242:             throw new IllegalArgumentException("Null 'paint' argument.");
 243:         }
 244:         this.wallPaint = paint;
 245:         fireChangeEvent();
 246:     }
 247: 
 248: 
 249:     /**
 250:      * Initialises the renderer and returns a state object that will be passed 
 251:      * to subsequent calls to the drawItem method.  This method gets called 
 252:      * once at the start of the process of drawing a chart.
 253:      *
 254:      * @param g2  the graphics device.
 255:      * @param dataArea  the area in which the data is to be plotted.
 256:      * @param plot  the plot.
 257:      * @param rendererIndex  the renderer index.
 258:      * @param info  collects chart rendering information for return to caller.
 259:      * 
 260:      * @return The renderer state.
 261:      */
 262:     public CategoryItemRendererState initialise(Graphics2D g2,
 263:                                                 Rectangle2D dataArea,
 264:                                                 CategoryPlot plot,
 265:                                                 int rendererIndex,
 266:                                                 PlotRenderingInfo info) {
 267: 
 268:         Rectangle2D adjusted = new Rectangle2D.Double(dataArea.getX(), 
 269:                 dataArea.getY() + getYOffset(), dataArea.getWidth() 
 270:                 - getXOffset(), dataArea.getHeight() - getYOffset());
 271:         CategoryItemRendererState state = super.initialise(g2, adjusted, plot, 
 272:                 rendererIndex, info);
 273:         return state;
 274:         
 275:     }
 276:     
 277:     /**
 278:      * Draws the background for the plot.
 279:      *
 280:      * @param g2  the graphics device.
 281:      * @param plot  the plot.
 282:      * @param dataArea  the area inside the axes.
 283:      */
 284:     public void drawBackground(Graphics2D g2, CategoryPlot plot, 
 285:                                Rectangle2D dataArea) {
 286: 
 287:         float x0 = (float) dataArea.getX();
 288:         float x1 = x0 + (float) Math.abs(this.xOffset);
 289:         float x3 = (float) dataArea.getMaxX();
 290:         float x2 = x3 - (float) Math.abs(this.xOffset);
 291: 
 292:         float y0 = (float) dataArea.getMaxY();
 293:         float y1 = y0 - (float) Math.abs(this.yOffset);
 294:         float y3 = (float) dataArea.getMinY();
 295:         float y2 = y3 + (float) Math.abs(this.yOffset);
 296: 
 297:         GeneralPath clip = new GeneralPath();
 298:         clip.moveTo(x0, y0);
 299:         clip.lineTo(x0, y2);
 300:         clip.lineTo(x1, y3);
 301:         clip.lineTo(x3, y3);
 302:         clip.lineTo(x3, y1);
 303:         clip.lineTo(x2, y0);
 304:         clip.closePath();
 305: 
 306:         Composite originalComposite = g2.getComposite();
 307:         g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
 308:                 plot.getBackgroundAlpha()));
 309:         
 310:         // fill background...
 311:         Paint backgroundPaint = plot.getBackgroundPaint();
 312:         if (backgroundPaint != null) {
 313:             g2.setPaint(backgroundPaint);
 314:             g2.fill(clip);
 315:         }
 316: 
 317:         GeneralPath leftWall = new GeneralPath();
 318:         leftWall.moveTo(x0, y0);
 319:         leftWall.lineTo(x0, y2);
 320:         leftWall.lineTo(x1, y3);
 321:         leftWall.lineTo(x1, y1);
 322:         leftWall.closePath();
 323:         g2.setPaint(getWallPaint());
 324:         g2.fill(leftWall);
 325: 
 326:         GeneralPath bottomWall = new GeneralPath();
 327:         bottomWall.moveTo(x0, y0);
 328:         bottomWall.lineTo(x1, y1);
 329:         bottomWall.lineTo(x3, y1);
 330:         bottomWall.lineTo(x2, y0);
 331:         bottomWall.closePath();
 332:         g2.setPaint(getWallPaint());
 333:         g2.fill(bottomWall);
 334: 
 335:         // highlight the background corners...
 336:         g2.setPaint(Color.lightGray);
 337:         Line2D corner = new Line2D.Double(x0, y0, x1, y1);
 338:         g2.draw(corner);
 339:         corner.setLine(x1, y1, x1, y3);
 340:         g2.draw(corner);
 341:         corner.setLine(x1, y1, x3, y1);
 342:         g2.draw(corner);
 343:                 
 344:         // draw background image, if there is one...
 345:         Image backgroundImage = plot.getBackgroundImage();
 346:         if (backgroundImage != null) {
 347:             Rectangle2D adjusted = new Rectangle2D.Double(dataArea.getX() 
 348:                     + getXOffset(), dataArea.getY(), 
 349:                     dataArea.getWidth() - getXOffset(), 
 350:                     dataArea.getHeight() - getYOffset());
 351:             plot.drawBackgroundImage(g2, adjusted);
 352:         }
 353:         
 354:         g2.setComposite(originalComposite);
 355: 
 356:     }
 357: 
 358:     /**
 359:      * Draws the outline for the plot.
 360:      *
 361:      * @param g2  the graphics device.
 362:      * @param plot  the plot.
 363:      * @param dataArea  the area inside the axes.
 364:      */
 365:     public void drawOutline(Graphics2D g2, CategoryPlot plot, 
 366:                             Rectangle2D dataArea) {
 367: 
 368:         float x0 = (float) dataArea.getX();
 369:         float x1 = x0 + (float) Math.abs(this.xOffset);
 370:         float x3 = (float) dataArea.getMaxX();
 371:         float x2 = x3 - (float) Math.abs(this.xOffset);
 372: 
 373:         float y0 = (float) dataArea.getMaxY();
 374:         float y1 = y0 - (float) Math.abs(this.yOffset);
 375:         float y3 = (float) dataArea.getMinY();
 376:         float y2 = y3 + (float) Math.abs(this.yOffset);
 377: 
 378:         GeneralPath clip = new GeneralPath();
 379:         clip.moveTo(x0, y0);
 380:         clip.lineTo(x0, y2);
 381:         clip.lineTo(x1, y3);
 382:         clip.lineTo(x3, y3);
 383:         clip.lineTo(x3, y1);
 384:         clip.lineTo(x2, y0);
 385:         clip.closePath();
 386: 
 387:         // put an outline around the data area...
 388:         Stroke outlineStroke = plot.getOutlineStroke();
 389:         Paint outlinePaint = plot.getOutlinePaint();
 390:         if ((outlineStroke != null) && (outlinePaint != null)) {
 391:             g2.setStroke(outlineStroke);
 392:             g2.setPaint(outlinePaint);
 393:             g2.draw(clip);
 394:         }
 395: 
 396:     }
 397: 
 398:     /**
 399:      * Draws a grid line against the domain axis.
 400:      *
 401:      * @param g2  the graphics device.
 402:      * @param plot  the plot.
 403:      * @param dataArea  the area for plotting data (not yet adjusted for any 
 404:      *                  3D effect).
 405:      * @param value  the Java2D value at which the grid line should be drawn.
 406:      *
 407:      */
 408:     public void drawDomainGridline(Graphics2D g2,
 409:                                    CategoryPlot plot,
 410:                                    Rectangle2D dataArea,
 411:                                    double value) {
 412: 
 413:         Line2D line1 = null;
 414:         Line2D line2 = null;
 415:         PlotOrientation orientation = plot.getOrientation();
 416:         if (orientation == PlotOrientation.HORIZONTAL) {
 417:             double y0 = value;
 418:             double y1 = value - getYOffset();
 419:             double x0 = dataArea.getMinX();
 420:             double x1 = x0 + getXOffset();
 421:             double x2 = dataArea.getMaxX();
 422:             line1 = new Line2D.Double(x0, y0, x1, y1);
 423:             line2 = new Line2D.Double(x1, y1, x2, y1);
 424:         }
 425:         else if (orientation == PlotOrientation.VERTICAL) {
 426:             double x0 = value;
 427:             double x1 = value + getXOffset();
 428:             double y0 = dataArea.getMaxY();
 429:             double y1 = y0 - getYOffset();
 430:             double y2 = dataArea.getMinY();
 431:             line1 = new Line2D.Double(x0, y0, x1, y1);
 432:             line2 = new Line2D.Double(x1, y1, x1, y2);
 433:         }
 434:         Paint paint = plot.getDomainGridlinePaint();
 435:         Stroke stroke = plot.getDomainGridlineStroke();
 436:         g2.setPaint(paint != null ? paint : Plot.DEFAULT_OUTLINE_PAINT);
 437:         g2.setStroke(stroke != null ? stroke : Plot.DEFAULT_OUTLINE_STROKE);
 438:         g2.draw(line1);
 439:         g2.draw(line2);
 440: 
 441:     }
 442: 
 443:     /**
 444:      * Draws a grid line against the range axis.
 445:      *
 446:      * @param g2  the graphics device.
 447:      * @param plot  the plot.
 448:      * @param axis  the value axis.
 449:      * @param dataArea  the area for plotting data (not yet adjusted for any 
 450:      *                  3D effect).
 451:      * @param value  the value at which the grid line should be drawn.
 452:      *
 453:      */
 454:     public void drawRangeGridline(Graphics2D g2,
 455:                                   CategoryPlot plot,
 456:                                   ValueAxis axis,
 457:                                   Rectangle2D dataArea,
 458:                                   double value) {
 459: 
 460:         Range range = axis.getRange();
 461: 
 462:         if (!range.contains(value)) {
 463:             return;
 464:         }
 465: 
 466:         Rectangle2D adjusted = new Rectangle2D.Double(dataArea.getX(),
 467:                 dataArea.getY() + getYOffset(), dataArea.getWidth() 
 468:                 - getXOffset(), dataArea.getHeight() - getYOffset());
 469: 
 470:         Line2D line1 = null;
 471:         Line2D line2 = null;
 472:         PlotOrientation orientation = plot.getOrientation();
 473:         if (orientation == PlotOrientation.HORIZONTAL) {
 474:             double x0 = axis.valueToJava2D(value, adjusted, 
 475:                     plot.getRangeAxisEdge());
 476:             double x1 = x0 + getXOffset();
 477:             double y0 = dataArea.getMaxY();
 478:             double y1 = y0 - getYOffset();
 479:             double y2 = dataArea.getMinY();
 480:             line1 = new Line2D.Double(x0, y0, x1, y1);
 481:             line2 = new Line2D.Double(x1, y1, x1, y2);
 482:         }
 483:         else if (orientation == PlotOrientation.VERTICAL) {
 484:             double y0 = axis.valueToJava2D(value, adjusted, 
 485:                     plot.getRangeAxisEdge());
 486:             double y1 = y0 - getYOffset();
 487:             double x0 = dataArea.getMinX();
 488:             double x1 = x0 + getXOffset();
 489:             double x2 = dataArea.getMaxX();
 490:             line1 = new Line2D.Double(x0, y0, x1, y1);
 491:             line2 = new Line2D.Double(x1, y1, x2, y1);
 492:         }
 493:         Paint paint = plot.getRangeGridlinePaint();
 494:         Stroke stroke = plot.getRangeGridlineStroke();
 495:         g2.setPaint(paint != null ? paint : Plot.DEFAULT_OUTLINE_PAINT);
 496:         g2.setStroke(stroke != null ? stroke : Plot.DEFAULT_OUTLINE_STROKE);
 497:         g2.draw(line1);
 498:         g2.draw(line2);
 499: 
 500:     }
 501: 
 502:     /**
 503:      * Draws a range marker.
 504:      *
 505:      * @param g2  the graphics device.
 506:      * @param plot  the plot.
 507:      * @param axis  the value axis.
 508:      * @param marker  the marker.
 509:      * @param dataArea  the area for plotting data (not including 3D effect).
 510:      */
 511:     public void drawRangeMarker(Graphics2D g2,
 512:                                 CategoryPlot plot,
 513:                                 ValueAxis axis,
 514:                                 Marker marker,
 515:                                 Rectangle2D dataArea) {
 516: 
 517: 
 518:         Rectangle2D adjusted = new Rectangle2D.Double(dataArea.getX(), 
 519:                 dataArea.getY() + getYOffset(), dataArea.getWidth() 
 520:                 - getXOffset(), dataArea.getHeight() - getYOffset());
 521:         if (marker instanceof ValueMarker) {
 522:             ValueMarker vm = (ValueMarker) marker;
 523:             double value = vm.getValue();
 524:             Range range = axis.getRange();
 525:             if (!range.contains(value)) {
 526:                 return;
 527:             }
 528: 
 529:             GeneralPath path = null;
 530:             PlotOrientation orientation = plot.getOrientation();
 531:             if (orientation == PlotOrientation.HORIZONTAL) {
 532:                 float x = (float) axis.valueToJava2D(value, adjusted, 
 533:                         plot.getRangeAxisEdge());
 534:                 float y = (float) adjusted.getMaxY();
 535:                 path = new GeneralPath();
 536:                 path.moveTo(x, y);
 537:                 path.lineTo((float) (x + getXOffset()), 
 538:                         y - (float) getYOffset());
 539:                 path.lineTo((float) (x + getXOffset()), 
 540:                         (float) (adjusted.getMinY() - getYOffset()));
 541:                 path.lineTo(x, (float) adjusted.getMinY());
 542:                 path.closePath();
 543:             }
 544:             else if (orientation == PlotOrientation.VERTICAL) {
 545:                 float y = (float) axis.valueToJava2D(value, adjusted, 
 546:                         plot.getRangeAxisEdge());
 547:                 float x = (float) dataArea.getX();
 548:                 path = new GeneralPath();
 549:                 path.moveTo(x, y);
 550:                 path.lineTo(x + (float) this.xOffset, y - (float) this.yOffset);
 551:                 path.lineTo((float) (adjusted.getMaxX() + this.xOffset), 
 552:                         y - (float) this.yOffset);
 553:                 path.lineTo((float) (adjusted.getMaxX()), y);
 554:                 path.closePath();
 555:             }
 556:             g2.setPaint(marker.getPaint());
 557:             g2.fill(path);
 558:             g2.setPaint(marker.getOutlinePaint());
 559:             g2.draw(path);
 560:         
 561:             String label = marker.getLabel();
 562:             RectangleAnchor anchor = marker.getLabelAnchor();
 563:             if (label != null) {
 564:                 Font labelFont = marker.getLabelFont();
 565:                 g2.setFont(labelFont);
 566:                 g2.setPaint(marker.getLabelPaint());
 567:                 Point2D coordinates = calculateRangeMarkerTextAnchorPoint(
 568:                         g2, orientation, dataArea, path.getBounds2D(), 
 569:                         marker.getLabelOffset(), LengthAdjustmentType.EXPAND, 
 570:                         anchor);
 571:                 TextUtilities.drawAlignedString(label, g2, 
 572:                         (float) coordinates.getX(), (float) coordinates.getY(), 
 573:                         marker.getLabelTextAnchor());
 574:             }
 575:         
 576:         }
 577:         else {
 578:             super.drawRangeMarker(g2, plot, axis, marker, adjusted);
 579:             // TODO: draw the interval marker with a 3D effect
 580:         }
 581:     }
 582: 
 583:     /**
 584:      * Draws a 3D bar to represent one data item.
 585:      *
 586:      * @param g2  the graphics device.
 587:      * @param state  the renderer state.
 588:      * @param dataArea  the area for plotting the data.
 589:      * @param plot  the plot.
 590:      * @param domainAxis  the domain axis.
 591:      * @param rangeAxis  the range axis.
 592:      * @param dataset  the dataset.
 593:      * @param row  the row index (zero-based).
 594:      * @param column  the column index (zero-based).
 595:      * @param pass  the pass index.
 596:      */
 597:     public void drawItem(Graphics2D g2,
 598:                          CategoryItemRendererState state,
 599:                          Rectangle2D dataArea,
 600:                          CategoryPlot plot,
 601:                          CategoryAxis domainAxis,
 602:                          ValueAxis rangeAxis,
 603:                          CategoryDataset dataset,
 604:                          int row,
 605:                          int column,
 606:                          int pass) {
 607:     
 608:         // check the value we are plotting...
 609:         Number dataValue = dataset.getValue(row, column);
 610:         if (dataValue == null) {
 611:             return;
 612:         }
 613:         
 614:         double value = dataValue.doubleValue();
 615:         
 616:         Rectangle2D adjusted = new Rectangle2D.Double(dataArea.getX(),
 617:                 dataArea.getY() + getYOffset(), 
 618:                 dataArea.getWidth() - getXOffset(), 
 619:                 dataArea.getHeight() - getYOffset());
 620: 
 621:         PlotOrientation orientation = plot.getOrientation();
 622:         
 623:         double barW0 = calculateBarW0(plot, orientation, adjusted, domainAxis, 
 624:                 state, row, column);
 625:         double[] barL0L1 = calculateBarL0L1(value);
 626:         if (barL0L1 == null) {
 627:             return;  // the bar is not visible
 628:         }
 629: 
 630:         RectangleEdge edge = plot.getRangeAxisEdge();
 631:         double transL0 = rangeAxis.valueToJava2D(barL0L1[0], adjusted, edge);
 632:         double transL1 = rangeAxis.valueToJava2D(barL0L1[1], adjusted, edge);
 633:         double barL0 = Math.min(transL0, transL1);
 634:         double barLength = Math.abs(transL1 - transL0);
 635:         
 636:         // draw the bar...
 637:         Rectangle2D bar = null;
 638:         if (orientation == PlotOrientation.HORIZONTAL) {
 639:             bar = new Rectangle2D.Double(barL0, barW0, barLength, 
 640:                     state.getBarWidth());
 641:         }
 642:         else {
 643:             bar = new Rectangle2D.Double(barW0, barL0, state.getBarWidth(), 
 644:                     barLength);
 645:         }
 646:         Paint itemPaint = getItemPaint(row, column);
 647:         g2.setPaint(itemPaint);
 648:         g2.fill(bar);
 649: 
 650:         double x0 = bar.getMinX();
 651:         double x1 = x0 + getXOffset();
 652:         double x2 = bar.getMaxX();
 653:         double x3 = x2 + getXOffset();
 654:         
 655:         double y0 = bar.getMinY() - getYOffset();
 656:         double y1 = bar.getMinY();
 657:         double y2 = bar.getMaxY() - getYOffset();
 658:         double y3 = bar.getMaxY();
 659:         
 660:         GeneralPath bar3dRight = null;
 661:         GeneralPath bar3dTop = null;
 662:         if (barLength > 0.0) {
 663:             bar3dRight = new GeneralPath();
 664:             bar3dRight.moveTo((float) x2, (float) y3);
 665:             bar3dRight.lineTo((float) x2, (float) y1);
 666:             bar3dRight.lineTo((float) x3, (float) y0);
 667:             bar3dRight.lineTo((float) x3, (float) y2);
 668:             bar3dRight.closePath();
 669: 
 670:             if (itemPaint instanceof Color) {
 671:                 g2.setPaint(((Color) itemPaint).darker());
 672:             }
 673:             g2.fill(bar3dRight);
 674:         }
 675: 
 676:         bar3dTop = new GeneralPath();
 677:         bar3dTop.moveTo((float) x0, (float) y1);
 678:         bar3dTop.lineTo((float) x1, (float) y0);
 679:         bar3dTop.lineTo((float) x3, (float) y0);
 680:         bar3dTop.lineTo((float) x2, (float) y1);
 681:         bar3dTop.closePath();
 682:         g2.fill(bar3dTop);
 683: 
 684:         if (isDrawBarOutline() 
 685:                 && state.getBarWidth() > BAR_OUTLINE_WIDTH_THRESHOLD) {
 686:             g2.setStroke(getItemOutlineStroke(row, column));
 687:             g2.setPaint(getItemOutlinePaint(row, column));
 688:             g2.draw(bar);
 689:             if (bar3dRight != null) {
 690:                 g2.draw(bar3dRight);
 691:             }
 692:             if (bar3dTop != null) {
 693:                 g2.draw(bar3dTop);
 694:             }
 695:         }
 696: 
 697:         CategoryItemLabelGenerator generator 
 698:             = getItemLabelGenerator(row, column);
 699:         if (generator != null && isItemLabelVisible(row, column)) {
 700:             drawItemLabel(g2, dataset, row, column, plot, generator, bar, 
 701:                     (value < 0.0));
 702:         }        
 703: 
 704:         // add an item entity, if this information is being collected
 705:         EntityCollection entities = state.getEntityCollection();
 706:         if (entities != null) {
 707:             GeneralPath barOutline = new GeneralPath();
 708:             barOutline.moveTo((float) x0, (float) y3);
 709:             barOutline.lineTo((float) x0, (float) y1);
 710:             barOutline.lineTo((float) x1, (float) y0);
 711:             barOutline.lineTo((float) x3, (float) y0);
 712:             barOutline.lineTo((float) x3, (float) y2);
 713:             barOutline.lineTo((float) x2, (float) y3);
 714:             barOutline.closePath();
 715:             addItemEntity(entities, dataset, row, column, barOutline);
 716:         }
 717: 
 718:     }
 719:     
 720:     /**
 721:      * Tests this renderer for equality with an arbitrary object.
 722:      * 
 723:      * @param obj  the object (<code>null</code> permitted).
 724:      * 
 725:      * @return A boolean.
 726:      */
 727:     public boolean equals(Object obj) {
 728:         if (obj == this) {
 729:             return true;
 730:         }
 731:         if (!(obj instanceof BarRenderer3D)) {
 732:             return false;
 733:         }
 734:         BarRenderer3D that = (BarRenderer3D) obj;
 735:         if (this.xOffset != that.xOffset) {
 736:             return false;
 737:         }
 738:         if (this.yOffset != that.yOffset) {
 739:             return false;
 740:         }
 741:         if (!PaintUtilities.equal(this.wallPaint, that.wallPaint)) {
 742:             return false;
 743:         }
 744:         return super.equals(obj);
 745:     }
 746: 
 747:     /**
 748:      * Provides serialization support.
 749:      *
 750:      * @param stream  the output stream.
 751:      *
 752:      * @throws IOException  if there is an I/O error.
 753:      */
 754:     private void writeObject(ObjectOutputStream stream) throws IOException {
 755:         stream.defaultWriteObject();
 756:         SerialUtilities.writePaint(this.wallPaint, stream);
 757:     }
 758: 
 759:     /**
 760:      * Provides serialization support.
 761:      *
 762:      * @param stream  the input stream.
 763:      *
 764:      * @throws IOException  if there is an I/O error.
 765:      * @throws ClassNotFoundException  if there is a classpath problem.
 766:      */
 767:     private void readObject(ObjectInputStream stream) 
 768:         throws IOException, ClassNotFoundException {
 769:         stream.defaultReadObject();
 770:         this.wallPaint = SerialUtilities.readPaint(stream);
 771:     }
 772: 
 773: }