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