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-2006, by Object Refinery Limited and Contributors.
   6:  *
   7:  * Project Info:  http://www.jfree.org/jfreechart/index.html
   8:  *
   9:  * This library is free software; you can redistribute it and/or modify it 
  10:  * under the terms of the GNU Lesser General Public License as published by 
  11:  * the Free Software Foundation; either version 2.1 of the License, or 
  12:  * (at your option) any later version.
  13:  *
  14:  * This library is distributed in the hope that it will be useful, but 
  15:  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
  16:  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 
  17:  * License for more details.
  18:  *
  19:  * You should have received a copy of the GNU Lesser General Public
  20:  * License along with this library; if not, write to the Free Software
  21:  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, 
  22:  * USA.  
  23:  *
  24:  * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 
  25:  * in the United States and other countries.]
  26:  *
  27:  * -------------------
  28:  * LineRenderer3D.java
  29:  * -------------------
  30:  * (C) Copyright 2004-2006, by Tobias Selb and Contributors.
  31:  *
  32:  * Original Author:  Tobias Selb;
  33:  * Contributor(s):   David Gilbert (for Object Refinery Limited);
  34:  *
  35:  * $Id: LineRenderer3D.java,v 1.10.2.5 2006/08/04 11:30:39 mungady Exp $
  36:  *
  37:  * Changes
  38:  * -------
  39:  * 15-Oct-2004 : Version 1 (TS);
  40:  * 05-Nov-2004 : Modified drawItem() signature (DG);
  41:  * 11-Nov-2004 : Now uses ShapeUtilities class to translate shapes (DG);
  42:  * 26-Jan-2005 : Update for changes in super class (DG);
  43:  * 13-Apr-2005 : Check item visibility in drawItem() method (DG);
  44:  * 09-Jun-2005 : Use addItemEntity() in drawItem() method (DG);
  45:  * 10-Jun-2005 : Fixed capitalisation of setXOffset() and setYOffset() (DG);
  46:  * 
  47:  */
  48: 
  49: package org.jfree.chart.renderer.category;
  50: 
  51: import java.awt.AlphaComposite;
  52: import java.awt.Color;
  53: import java.awt.Composite;
  54: import java.awt.Graphics2D;
  55: import java.awt.Image;
  56: import java.awt.Paint;
  57: import java.awt.Shape;
  58: import java.awt.Stroke;
  59: import java.awt.geom.GeneralPath;
  60: import java.awt.geom.Line2D;
  61: import java.awt.geom.Rectangle2D;
  62: import java.io.Serializable;
  63: 
  64: import org.jfree.chart.Effect3D;
  65: import org.jfree.chart.axis.CategoryAxis;
  66: import org.jfree.chart.axis.ValueAxis;
  67: import org.jfree.chart.entity.EntityCollection;
  68: import org.jfree.chart.event.RendererChangeEvent;
  69: import org.jfree.chart.plot.CategoryPlot;
  70: import org.jfree.chart.plot.Marker;
  71: import org.jfree.chart.plot.PlotOrientation;
  72: import org.jfree.chart.plot.ValueMarker;
  73: import org.jfree.data.Range;
  74: import org.jfree.data.category.CategoryDataset;
  75: import org.jfree.util.ShapeUtilities;
  76: 
  77: /**
  78:  * A line renderer with a 3D effect.
  79:  * 
  80:  * @author Tobias Selb (http://www.uepselon.com) 
  81:  */
  82: public class LineRenderer3D extends LineAndShapeRenderer 
  83:                             implements Effect3D, Serializable {
  84:    
  85:     /** For serialization. */
  86:     private static final long serialVersionUID = 5467931468380928736L;
  87:     
  88:     /** The default x-offset for the 3D effect. */
  89:     public static final double DEFAULT_X_OFFSET = 12.0;
  90: 
  91:     /** The default y-offset for the 3D effect. */
  92:     public static final double DEFAULT_Y_OFFSET = 8.0;
  93:    
  94:     /** The default wall paint. */
  95:     public static final Paint DEFAULT_WALL_PAINT = new Color(0xDD, 0xDD, 0xDD);
  96:    
  97:     /** The size of x-offset for the 3D effect. */
  98:     private double xOffset;
  99: 
 100:     /** The size of y-offset for the 3D effect. */
 101:     private double yOffset;
 102:    
 103:     /** The paint used to shade the left and lower 3D wall. */
 104:     private transient Paint wallPaint;
 105:    
 106:     /**
 107:      * Creates a new renderer.
 108:      */
 109:     public LineRenderer3D() {
 110:         super(true, false);  //Create a line renderer only
 111:         this.xOffset = DEFAULT_X_OFFSET;
 112:         this.yOffset = DEFAULT_Y_OFFSET;
 113:         this.wallPaint = DEFAULT_WALL_PAINT;
 114:     }
 115:    
 116:     /**
 117:      * Returns the x-offset for the 3D effect.
 118:      *
 119:      * @return The x-offset.
 120:      */
 121:     public double getXOffset() {
 122:         return this.xOffset;
 123:     }
 124: 
 125:     /**
 126:      * Returns the y-offset for the 3D effect.
 127:      *
 128:      * @return The y-offset.
 129:      */
 130:     public double getYOffset() {
 131:         return this.yOffset;
 132:     }
 133:    
 134:     /**
 135:      * Sets the x-offset.
 136:      * 
 137:      * @param xOffset  the x-offset.
 138:      */
 139:     public void setXOffset(double xOffset) {
 140:         this.xOffset = xOffset;
 141:         notifyListeners(new RendererChangeEvent(this));
 142:     }
 143: 
 144:     /**
 145:      * Sets the y-offset.
 146:      * 
 147:      * @param yOffset  the y-offset.
 148:      */
 149:     public void setYOffset(double yOffset) {
 150:         this.yOffset = yOffset;
 151:         notifyListeners(new RendererChangeEvent(this));
 152:     }
 153: 
 154:     /**
 155:      * Returns the paint used to highlight the left and bottom wall in the plot
 156:      * background.
 157:      *
 158:      * @return The paint.
 159:      */
 160:     public Paint getWallPaint() {
 161:         return this.wallPaint;
 162:     }
 163: 
 164:     /**
 165:      * Sets the paint used to hightlight the left and bottom walls in the plot
 166:      * background.
 167:      *
 168:      * @param paint  the paint.
 169:      */
 170:     public void setWallPaint(Paint paint) {
 171:         this.wallPaint = paint;
 172:         notifyListeners(new RendererChangeEvent(this));
 173:     }
 174:    
 175:     /**
 176:      * Draws the background for the plot.
 177:      *
 178:      * @param g2  the graphics device.
 179:      * @param plot  the plot.
 180:      * @param dataArea  the area inside the axes.
 181:      */
 182:     public void drawBackground(Graphics2D g2, CategoryPlot plot, 
 183:                                Rectangle2D dataArea) {
 184: 
 185:         float x0 = (float) dataArea.getX();
 186:         float x1 = x0 + (float) Math.abs(this.xOffset);
 187:         float x3 = (float) dataArea.getMaxX();
 188:         float x2 = x3 - (float) Math.abs(this.xOffset);
 189: 
 190:         float y0 = (float) dataArea.getMaxY();
 191:         float y1 = y0 - (float) Math.abs(this.yOffset);
 192:         float y3 = (float) dataArea.getMinY();
 193:         float y2 = y3 + (float) Math.abs(this.yOffset);
 194: 
 195:         GeneralPath clip = new GeneralPath();
 196:         clip.moveTo(x0, y0);
 197:         clip.lineTo(x0, y2);
 198:         clip.lineTo(x1, y3);
 199:         clip.lineTo(x3, y3);
 200:         clip.lineTo(x3, y1);
 201:         clip.lineTo(x2, y0);
 202:         clip.closePath();
 203: 
 204:         // fill background...
 205:         Paint backgroundPaint = plot.getBackgroundPaint();
 206:         if (backgroundPaint != null) {
 207:             g2.setPaint(backgroundPaint);
 208:             g2.fill(clip);
 209:         }
 210: 
 211:         GeneralPath leftWall = new GeneralPath();
 212:         leftWall.moveTo(x0, y0);
 213:         leftWall.lineTo(x0, y2);
 214:         leftWall.lineTo(x1, y3);
 215:         leftWall.lineTo(x1, y1);
 216:         leftWall.closePath();
 217:         g2.setPaint(getWallPaint());
 218:         g2.fill(leftWall);
 219: 
 220:         GeneralPath bottomWall = new GeneralPath();
 221:         bottomWall.moveTo(x0, y0);
 222:         bottomWall.lineTo(x1, y1);
 223:         bottomWall.lineTo(x3, y1);
 224:         bottomWall.lineTo(x2, y0);
 225:         bottomWall.closePath();
 226:         g2.setPaint(getWallPaint());
 227:         g2.fill(bottomWall);
 228: 
 229:         // higlight the background corners...
 230:         g2.setPaint(Color.lightGray);
 231:         Line2D corner = new Line2D.Double(x0, y0, x1, y1);
 232:         g2.draw(corner);
 233:         corner.setLine(x1, y1, x1, y3);
 234:         g2.draw(corner);
 235:         corner.setLine(x1, y1, x3, y1);
 236:         g2.draw(corner);
 237: 
 238:         // draw background image, if there is one...
 239:         Image backgroundImage = plot.getBackgroundImage();
 240:         if (backgroundImage != null) {
 241:             Composite originalComposite = g2.getComposite();
 242:             g2.setComposite(AlphaComposite.getInstance(
 243:                     AlphaComposite.SRC, plot.getBackgroundAlpha()));
 244:             g2.drawImage(backgroundImage, (int) x1, (int) y3, 
 245:                     (int) (x3 - x1 + 1), (int) (y1 - y3 + 1), null);
 246:             g2.setComposite(originalComposite);
 247:         }
 248: 
 249:     }
 250: 
 251:     /**
 252:      * Draws the outline for the plot.
 253:      *
 254:      * @param g2  the graphics device.
 255:      * @param plot  the plot.
 256:      * @param dataArea  the area inside the axes.
 257:      */
 258:     public void drawOutline(Graphics2D g2, CategoryPlot plot, 
 259:                             Rectangle2D dataArea) {
 260: 
 261:         float x0 = (float) dataArea.getX();
 262:         float x1 = x0 + (float) Math.abs(this.xOffset);
 263:         float x3 = (float) dataArea.getMaxX();
 264:         float x2 = x3 - (float) Math.abs(this.xOffset);
 265: 
 266:         float y0 = (float) dataArea.getMaxY();
 267:         float y1 = y0 - (float) Math.abs(this.yOffset);
 268:         float y3 = (float) dataArea.getMinY();
 269:         float y2 = y3 + (float) Math.abs(this.yOffset);
 270: 
 271:         GeneralPath clip = new GeneralPath();
 272:         clip.moveTo(x0, y0);
 273:         clip.lineTo(x0, y2);
 274:         clip.lineTo(x1, y3);
 275:         clip.lineTo(x3, y3);
 276:         clip.lineTo(x3, y1);
 277:         clip.lineTo(x2, y0);
 278:         clip.closePath();
 279: 
 280:         // put an outline around the data area...
 281:         Stroke outlineStroke = plot.getOutlineStroke();
 282:         Paint outlinePaint = plot.getOutlinePaint();
 283:         if ((outlineStroke != null) && (outlinePaint != null)) {
 284:             g2.setStroke(outlineStroke);
 285:             g2.setPaint(outlinePaint);
 286:             g2.draw(clip);
 287:         }
 288: 
 289:     }
 290: 
 291:     /**
 292:      * Draws a grid line against the domain axis.
 293:      *
 294:      * @param g2  the graphics device.
 295:      * @param plot  the plot.
 296:      * @param dataArea  the area for plotting data (not yet adjusted for any 
 297:      *                  3D effect).
 298:      * @param value  the Java2D value at which the grid line should be drawn.
 299:      *
 300:      */
 301:     public void drawDomainGridline(Graphics2D g2,
 302:                                    CategoryPlot plot,
 303:                                    Rectangle2D dataArea,
 304:                                    double value) {
 305: 
 306:         Line2D line1 = null;
 307:         Line2D line2 = null;
 308:         PlotOrientation orientation = plot.getOrientation();
 309:         if (orientation == PlotOrientation.HORIZONTAL) {
 310:             double y0 = value;
 311:             double y1 = value - getYOffset();
 312:             double x0 = dataArea.getMinX();
 313:             double x1 = x0 + getXOffset();
 314:             double x2 = dataArea.getMaxY();
 315:             line1 = new Line2D.Double(x0, y0, x1, y1);
 316:             line2 = new Line2D.Double(x1, y1, x2, y1);
 317:         }
 318:         else if (orientation == PlotOrientation.VERTICAL) {
 319:             double x0 = value;
 320:             double x1 = value + getXOffset();
 321:             double y0 = dataArea.getMaxY();
 322:             double y1 = y0 - getYOffset();
 323:             double y2 = dataArea.getMinY();
 324:             line1 = new Line2D.Double(x0, y0, x1, y1);
 325:             line2 = new Line2D.Double(x1, y1, x1, y2);
 326:         }
 327:         g2.setPaint(plot.getDomainGridlinePaint());
 328:         g2.setStroke(plot.getDomainGridlineStroke());
 329:         g2.draw(line1);
 330:         g2.draw(line2);
 331: 
 332:     }
 333: 
 334:     /**
 335:      * Draws a grid line against the range axis.
 336:      *
 337:      * @param g2  the graphics device.
 338:      * @param plot  the plot.
 339:      * @param axis  the value axis.
 340:      * @param dataArea  the area for plotting data (not yet adjusted for any 
 341:      *                  3D effect).
 342:      * @param value  the value at which the grid line should be drawn.
 343:      *
 344:      */
 345:     public void drawRangeGridline(Graphics2D g2,
 346:                                   CategoryPlot plot,
 347:                                   ValueAxis axis,
 348:                                   Rectangle2D dataArea,
 349:                                   double value) {
 350: 
 351:         Range range = axis.getRange();
 352: 
 353:         if (!range.contains(value)) {
 354:             return;
 355:         }
 356: 
 357:         Rectangle2D adjusted = new Rectangle2D.Double(dataArea.getX(),
 358:                 dataArea.getY() + getYOffset(),
 359:                 dataArea.getWidth() - getXOffset(),
 360:                 dataArea.getHeight() - getYOffset());
 361: 
 362:         Line2D line1 = null;
 363:         Line2D line2 = null;
 364:         PlotOrientation orientation = plot.getOrientation();
 365:         if (orientation == PlotOrientation.HORIZONTAL) {
 366:             double x0 = axis.valueToJava2D(value, adjusted, 
 367:                     plot.getRangeAxisEdge());
 368:             double x1 = x0 + getXOffset();
 369:             double y0 = dataArea.getMaxY();
 370:             double y1 = y0 - getYOffset();
 371:             double y2 = dataArea.getMinY();
 372:             line1 = new Line2D.Double(x0, y0, x1, y1);
 373:             line2 = new Line2D.Double(x1, y1, x1, y2);
 374:         }
 375:         else if (orientation == PlotOrientation.VERTICAL) {
 376:             double y0 = axis.valueToJava2D(value, adjusted,
 377:                     plot.getRangeAxisEdge());
 378:             double y1 = y0 - getYOffset();
 379:             double x0 = dataArea.getMinX();
 380:             double x1 = x0 + getXOffset();
 381:             double x2 = dataArea.getMaxX();
 382:             line1 = new Line2D.Double(x0, y0, x1, y1);
 383:             line2 = new Line2D.Double(x1, y1, x2, y1);
 384:         }
 385:         g2.setPaint(plot.getRangeGridlinePaint());
 386:         g2.setStroke(plot.getRangeGridlineStroke());
 387:         g2.draw(line1);
 388:         g2.draw(line2);
 389: 
 390:     }
 391: 
 392:     /**
 393:      * Draws a range marker.
 394:      *
 395:      * @param g2  the graphics device.
 396:      * @param plot  the plot.
 397:      * @param axis  the value axis.
 398:      * @param marker  the marker.
 399:      * @param dataArea  the area for plotting data (not including 3D effect).
 400:      */
 401:     public void drawRangeMarker(Graphics2D g2,
 402:                                 CategoryPlot plot,
 403:                                 ValueAxis axis,
 404:                                 Marker marker,
 405:                                 Rectangle2D dataArea) {
 406: 
 407:         if (marker instanceof ValueMarker) {
 408:             ValueMarker vm = (ValueMarker) marker;
 409:             double value = vm.getValue();
 410:             Range range = axis.getRange();
 411:             if (!range.contains(value)) {
 412:                 return;
 413:             }
 414: 
 415:             Rectangle2D adjusted = new Rectangle2D.Double(dataArea.getX(), 
 416:                     dataArea.getY() + getYOffset(), 
 417:                     dataArea.getWidth() - getXOffset(), 
 418:                     dataArea.getHeight() - getYOffset());
 419: 
 420:             GeneralPath path = null;
 421:             PlotOrientation orientation = plot.getOrientation();
 422:             if (orientation == PlotOrientation.HORIZONTAL) {
 423:                 float x = (float) axis.valueToJava2D(value, adjusted, 
 424:                         plot.getRangeAxisEdge());
 425:                 float y = (float) adjusted.getMaxY();
 426:                 path = new GeneralPath();
 427:                 path.moveTo(x, y);
 428:                 path.lineTo((float) (x + getXOffset()), 
 429:                         y - (float) getYOffset());
 430:                 path.lineTo((float) (x + getXOffset()), 
 431:                         (float) (adjusted.getMinY() - getYOffset()));
 432:                 path.lineTo(x, (float) adjusted.getMinY());
 433:                 path.closePath();
 434:             }
 435:             else if (orientation == PlotOrientation.VERTICAL) {
 436:                 float y = (float) axis.valueToJava2D(value, adjusted, 
 437:                         plot.getRangeAxisEdge());
 438:                 float x = (float) dataArea.getX();
 439:                 path = new GeneralPath();
 440:                 path.moveTo(x, y);
 441:                 path.lineTo(x + (float) this.xOffset, y - (float) this.yOffset);
 442:                 path.lineTo((float) (adjusted.getMaxX() + this.xOffset), 
 443:                         y - (float) this.yOffset);
 444:                 path.lineTo((float) (adjusted.getMaxX()), y);
 445:                 path.closePath();
 446:             }
 447:             g2.setPaint(marker.getPaint());
 448:             g2.fill(path);
 449:             g2.setPaint(marker.getOutlinePaint());
 450:             g2.draw(path);
 451:         }
 452:     }
 453:    
 454:    /**
 455:      * Draw a single data item.
 456:      *
 457:      * @param g2  the graphics device.
 458:      * @param state  the renderer state.
 459:      * @param dataArea  the area in which the data is drawn.
 460:      * @param plot  the plot.
 461:      * @param domainAxis  the domain axis.
 462:      * @param rangeAxis  the range axis.
 463:      * @param dataset  the dataset.
 464:      * @param row  the row index (zero-based).
 465:      * @param column  the column index (zero-based).
 466:      * @param pass  the pass index.
 467:      */
 468:     public void drawItem(Graphics2D g2,
 469:                          CategoryItemRendererState state,
 470:                          Rectangle2D dataArea,
 471:                          CategoryPlot plot,
 472:                          CategoryAxis domainAxis,
 473:                          ValueAxis rangeAxis,
 474:                          CategoryDataset dataset,
 475:                          int row,
 476:                          int column,
 477:                          int pass) {
 478: 
 479:         if (!getItemVisible(row, column)) {
 480:             return;   
 481:         }
 482:         
 483:         // nothing is drawn for null...
 484:         Number v = dataset.getValue(row, column);
 485:         if (v == null) {
 486:             return;
 487:         }
 488:        
 489:         Rectangle2D adjusted = new Rectangle2D.Double(dataArea.getX(),
 490:                 dataArea.getY() + getYOffset(), 
 491:                 dataArea.getWidth() - getXOffset(),
 492:                 dataArea.getHeight() - getYOffset());
 493:        
 494:         PlotOrientation orientation = plot.getOrientation();
 495: 
 496:         // current data point...
 497:         double x1 = domainAxis.getCategoryMiddle(column, getColumnCount(), 
 498:                 adjusted, plot.getDomainAxisEdge());
 499:         double value = v.doubleValue();
 500:         double y1 = rangeAxis.valueToJava2D(value, adjusted, 
 501:                 plot.getRangeAxisEdge());
 502: 
 503:         Shape shape = getItemShape(row, column);
 504:         if (orientation == PlotOrientation.HORIZONTAL) {
 505:             shape = ShapeUtilities.createTranslatedShape(shape, y1, x1);
 506:         }
 507:         else if (orientation == PlotOrientation.VERTICAL) {
 508:             shape = ShapeUtilities.createTranslatedShape(shape, x1, y1);
 509:         }
 510:        
 511:         if (getItemLineVisible(row, column)) {
 512:             if (column != 0) {
 513: 
 514:                 Number previousValue = dataset.getValue(row, column - 1);
 515:                 if (previousValue != null) {
 516: 
 517:                     // previous data point...
 518:                     double previous = previousValue.doubleValue();
 519:                     double x0 = domainAxis.getCategoryMiddle(column - 1, 
 520:                             getColumnCount(), adjusted, 
 521:                             plot.getDomainAxisEdge());
 522:                     double y0 = rangeAxis.valueToJava2D(previous, adjusted, 
 523:                             plot.getRangeAxisEdge());
 524: 
 525:                     double x2 = x0 + getXOffset();
 526:                     double y2 = y0 - getYOffset();
 527:                     double x3 = x1 + getXOffset();
 528:                     double y3 = y1 - getYOffset();
 529:                    
 530:                     GeneralPath clip = new GeneralPath();
 531:                    
 532:                     if (orientation == PlotOrientation.HORIZONTAL) {
 533:                         clip.moveTo((float) y0, (float) x0);
 534:                         clip.lineTo((float) y1, (float) x1);
 535:                         clip.lineTo((float) y3, (float) x3);
 536:                         clip.lineTo((float) y2, (float) x2);
 537:                         clip.lineTo((float) y0, (float) x0);
 538:                         clip.closePath();
 539:                     }
 540:                     else if (orientation == PlotOrientation.VERTICAL) {
 541:                         clip.moveTo((float) x0, (float) y0);
 542:                         clip.lineTo((float) x1, (float) y1);
 543:                         clip.lineTo((float) x3, (float) y3);
 544:                         clip.lineTo((float) x2, (float) y2);
 545:                         clip.lineTo((float) x0, (float) y0);
 546:                         clip.closePath();
 547:                     }
 548:                    
 549:                     g2.setPaint(getItemPaint(row, column));
 550:                     g2.fill(clip);
 551:                     g2.setStroke(getItemOutlineStroke(row, column));
 552:                     g2.setPaint(getItemOutlinePaint(row, column));
 553:                     g2.draw(clip);
 554:                 }
 555:             }
 556:         }
 557: 
 558:         // draw the item label if there is one...
 559:         if (isItemLabelVisible(row, column)) {
 560:             drawItemLabel(g2, orientation, dataset, row, column, x1, y1, 
 561:                     (value < 0.0));
 562:         }
 563: 
 564:         // add an item entity, if this information is being collected
 565:         EntityCollection entities = state.getEntityCollection();
 566:         if (entities != null) {
 567:             addItemEntity(entities, dataset, row, column, shape);
 568:         }
 569: 
 570:     }
 571: 
 572: }