001 /* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2008, 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-2008, by Tobias Selb and Contributors. 031 * 032 * Original Author: Tobias Selb (http://www.uepselon.com); 033 * Contributor(s): David Gilbert (for Object Refinery Limited); 034 * 035 * Changes 036 * ------- 037 * 15-Oct-2004 : Version 1 (TS); 038 * 05-Nov-2004 : Modified drawItem() signature (DG); 039 * 11-Nov-2004 : Now uses ShapeUtilities class to translate shapes (DG); 040 * 26-Jan-2005 : Update for changes in super class (DG); 041 * 13-Apr-2005 : Check item visibility in drawItem() method (DG); 042 * 09-Jun-2005 : Use addItemEntity() in drawItem() method (DG); 043 * 10-Jun-2005 : Fixed capitalisation of setXOffset() and setYOffset() (DG); 044 * ------------- JFREECHART 1.0.x --------------------------------------------- 045 * 01-Dec-2006 : Fixed equals() and serialization (DG); 046 * 17-Jan-2007 : Fixed bug in drawDomainGridline() method and added 047 * argument check to setWallPaint() (DG); 048 * 03-Apr-2007 : Fixed bugs in drawBackground() method (DG); 049 * 16-Oct-2007 : Fixed bug in range marker drawing (DG); 050 * 051 */ 052 053 package org.jfree.chart.renderer.category; 054 055 import java.awt.AlphaComposite; 056 import java.awt.Color; 057 import java.awt.Composite; 058 import java.awt.Graphics2D; 059 import java.awt.Image; 060 import java.awt.Paint; 061 import java.awt.Shape; 062 import java.awt.Stroke; 063 import java.awt.geom.GeneralPath; 064 import java.awt.geom.Line2D; 065 import java.awt.geom.Rectangle2D; 066 import java.io.IOException; 067 import java.io.ObjectInputStream; 068 import java.io.ObjectOutputStream; 069 import java.io.Serializable; 070 071 import org.jfree.chart.Effect3D; 072 import org.jfree.chart.axis.CategoryAxis; 073 import org.jfree.chart.axis.ValueAxis; 074 import org.jfree.chart.entity.EntityCollection; 075 import org.jfree.chart.event.RendererChangeEvent; 076 import org.jfree.chart.plot.CategoryPlot; 077 import org.jfree.chart.plot.Marker; 078 import org.jfree.chart.plot.PlotOrientation; 079 import org.jfree.chart.plot.ValueMarker; 080 import org.jfree.data.Range; 081 import org.jfree.data.category.CategoryDataset; 082 import org.jfree.io.SerialUtilities; 083 import org.jfree.util.PaintUtilities; 084 import org.jfree.util.ShapeUtilities; 085 086 /** 087 * A line renderer with a 3D effect. 088 */ 089 public class LineRenderer3D extends LineAndShapeRenderer 090 implements Effect3D, Serializable { 091 092 /** For serialization. */ 093 private static final long serialVersionUID = 5467931468380928736L; 094 095 /** The default x-offset for the 3D effect. */ 096 public static final double DEFAULT_X_OFFSET = 12.0; 097 098 /** The default y-offset for the 3D effect. */ 099 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 }