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