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

   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:  * LineRenderer3D.java
  29:  * -------------------
  30:  * (C) Copyright 2004-2008, by Tobias Selb and Contributors.
  31:  *
  32:  * Original Author:  Tobias Selb (http://www.uepselon.com);
  33:  * Contributor(s):   David Gilbert (for Object Refinery Limited);
  34:  *
  35:  * Changes
  36:  * -------
  37:  * 15-Oct-2004 : Version 1 (TS);
  38:  * 05-Nov-2004 : Modified drawItem() signature (DG);
  39:  * 11-Nov-2004 : Now uses ShapeUtilities class to translate shapes (DG);
  40:  * 26-Jan-2005 : Update for changes in super class (DG);
  41:  * 13-Apr-2005 : Check item visibility in drawItem() method (DG);
  42:  * 09-Jun-2005 : Use addItemEntity() in drawItem() method (DG);
  43:  * 10-Jun-2005 : Fixed capitalisation of setXOffset() and setYOffset() (DG);
  44:  * ------------- JFREECHART 1.0.x ---------------------------------------------
  45:  * 01-Dec-2006 : Fixed equals() and serialization (DG);
  46:  * 17-Jan-2007 : Fixed bug in drawDomainGridline() method and added
  47:  *               argument check to setWallPaint() (DG);
  48:  * 03-Apr-2007 : Fixed bugs in drawBackground() method (DG);
  49:  * 16-Oct-2007 : Fixed bug in range marker drawing (DG);
  50:  *
  51:  */
  52: 
  53: package org.jfree.chart.renderer.category;
  54: 
  55: import java.awt.AlphaComposite;
  56: import java.awt.Color;
  57: import java.awt.Composite;
  58: import java.awt.Graphics2D;
  59: import java.awt.Image;
  60: import java.awt.Paint;
  61: import java.awt.Shape;
  62: import java.awt.Stroke;
  63: import java.awt.geom.GeneralPath;
  64: import java.awt.geom.Line2D;
  65: import java.awt.geom.Rectangle2D;
  66: import java.io.IOException;
  67: import java.io.ObjectInputStream;
  68: import java.io.ObjectOutputStream;
  69: import java.io.Serializable;
  70: 
  71: import org.jfree.chart.Effect3D;
  72: import org.jfree.chart.axis.CategoryAxis;
  73: import org.jfree.chart.axis.ValueAxis;
  74: import org.jfree.chart.entity.EntityCollection;
  75: import org.jfree.chart.event.RendererChangeEvent;
  76: import org.jfree.chart.plot.CategoryPlot;
  77: import org.jfree.chart.plot.Marker;
  78: import org.jfree.chart.plot.PlotOrientation;
  79: import org.jfree.chart.plot.ValueMarker;
  80: import org.jfree.data.Range;
  81: import org.jfree.data.category.CategoryDataset;
  82: import org.jfree.io.SerialUtilities;
  83: import org.jfree.util.PaintUtilities;
  84: import org.jfree.util.ShapeUtilities;
  85: 
  86: /**
  87:  * A line renderer with a 3D effect.
  88:  */
  89: public class LineRenderer3D extends LineAndShapeRenderer
  90:                             implements Effect3D, Serializable {
  91: 
  92:     /** For serialization. */
  93:     private static final long serialVersionUID = 5467931468380928736L;
  94: 
  95:     /** The default x-offset for the 3D effect. */
  96:     public static final double DEFAULT_X_OFFSET = 12.0;
  97: 
  98:     /** The default y-offset for the 3D effect. */
  99:     public static final double DEFAULT_Y_OFFSET = 8.0;
 100: 
 101:     /** The default wall paint. */
 102:     public static final Paint DEFAULT_WALL_PAINT = new Color(0xDD, 0xDD, 0xDD);
 103: 
 104:     /** The size of x-offset for the 3D effect. */
 105:     private double xOffset;
 106: 
 107:     /** The size of y-offset for the 3D effect. */
 108:     private double yOffset;
 109: 
 110:     /** The paint used to shade the left and lower 3D wall. */
 111:     private transient Paint wallPaint;
 112: 
 113:     /**
 114:      * Creates a new renderer.
 115:      */
 116:     public LineRenderer3D() {
 117:         super(true, false);  //Create a line renderer only
 118:         this.xOffset = DEFAULT_X_OFFSET;
 119:         this.yOffset = DEFAULT_Y_OFFSET;
 120:         this.wallPaint = DEFAULT_WALL_PAINT;
 121:     }
 122: 
 123:     /**
 124:      * Returns the x-offset for the 3D effect.
 125:      *
 126:      * @return The x-offset.
 127:      *
 128:      * @see #setXOffset(double)
 129:      * @see #getYOffset()
 130:      */
 131:     public double getXOffset() {
 132:         return this.xOffset;
 133:     }
 134: 
 135:     /**
 136:      * Returns the y-offset for the 3D effect.
 137:      *
 138:      * @return The y-offset.
 139:      *
 140:      * @see #setYOffset(double)
 141:      * @see #getXOffset()
 142:      */
 143:     public double getYOffset() {
 144:         return this.yOffset;
 145:     }
 146: 
 147:     /**
 148:      * Sets the x-offset and sends a {@link RendererChangeEvent} to all
 149:      * registered listeners.
 150:      *
 151:      * @param xOffset  the x-offset.
 152:      *
 153:      * @see #getXOffset()
 154:      */
 155:     public void setXOffset(double xOffset) {
 156:         this.xOffset = xOffset;
 157:         fireChangeEvent();
 158:     }
 159: 
 160:     /**
 161:      * Sets the y-offset and sends a {@link RendererChangeEvent} to all
 162:      * registered listeners.
 163:      *
 164:      * @param yOffset  the y-offset.
 165:      *
 166:      * @see #getYOffset()
 167:      */
 168:     public void setYOffset(double yOffset) {
 169:         this.yOffset = yOffset;
 170:         fireChangeEvent();
 171:     }
 172: 
 173:     /**
 174:      * Returns the paint used to highlight the left and bottom wall in the plot
 175:      * background.
 176:      *
 177:      * @return The paint.
 178:      *
 179:      * @see #setWallPaint(Paint)
 180:      */
 181:     public Paint getWallPaint() {
 182:         return this.wallPaint;
 183:     }
 184: 
 185:     /**
 186:      * Sets the paint used to hightlight the left and bottom walls in the plot
 187:      * background, and sends a {@link RendererChangeEvent} to all
 188:      * registered listeners.
 189:      *
 190:      * @param paint  the paint (<code>null</code> not permitted).
 191:      *
 192:      * @see #getWallPaint()
 193:      */
 194:     public void setWallPaint(Paint paint) {
 195:         if (paint == null) {
 196:             throw new IllegalArgumentException("Null 'paint' argument.");
 197:         }
 198:         this.wallPaint = paint;
 199:         fireChangeEvent();
 200:     }
 201: 
 202:     /**
 203:      * Draws the background for the plot.
 204:      *
 205:      * @param g2  the graphics device.
 206:      * @param plot  the plot.
 207:      * @param dataArea  the area inside the axes.
 208:      */
 209:     public void drawBackground(Graphics2D g2, CategoryPlot plot,
 210:                                Rectangle2D dataArea) {
 211: 
 212:         float x0 = (float) dataArea.getX();
 213:         float x1 = x0 + (float) Math.abs(this.xOffset);
 214:         float x3 = (float) dataArea.getMaxX();
 215:         float x2 = x3 - (float) Math.abs(this.xOffset);
 216: 
 217:         float y0 = (float) dataArea.getMaxY();
 218:         float y1 = y0 - (float) Math.abs(this.yOffset);
 219:         float y3 = (float) dataArea.getMinY();
 220:         float y2 = y3 + (float) Math.abs(this.yOffset);
 221: 
 222:         GeneralPath clip = new GeneralPath();
 223:         clip.moveTo(x0, y0);
 224:         clip.lineTo(x0, y2);
 225:         clip.lineTo(x1, y3);
 226:         clip.lineTo(x3, y3);
 227:         clip.lineTo(x3, y1);
 228:         clip.lineTo(x2, y0);
 229:         clip.closePath();
 230: 
 231:         Composite originalComposite = g2.getComposite();
 232:         g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
 233:                 plot.getBackgroundAlpha()));
 234: 
 235:         // fill background...
 236:         Paint backgroundPaint = plot.getBackgroundPaint();
 237:         if (backgroundPaint != null) {
 238:             g2.setPaint(backgroundPaint);
 239:             g2.fill(clip);
 240:         }
 241: 
 242:         GeneralPath leftWall = new GeneralPath();
 243:         leftWall.moveTo(x0, y0);
 244:         leftWall.lineTo(x0, y2);
 245:         leftWall.lineTo(x1, y3);
 246:         leftWall.lineTo(x1, y1);
 247:         leftWall.closePath();
 248:         g2.setPaint(getWallPaint());
 249:         g2.fill(leftWall);
 250: 
 251:         GeneralPath bottomWall = new GeneralPath();
 252:         bottomWall.moveTo(x0, y0);
 253:         bottomWall.lineTo(x1, y1);
 254:         bottomWall.lineTo(x3, y1);
 255:         bottomWall.lineTo(x2, y0);
 256:         bottomWall.closePath();
 257:         g2.setPaint(getWallPaint());
 258:         g2.fill(bottomWall);
 259: 
 260:         // higlight the background corners...
 261:         g2.setPaint(Color.lightGray);
 262:         Line2D corner = new Line2D.Double(x0, y0, x1, y1);
 263:         g2.draw(corner);
 264:         corner.setLine(x1, y1, x1, y3);
 265:         g2.draw(corner);
 266:         corner.setLine(x1, y1, x3, y1);
 267:         g2.draw(corner);
 268: 
 269:         // draw background image, if there is one...
 270:         Image backgroundImage = plot.getBackgroundImage();
 271:         if (backgroundImage != null) {
 272:             Rectangle2D adjusted = new Rectangle2D.Double(dataArea.getX()
 273:                     + getXOffset(), dataArea.getY(),
 274:                     dataArea.getWidth() - getXOffset(),
 275:                     dataArea.getHeight() - getYOffset());
 276:             plot.drawBackgroundImage(g2, adjusted);
 277:         }
 278: 
 279:         g2.setComposite(originalComposite);
 280: 
 281:     }
 282: 
 283:     /**
 284:      * Draws the outline for the plot.
 285:      *
 286:      * @param g2  the graphics device.
 287:      * @param plot  the plot.
 288:      * @param dataArea  the area inside the axes.
 289:      */
 290:     public void drawOutline(Graphics2D g2, CategoryPlot plot,
 291:                             Rectangle2D dataArea) {
 292: 
 293:         float x0 = (float) dataArea.getX();
 294:         float x1 = x0 + (float) Math.abs(this.xOffset);
 295:         float x3 = (float) dataArea.getMaxX();
 296:         float x2 = x3 - (float) Math.abs(this.xOffset);
 297: 
 298:         float y0 = (float) dataArea.getMaxY();
 299:         float y1 = y0 - (float) Math.abs(this.yOffset);
 300:         float y3 = (float) dataArea.getMinY();
 301:         float y2 = y3 + (float) Math.abs(this.yOffset);
 302: 
 303:         GeneralPath clip = new GeneralPath();
 304:         clip.moveTo(x0, y0);
 305:         clip.lineTo(x0, y2);
 306:         clip.lineTo(x1, y3);
 307:         clip.lineTo(x3, y3);
 308:         clip.lineTo(x3, y1);
 309:         clip.lineTo(x2, y0);
 310:         clip.closePath();
 311: 
 312:         // put an outline around the data area...
 313:         Stroke outlineStroke = plot.getOutlineStroke();
 314:         Paint outlinePaint = plot.getOutlinePaint();
 315:         if ((outlineStroke != null) && (outlinePaint != null)) {
 316:             g2.setStroke(outlineStroke);
 317:             g2.setPaint(outlinePaint);
 318:             g2.draw(clip);
 319:         }
 320: 
 321:     }
 322: 
 323:     /**
 324:      * Draws a grid line against the domain axis.
 325:      *
 326:      * @param g2  the graphics device.
 327:      * @param plot  the plot.
 328:      * @param dataArea  the area for plotting data (not yet adjusted for any
 329:      *                  3D effect).
 330:      * @param value  the Java2D value at which the grid line should be drawn.
 331:      *
 332:      */
 333:     public void drawDomainGridline(Graphics2D g2,
 334:                                    CategoryPlot plot,
 335:                                    Rectangle2D dataArea,
 336:                                    double value) {
 337: 
 338:         Line2D line1 = null;
 339:         Line2D line2 = null;
 340:         PlotOrientation orientation = plot.getOrientation();
 341:         if (orientation == PlotOrientation.HORIZONTAL) {
 342:             double y0 = value;
 343:             double y1 = value - getYOffset();
 344:             double x0 = dataArea.getMinX();
 345:             double x1 = x0 + getXOffset();
 346:             double x2 = dataArea.getMaxX();
 347:             line1 = new Line2D.Double(x0, y0, x1, y1);
 348:             line2 = new Line2D.Double(x1, y1, x2, y1);
 349:         }
 350:         else if (orientation == PlotOrientation.VERTICAL) {
 351:             double x0 = value;
 352:             double x1 = value + getXOffset();
 353:             double y0 = dataArea.getMaxY();
 354:             double y1 = y0 - getYOffset();
 355:             double y2 = dataArea.getMinY();
 356:             line1 = new Line2D.Double(x0, y0, x1, y1);
 357:             line2 = new Line2D.Double(x1, y1, x1, y2);
 358:         }
 359:         g2.setPaint(plot.getDomainGridlinePaint());
 360:         g2.setStroke(plot.getDomainGridlineStroke());
 361:         g2.draw(line1);
 362:         g2.draw(line2);
 363: 
 364:     }
 365: 
 366:     /**
 367:      * Draws a grid line against the range axis.
 368:      *
 369:      * @param g2  the graphics device.
 370:      * @param plot  the plot.
 371:      * @param axis  the value axis.
 372:      * @param dataArea  the area for plotting data (not yet adjusted for any
 373:      *                  3D effect).
 374:      * @param value  the value at which the grid line should be drawn.
 375:      *
 376:      */
 377:     public void drawRangeGridline(Graphics2D g2,
 378:                                   CategoryPlot plot,
 379:                                   ValueAxis axis,
 380:                                   Rectangle2D dataArea,
 381:                                   double value) {
 382: 
 383:         Range range = axis.getRange();
 384: 
 385:         if (!range.contains(value)) {
 386:             return;
 387:         }
 388: 
 389:         Rectangle2D adjusted = new Rectangle2D.Double(dataArea.getX(),
 390:                 dataArea.getY() + getYOffset(),
 391:                 dataArea.getWidth() - getXOffset(),
 392:                 dataArea.getHeight() - getYOffset());
 393: 
 394:         Line2D line1 = null;
 395:         Line2D line2 = null;
 396:         PlotOrientation orientation = plot.getOrientation();
 397:         if (orientation == PlotOrientation.HORIZONTAL) {
 398:             double x0 = axis.valueToJava2D(value, adjusted,
 399:                     plot.getRangeAxisEdge());
 400:             double x1 = x0 + getXOffset();
 401:             double y0 = dataArea.getMaxY();
 402:             double y1 = y0 - getYOffset();
 403:             double y2 = dataArea.getMinY();
 404:             line1 = new Line2D.Double(x0, y0, x1, y1);
 405:             line2 = new Line2D.Double(x1, y1, x1, y2);
 406:         }
 407:         else if (orientation == PlotOrientation.VERTICAL) {
 408:             double y0 = axis.valueToJava2D(value, adjusted,
 409:                     plot.getRangeAxisEdge());
 410:             double y1 = y0 - getYOffset();
 411:             double x0 = dataArea.getMinX();
 412:             double x1 = x0 + getXOffset();
 413:             double x2 = dataArea.getMaxX();
 414:             line1 = new Line2D.Double(x0, y0, x1, y1);
 415:             line2 = new Line2D.Double(x1, y1, x2, y1);
 416:         }
 417:         g2.setPaint(plot.getRangeGridlinePaint());
 418:         g2.setStroke(plot.getRangeGridlineStroke());
 419:         g2.draw(line1);
 420:         g2.draw(line2);
 421: 
 422:     }
 423: 
 424:     /**
 425:      * Draws a range marker.
 426:      *
 427:      * @param g2  the graphics device.
 428:      * @param plot  the plot.
 429:      * @param axis  the value axis.
 430:      * @param marker  the marker.
 431:      * @param dataArea  the area for plotting data (not including 3D effect).
 432:      */
 433:     public void drawRangeMarker(Graphics2D g2,
 434:                                 CategoryPlot plot,
 435:                                 ValueAxis axis,
 436:                                 Marker marker,
 437:                                 Rectangle2D dataArea) {
 438: 
 439:         Rectangle2D adjusted = new Rectangle2D.Double(dataArea.getX(),
 440:                 dataArea.getY() + getYOffset(),
 441:                 dataArea.getWidth() - getXOffset(),
 442:                 dataArea.getHeight() - getYOffset());
 443: 
 444:         if (marker instanceof ValueMarker) {
 445:             ValueMarker vm = (ValueMarker) marker;
 446:             double value = vm.getValue();
 447:             Range range = axis.getRange();
 448:             if (!range.contains(value)) {
 449:                 return;
 450:             }
 451: 
 452:             GeneralPath path = null;
 453:             PlotOrientation orientation = plot.getOrientation();
 454:             if (orientation == PlotOrientation.HORIZONTAL) {
 455:                 float x = (float) axis.valueToJava2D(value, adjusted,
 456:                         plot.getRangeAxisEdge());
 457:                 float y = (float) adjusted.getMaxY();
 458:                 path = new GeneralPath();
 459:                 path.moveTo(x, y);
 460:                 path.lineTo((float) (x + getXOffset()),
 461:                         y - (float) getYOffset());
 462:                 path.lineTo((float) (x + getXOffset()),
 463:                         (float) (adjusted.getMinY() - getYOffset()));
 464:                 path.lineTo(x, (float) adjusted.getMinY());
 465:                 path.closePath();
 466:             }
 467:             else if (orientation == PlotOrientation.VERTICAL) {
 468:                 float y = (float) axis.valueToJava2D(value, adjusted,
 469:                         plot.getRangeAxisEdge());
 470:                 float x = (float) dataArea.getX();
 471:                 path = new GeneralPath();
 472:                 path.moveTo(x, y);
 473:                 path.lineTo(x + (float) this.xOffset, y - (float) this.yOffset);
 474:                 path.lineTo((float) (adjusted.getMaxX() + this.xOffset),
 475:                         y - (float) this.yOffset);
 476:                 path.lineTo((float) (adjusted.getMaxX()), y);
 477:                 path.closePath();
 478:             }
 479:             g2.setPaint(marker.getPaint());
 480:             g2.fill(path);
 481:             g2.setPaint(marker.getOutlinePaint());
 482:             g2.draw(path);
 483:         }
 484:         else {
 485:             super.drawRangeMarker(g2, plot, axis, marker, adjusted);
 486:             // TODO: draw the interval marker with a 3D effect
 487:         }
 488:     }
 489: 
 490:    /**
 491:      * Draw a single data item.
 492:      *
 493:      * @param g2  the graphics device.
 494:      * @param state  the renderer state.
 495:      * @param dataArea  the area in which the data is drawn.
 496:      * @param plot  the plot.
 497:      * @param domainAxis  the domain axis.
 498:      * @param rangeAxis  the range axis.
 499:      * @param dataset  the dataset.
 500:      * @param row  the row index (zero-based).
 501:      * @param column  the column index (zero-based).
 502:      * @param pass  the pass index.
 503:      */
 504:     public void drawItem(Graphics2D g2,
 505:                          CategoryItemRendererState state,
 506:                          Rectangle2D dataArea,
 507:                          CategoryPlot plot,
 508:                          CategoryAxis domainAxis,
 509:                          ValueAxis rangeAxis,
 510:                          CategoryDataset dataset,
 511:                          int row,
 512:                          int column,
 513:                          int pass) {
 514: 
 515:         if (!getItemVisible(row, column)) {
 516:             return;
 517:         }
 518: 
 519:         // nothing is drawn for null...
 520:         Number v = dataset.getValue(row, column);
 521:         if (v == null) {
 522:             return;
 523:         }
 524: 
 525:         Rectangle2D adjusted = new Rectangle2D.Double(dataArea.getX(),
 526:                 dataArea.getY() + getYOffset(),
 527:                 dataArea.getWidth() - getXOffset(),
 528:                 dataArea.getHeight() - getYOffset());
 529: 
 530:         PlotOrientation orientation = plot.getOrientation();
 531: 
 532:         // current data point...
 533:         double x1 = domainAxis.getCategoryMiddle(column, getColumnCount(),
 534:                 adjusted, plot.getDomainAxisEdge());
 535:         double value = v.doubleValue();
 536:         double y1 = rangeAxis.valueToJava2D(value, adjusted,
 537:                 plot.getRangeAxisEdge());
 538: 
 539:         Shape shape = getItemShape(row, column);
 540:         if (orientation == PlotOrientation.HORIZONTAL) {
 541:             shape = ShapeUtilities.createTranslatedShape(shape, y1, x1);
 542:         }
 543:         else if (orientation == PlotOrientation.VERTICAL) {
 544:             shape = ShapeUtilities.createTranslatedShape(shape, x1, y1);
 545:         }
 546: 
 547:         if (getItemLineVisible(row, column)) {
 548:             if (column != 0) {
 549: 
 550:                 Number previousValue = dataset.getValue(row, column - 1);
 551:                 if (previousValue != null) {
 552: 
 553:                     // previous data point...
 554:                     double previous = previousValue.doubleValue();
 555:                     double x0 = domainAxis.getCategoryMiddle(column - 1,
 556:                             getColumnCount(), adjusted,
 557:                             plot.getDomainAxisEdge());
 558:                     double y0 = rangeAxis.valueToJava2D(previous, adjusted,
 559:                             plot.getRangeAxisEdge());
 560: 
 561:                     double x2 = x0 + getXOffset();
 562:                     double y2 = y0 - getYOffset();
 563:                     double x3 = x1 + getXOffset();
 564:                     double y3 = y1 - getYOffset();
 565: 
 566:                     GeneralPath clip = new GeneralPath();
 567: 
 568:                     if (orientation == PlotOrientation.HORIZONTAL) {
 569:                         clip.moveTo((float) y0, (float) x0);
 570:                         clip.lineTo((float) y1, (float) x1);
 571:                         clip.lineTo((float) y3, (float) x3);
 572:                         clip.lineTo((float) y2, (float) x2);
 573:                         clip.lineTo((float) y0, (float) x0);
 574:                         clip.closePath();
 575:                     }
 576:                     else if (orientation == PlotOrientation.VERTICAL) {
 577:                         clip.moveTo((float) x0, (float) y0);
 578:                         clip.lineTo((float) x1, (float) y1);
 579:                         clip.lineTo((float) x3, (float) y3);
 580:                         clip.lineTo((float) x2, (float) y2);
 581:                         clip.lineTo((float) x0, (float) y0);
 582:                         clip.closePath();
 583:                     }
 584: 
 585:                     g2.setPaint(getItemPaint(row, column));
 586:                     g2.fill(clip);
 587:                     g2.setStroke(getItemOutlineStroke(row, column));
 588:                     g2.setPaint(getItemOutlinePaint(row, column));
 589:                     g2.draw(clip);
 590:                 }
 591:             }
 592:         }
 593: 
 594:         // draw the item label if there is one...
 595:         if (isItemLabelVisible(row, column)) {
 596:             drawItemLabel(g2, orientation, dataset, row, column, x1, y1,
 597:                     (value < 0.0));
 598:         }
 599: 
 600:         // add an item entity, if this information is being collected
 601:         EntityCollection entities = state.getEntityCollection();
 602:         if (entities != null) {
 603:             addItemEntity(entities, dataset, row, column, shape);
 604:         }
 605: 
 606:     }
 607: 
 608:     /**
 609:      * Checks this renderer for equality with an arbitrary object.
 610:      *
 611:      * @param obj  the object (<code>null</code> permitted).
 612:      *
 613:      * @return A boolean.
 614:      */
 615:     public boolean equals(Object obj) {
 616:         if (obj == this) {
 617:             return true;
 618:         }
 619:         if (!(obj instanceof LineRenderer3D)) {
 620:             return false;
 621:         }
 622:         LineRenderer3D that = (LineRenderer3D) obj;
 623:         if (this.xOffset != that.xOffset) {
 624:             return false;
 625:         }
 626:         if (this.yOffset != that.yOffset) {
 627:             return false;
 628:         }
 629:         if (!PaintUtilities.equal(this.wallPaint, that.wallPaint)) {
 630:             return false;
 631:         }
 632:         return super.equals(obj);
 633:     }
 634: 
 635:     /**
 636:      * Provides serialization support.
 637:      *
 638:      * @param stream  the output stream.
 639:      *
 640:      * @throws IOException  if there is an I/O error.
 641:      */
 642:     private void writeObject(ObjectOutputStream stream) throws IOException {
 643:         stream.defaultWriteObject();
 644:         SerialUtilities.writePaint(this.wallPaint, stream);
 645:     }
 646: 
 647:     /**
 648:      * Provides serialization support.
 649:      *
 650:      * @param stream  the input stream.
 651:      *
 652:      * @throws IOException  if there is an I/O error.
 653:      * @throws ClassNotFoundException  if there is a classpath problem.
 654:      */
 655:     private void readObject(ObjectInputStream stream)
 656:             throws IOException, ClassNotFoundException {
 657:         stream.defaultReadObject();
 658:         this.wallPaint = SerialUtilities.readPaint(stream);
 659:     }
 660: 
 661: }