001/* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2014, 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 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. 025 * Other names may be trademarks of their respective owners.] 026 * 027 * ------------------ 028 * BarRenderer3D.java 029 * ------------------ 030 * (C) Copyright 2001-2014, by Serge V. Grachov and Contributors. 031 * 032 * Original Author: Serge V. Grachov; 033 * Contributor(s): David Gilbert (for Object Refinery Limited); 034 * Tin Luu; 035 * Milo Simpson; 036 * Richard Atkinson; 037 * Rich Unger; 038 * Christian W. Zuckschwerdt; 039 * DaveLaw (dave ATT davelaw DOTT de) - patch 3204823; 040 * 041 * Changes 042 * ------- 043 * 31-Oct-2001 : First version, contributed by Serge V. Grachov (DG); 044 * 15-Nov-2001 : Modified to allow for null data values (DG); 045 * 13-Dec-2001 : Added tooltips (DG); 046 * 16-Jan-2002 : Added fix for single category or single series datasets, 047 * pointed out by Taoufik Romdhane (DG); 048 * 24-May-2002 : Incorporated tooltips into chart entities (DG); 049 * 11-Jun-2002 : Added check for (permitted) null info object, bug and fix 050 * reported by David Basten. Also updated Javadocs. (DG); 051 * 19-Jun-2002 : Added code to draw labels on bars (TL); 052 * 26-Jun-2002 : Added bar clipping to avoid PRExceptions (DG); 053 * 05-Aug-2002 : Small modification to drawCategoryItem method to support URLs 054 * for HTML image maps (RA); 055 * 06-Aug-2002 : Value labels now use number formatter, thanks to Milo 056 * Simpson (DG); 057 * 08-Aug-2002 : Applied fixed in bug id 592218 (DG); 058 * 20-Sep-2002 : Added fix for categoryPaint by Rich Unger, and fixed errors 059 * reported by Checkstyle (DG); 060 * 24-Oct-2002 : Amendments for changes in CategoryDataset interface and 061 * CategoryToolTipGenerator interface (DG); 062 * 05-Nov-2002 : Replaced references to CategoryDataset with TableDataset (DG); 063 * 06-Nov-2002 : Moved to the com.jrefinery.chart.renderer package (DG); 064 * 28-Jan-2003 : Added an attribute to control the shading of the left and 065 * bottom walls in the plot background (DG); 066 * 25-Mar-2003 : Implemented Serializable (DG); 067 * 10-Apr-2003 : Removed category paint usage (DG); 068 * 13-May-2003 : Renamed VerticalBarRenderer3D --> BarRenderer3D and merged with 069 * HorizontalBarRenderer3D (DG); 070 * 30-Jul-2003 : Modified entity constructor (CZ); 071 * 19-Aug-2003 : Implemented Cloneable and PublicCloneable (DG); 072 * 07-Oct-2003 : Added renderer state (DG); 073 * 08-Oct-2003 : Removed clipping (replaced with flag in CategoryPlot to 074 * control order in which the data items are processed) (DG); 075 * 20-Oct-2003 : Fixed bug (outline stroke not being used for bar 076 * outlines) (DG); 077 * 21-Oct-2003 : Bar width moved into CategoryItemRendererState (DG); 078 * 24-Nov-2003 : Fixed bug 846324 (item labels not showing) (DG); 079 * 27-Nov-2003 : Added code to respect maxBarWidth setting (DG); 080 * 02-Feb-2004 : Fixed bug where 'drawBarOutline' flag is not respected (DG); 081 * 10-Feb-2004 : Small change to drawItem() method to make cut-and-paste 082 * overriding easier (DG); 083 * 04-Oct-2004 : Fixed bug with item label positioning when plot alignment is 084 * horizontal (DG); 085 * 05-Nov-2004 : Modified drawItem() signature (DG); 086 * 20-Apr-2005 : Renamed CategoryLabelGenerator 087 * --> CategoryItemLabelGenerator (DG); 088 * 25-Apr-2005 : Override initialise() method to fix bug 1189642 (DG); 089 * 09-Jun-2005 : Use addEntityItem from super class (DG); 090 * ------------- JFREECHART 1.0.x --------------------------------------------- 091 * 07-Dec-2006 : Implemented equals() override (DG); 092 * 17-Jan-2007 : Fixed bug in drawDomainGridline() method (DG); 093 * 03-Apr-2007 : Fixed bugs in drawBackground() method (DG); 094 * 16-Oct-2007 : Fixed bug in range marker drawing (DG); 095 * 19-Mar-2009 : Override for drawRangeLine() method (DG); 096 * 11-Jun-2012 : Utilise new PaintAlpha class - patch 3204823 from DaveLaw (DG); 097 * 03-Jul-2013 : Use ParamChecks (DG); 098 * 11-Mar-2014 : Check visible series (DG); 099 * 100 */ 101 102package org.jfree.chart.renderer.category; 103 104import java.awt.AlphaComposite; 105import java.awt.Color; 106import java.awt.Composite; 107import java.awt.Font; 108import java.awt.Graphics2D; 109import java.awt.Image; 110import java.awt.Paint; 111import java.awt.Stroke; 112import java.awt.geom.GeneralPath; 113import java.awt.geom.Line2D; 114import java.awt.geom.Point2D; 115import java.awt.geom.Rectangle2D; 116import java.io.IOException; 117import java.io.ObjectInputStream; 118import java.io.ObjectOutputStream; 119import java.io.Serializable; 120 121import org.jfree.chart.Effect3D; 122import org.jfree.chart.axis.CategoryAxis; 123import org.jfree.chart.axis.ValueAxis; 124import org.jfree.chart.entity.EntityCollection; 125import org.jfree.chart.event.RendererChangeEvent; 126import org.jfree.chart.labels.CategoryItemLabelGenerator; 127import org.jfree.chart.labels.ItemLabelAnchor; 128import org.jfree.chart.labels.ItemLabelPosition; 129import org.jfree.chart.plot.CategoryPlot; 130import org.jfree.chart.plot.Marker; 131import org.jfree.chart.plot.Plot; 132import org.jfree.chart.plot.PlotOrientation; 133import org.jfree.chart.plot.PlotRenderingInfo; 134import org.jfree.chart.plot.ValueMarker; 135import org.jfree.chart.util.PaintAlpha; 136import org.jfree.chart.util.ParamChecks; 137import org.jfree.data.Range; 138import org.jfree.data.category.CategoryDataset; 139import org.jfree.io.SerialUtilities; 140import org.jfree.text.TextUtilities; 141import org.jfree.ui.LengthAdjustmentType; 142import org.jfree.ui.RectangleAnchor; 143import org.jfree.ui.RectangleEdge; 144import org.jfree.ui.TextAnchor; 145import org.jfree.util.PaintUtilities; 146import org.jfree.util.PublicCloneable; 147 148/** 149 * A renderer for bars with a 3D effect, for use with the 150 * {@link CategoryPlot} class. The example shown here is generated 151 * by the <code>BarChart3DDemo1.java</code> program included in the JFreeChart 152 * Demo Collection: 153 * <br><br> 154 * <img src="../../../../../images/BarRenderer3DSample.png" 155 * alt="BarRenderer3DSample.png"> 156 */ 157public class BarRenderer3D extends BarRenderer 158 implements Effect3D, Cloneable, PublicCloneable, Serializable { 159 160 /** For serialization. */ 161 private static final long serialVersionUID = 7686976503536003636L; 162 163 /** The default x-offset for the 3D effect. */ 164 public static final double DEFAULT_X_OFFSET = 12.0; 165 166 /** The default y-offset for the 3D effect. */ 167 public static final double DEFAULT_Y_OFFSET = 8.0; 168 169 /** The default wall paint. */ 170 public static final Paint DEFAULT_WALL_PAINT = new Color(0xDD, 0xDD, 0xDD); 171 172 /** The size of x-offset for the 3D effect. */ 173 private double xOffset; 174 175 /** The size of y-offset for the 3D effect. */ 176 private double yOffset; 177 178 /** The paint used to shade the left and lower 3D wall. */ 179 private transient Paint wallPaint; 180 181 /** 182 * Default constructor, creates a renderer with a default '3D effect'. 183 */ 184 public BarRenderer3D() { 185 this(DEFAULT_X_OFFSET, DEFAULT_Y_OFFSET); 186 } 187 188 /** 189 * Constructs a new renderer with the specified '3D effect'. 190 * 191 * @param xOffset the x-offset for the 3D effect. 192 * @param yOffset the y-offset for the 3D effect. 193 */ 194 public BarRenderer3D(double xOffset, double yOffset) { 195 196 super(); 197 this.xOffset = xOffset; 198 this.yOffset = yOffset; 199 this.wallPaint = DEFAULT_WALL_PAINT; 200 // set the default item label positions 201 ItemLabelPosition p1 = new ItemLabelPosition(ItemLabelAnchor.INSIDE12, 202 TextAnchor.TOP_CENTER); 203 setBasePositiveItemLabelPosition(p1); 204 ItemLabelPosition p2 = new ItemLabelPosition(ItemLabelAnchor.INSIDE12, 205 TextAnchor.TOP_CENTER); 206 setBaseNegativeItemLabelPosition(p2); 207 208 } 209 210 /** 211 * Returns the x-offset for the 3D effect. 212 * 213 * @return The 3D effect. 214 * 215 * @see #getYOffset() 216 */ 217 @Override 218 public double getXOffset() { 219 return this.xOffset; 220 } 221 222 /** 223 * Returns the y-offset for the 3D effect. 224 * 225 * @return The 3D effect. 226 */ 227 @Override 228 public double getYOffset() { 229 return this.yOffset; 230 } 231 232 /** 233 * Returns the paint used to highlight the left and bottom wall in the plot 234 * background. 235 * 236 * @return The paint. 237 * 238 * @see #setWallPaint(Paint) 239 */ 240 public Paint getWallPaint() { 241 return this.wallPaint; 242 } 243 244 /** 245 * Sets the paint used to hightlight the left and bottom walls in the plot 246 * background, and sends a {@link RendererChangeEvent} to all registered 247 * listeners. 248 * 249 * @param paint the paint (<code>null</code> not permitted). 250 * 251 * @see #getWallPaint() 252 */ 253 public void setWallPaint(Paint paint) { 254 ParamChecks.nullNotPermitted(paint, "paint"); 255 this.wallPaint = paint; 256 fireChangeEvent(); 257 } 258 259 260 /** 261 * Initialises the renderer and returns a state object that will be passed 262 * to subsequent calls to the drawItem method. This method gets called 263 * once at the start of the process of drawing a chart. 264 * 265 * @param g2 the graphics device. 266 * @param dataArea the area in which the data is to be plotted. 267 * @param plot the plot. 268 * @param rendererIndex the renderer index. 269 * @param info collects chart rendering information for return to caller. 270 * 271 * @return The renderer state. 272 */ 273 @Override 274 public CategoryItemRendererState initialise(Graphics2D g2, 275 Rectangle2D dataArea, CategoryPlot plot, int rendererIndex, 276 PlotRenderingInfo info) { 277 278 Rectangle2D adjusted = new Rectangle2D.Double(dataArea.getX(), 279 dataArea.getY() + getYOffset(), dataArea.getWidth() 280 - getXOffset(), dataArea.getHeight() - getYOffset()); 281 CategoryItemRendererState state = super.initialise(g2, adjusted, plot, 282 rendererIndex, info); 283 return state; 284 285 } 286 287 /** 288 * Draws the background for the plot. 289 * 290 * @param g2 the graphics device. 291 * @param plot the plot. 292 * @param dataArea the area inside the axes. 293 */ 294 @Override 295 public void drawBackground(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 Composite originalComposite = g2.getComposite(); 318 g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 319 plot.getBackgroundAlpha())); 320 321 // fill background... 322 Paint backgroundPaint = plot.getBackgroundPaint(); 323 if (backgroundPaint != null) { 324 g2.setPaint(backgroundPaint); 325 g2.fill(clip); 326 } 327 328 GeneralPath leftWall = new GeneralPath(); 329 leftWall.moveTo(x0, y0); 330 leftWall.lineTo(x0, y2); 331 leftWall.lineTo(x1, y3); 332 leftWall.lineTo(x1, y1); 333 leftWall.closePath(); 334 g2.setPaint(getWallPaint()); 335 g2.fill(leftWall); 336 337 GeneralPath bottomWall = new GeneralPath(); 338 bottomWall.moveTo(x0, y0); 339 bottomWall.lineTo(x1, y1); 340 bottomWall.lineTo(x3, y1); 341 bottomWall.lineTo(x2, y0); 342 bottomWall.closePath(); 343 g2.setPaint(getWallPaint()); 344 g2.fill(bottomWall); 345 346 // highlight the background corners... 347 g2.setPaint(Color.lightGray); 348 Line2D corner = new Line2D.Double(x0, y0, x1, y1); 349 g2.draw(corner); 350 corner.setLine(x1, y1, x1, y3); 351 g2.draw(corner); 352 corner.setLine(x1, y1, x3, y1); 353 g2.draw(corner); 354 355 // draw background image, if there is one... 356 Image backgroundImage = plot.getBackgroundImage(); 357 if (backgroundImage != null) { 358 Rectangle2D adjusted = new Rectangle2D.Double(dataArea.getX() 359 + getXOffset(), dataArea.getY(), 360 dataArea.getWidth() - getXOffset(), 361 dataArea.getHeight() - getYOffset()); 362 plot.drawBackgroundImage(g2, adjusted); 363 } 364 365 g2.setComposite(originalComposite); 366 367 } 368 369 /** 370 * Draws the outline for the plot. 371 * 372 * @param g2 the graphics device. 373 * @param plot the plot. 374 * @param dataArea the area inside the axes. 375 */ 376 @Override 377 public void drawOutline(Graphics2D g2, CategoryPlot plot, 378 Rectangle2D dataArea) { 379 380 float x0 = (float) dataArea.getX(); 381 float x1 = x0 + (float) Math.abs(this.xOffset); 382 float x3 = (float) dataArea.getMaxX(); 383 float x2 = x3 - (float) Math.abs(this.xOffset); 384 385 float y0 = (float) dataArea.getMaxY(); 386 float y1 = y0 - (float) Math.abs(this.yOffset); 387 float y3 = (float) dataArea.getMinY(); 388 float y2 = y3 + (float) Math.abs(this.yOffset); 389 390 GeneralPath clip = new GeneralPath(); 391 clip.moveTo(x0, y0); 392 clip.lineTo(x0, y2); 393 clip.lineTo(x1, y3); 394 clip.lineTo(x3, y3); 395 clip.lineTo(x3, y1); 396 clip.lineTo(x2, y0); 397 clip.closePath(); 398 399 // put an outline around the data area... 400 Stroke outlineStroke = plot.getOutlineStroke(); 401 Paint outlinePaint = plot.getOutlinePaint(); 402 if ((outlineStroke != null) && (outlinePaint != null)) { 403 g2.setStroke(outlineStroke); 404 g2.setPaint(outlinePaint); 405 g2.draw(clip); 406 } 407 408 } 409 410 /** 411 * Draws a grid line against the domain axis. 412 * 413 * @param g2 the graphics device. 414 * @param plot the plot. 415 * @param dataArea the area for plotting data (not yet adjusted for any 416 * 3D effect). 417 * @param value the Java2D value at which the grid line should be drawn. 418 * 419 */ 420 @Override 421 public void drawDomainGridline(Graphics2D g2, CategoryPlot plot, 422 Rectangle2D dataArea, double value) { 423 424 Line2D line1 = null; 425 Line2D line2 = null; 426 PlotOrientation orientation = plot.getOrientation(); 427 if (orientation == PlotOrientation.HORIZONTAL) { 428 double y0 = value; 429 double y1 = value - getYOffset(); 430 double x0 = dataArea.getMinX(); 431 double x1 = x0 + getXOffset(); 432 double x2 = dataArea.getMaxX(); 433 line1 = new Line2D.Double(x0, y0, x1, y1); 434 line2 = new Line2D.Double(x1, y1, x2, y1); 435 } 436 else if (orientation == PlotOrientation.VERTICAL) { 437 double x0 = value; 438 double x1 = value + getXOffset(); 439 double y0 = dataArea.getMaxY(); 440 double y1 = y0 - getYOffset(); 441 double y2 = dataArea.getMinY(); 442 line1 = new Line2D.Double(x0, y0, x1, y1); 443 line2 = new Line2D.Double(x1, y1, x1, y2); 444 } 445 Paint paint = plot.getDomainGridlinePaint(); 446 Stroke stroke = plot.getDomainGridlineStroke(); 447 g2.setPaint(paint != null ? paint : Plot.DEFAULT_OUTLINE_PAINT); 448 g2.setStroke(stroke != null ? stroke : Plot.DEFAULT_OUTLINE_STROKE); 449 g2.draw(line1); 450 g2.draw(line2); 451 452 } 453 454 /** 455 * Draws a grid line against the range axis. 456 * 457 * @param g2 the graphics device. 458 * @param plot the plot. 459 * @param axis the value axis. 460 * @param dataArea the area for plotting data (not yet adjusted for any 461 * 3D effect). 462 * @param value the value at which the grid line should be drawn. 463 * 464 */ 465 @Override 466 public void drawRangeGridline(Graphics2D g2, CategoryPlot plot, 467 ValueAxis axis, Rectangle2D dataArea, double value) { 468 469 Range range = axis.getRange(); 470 471 if (!range.contains(value)) { 472 return; 473 } 474 475 Rectangle2D adjusted = new Rectangle2D.Double(dataArea.getX(), 476 dataArea.getY() + getYOffset(), dataArea.getWidth() 477 - getXOffset(), dataArea.getHeight() - getYOffset()); 478 479 Line2D line1 = null; 480 Line2D line2 = null; 481 PlotOrientation orientation = plot.getOrientation(); 482 if (orientation == PlotOrientation.HORIZONTAL) { 483 double x0 = axis.valueToJava2D(value, adjusted, 484 plot.getRangeAxisEdge()); 485 double x1 = x0 + getXOffset(); 486 double y0 = dataArea.getMaxY(); 487 double y1 = y0 - getYOffset(); 488 double y2 = dataArea.getMinY(); 489 line1 = new Line2D.Double(x0, y0, x1, y1); 490 line2 = new Line2D.Double(x1, y1, x1, y2); 491 } 492 else if (orientation == PlotOrientation.VERTICAL) { 493 double y0 = axis.valueToJava2D(value, adjusted, 494 plot.getRangeAxisEdge()); 495 double y1 = y0 - getYOffset(); 496 double x0 = dataArea.getMinX(); 497 double x1 = x0 + getXOffset(); 498 double x2 = dataArea.getMaxX(); 499 line1 = new Line2D.Double(x0, y0, x1, y1); 500 line2 = new Line2D.Double(x1, y1, x2, y1); 501 } 502 Paint paint = plot.getRangeGridlinePaint(); 503 Stroke stroke = plot.getRangeGridlineStroke(); 504 g2.setPaint(paint != null ? paint : Plot.DEFAULT_OUTLINE_PAINT); 505 g2.setStroke(stroke != null ? stroke : Plot.DEFAULT_OUTLINE_STROKE); 506 g2.draw(line1); 507 g2.draw(line2); 508 509 } 510 511 /** 512 * Draws a line perpendicular to the range axis. 513 * 514 * @param g2 the graphics device. 515 * @param plot the plot. 516 * @param axis the value axis. 517 * @param dataArea the area for plotting data (not yet adjusted for any 3D 518 * effect). 519 * @param value the value at which the grid line should be drawn. 520 * @param paint the paint. 521 * @param stroke the stroke. 522 * 523 * @see #drawRangeGridline 524 * 525 * @since 1.0.13 526 */ 527 @Override 528 public void drawRangeLine(Graphics2D g2, CategoryPlot plot, ValueAxis axis, 529 Rectangle2D dataArea, double value, Paint paint, Stroke stroke) { 530 531 Range range = axis.getRange(); 532 if (!range.contains(value)) { 533 return; 534 } 535 536 Rectangle2D adjusted = new Rectangle2D.Double(dataArea.getX(), 537 dataArea.getY() + getYOffset(), dataArea.getWidth() 538 - getXOffset(), dataArea.getHeight() - getYOffset()); 539 540 Line2D line1 = null; 541 Line2D line2 = null; 542 PlotOrientation orientation = plot.getOrientation(); 543 if (orientation == PlotOrientation.HORIZONTAL) { 544 double x0 = axis.valueToJava2D(value, adjusted, 545 plot.getRangeAxisEdge()); 546 double x1 = x0 + getXOffset(); 547 double y0 = dataArea.getMaxY(); 548 double y1 = y0 - getYOffset(); 549 double y2 = dataArea.getMinY(); 550 line1 = new Line2D.Double(x0, y0, x1, y1); 551 line2 = new Line2D.Double(x1, y1, x1, y2); 552 } 553 else if (orientation == PlotOrientation.VERTICAL) { 554 double y0 = axis.valueToJava2D(value, adjusted, 555 plot.getRangeAxisEdge()); 556 double y1 = y0 - getYOffset(); 557 double x0 = dataArea.getMinX(); 558 double x1 = x0 + getXOffset(); 559 double x2 = dataArea.getMaxX(); 560 line1 = new Line2D.Double(x0, y0, x1, y1); 561 line2 = new Line2D.Double(x1, y1, x2, y1); 562 } 563 g2.setPaint(paint); 564 g2.setStroke(stroke); 565 g2.draw(line1); 566 g2.draw(line2); 567 568 } 569 570 /** 571 * Draws a range marker. 572 * 573 * @param g2 the graphics device. 574 * @param plot the plot. 575 * @param axis the value axis. 576 * @param marker the marker. 577 * @param dataArea the area for plotting data (not including 3D effect). 578 */ 579 @Override 580 public void drawRangeMarker(Graphics2D g2, CategoryPlot plot, 581 ValueAxis axis, Marker marker, Rectangle2D dataArea) { 582 583 584 Rectangle2D adjusted = new Rectangle2D.Double(dataArea.getX(), 585 dataArea.getY() + getYOffset(), dataArea.getWidth() 586 - getXOffset(), dataArea.getHeight() - getYOffset()); 587 if (marker instanceof ValueMarker) { 588 ValueMarker vm = (ValueMarker) marker; 589 double value = vm.getValue(); 590 Range range = axis.getRange(); 591 if (!range.contains(value)) { 592 return; 593 } 594 595 GeneralPath path = null; 596 PlotOrientation orientation = plot.getOrientation(); 597 if (orientation == PlotOrientation.HORIZONTAL) { 598 float x = (float) axis.valueToJava2D(value, adjusted, 599 plot.getRangeAxisEdge()); 600 float y = (float) adjusted.getMaxY(); 601 path = new GeneralPath(); 602 path.moveTo(x, y); 603 path.lineTo((float) (x + getXOffset()), 604 y - (float) getYOffset()); 605 path.lineTo((float) (x + getXOffset()), 606 (float) (adjusted.getMinY() - getYOffset())); 607 path.lineTo(x, (float) adjusted.getMinY()); 608 path.closePath(); 609 } 610 else if (orientation == PlotOrientation.VERTICAL) { 611 float y = (float) axis.valueToJava2D(value, adjusted, 612 plot.getRangeAxisEdge()); 613 float x = (float) dataArea.getX(); 614 path = new GeneralPath(); 615 path.moveTo(x, y); 616 path.lineTo(x + (float) this.xOffset, y - (float) this.yOffset); 617 path.lineTo((float) (adjusted.getMaxX() + this.xOffset), 618 y - (float) this.yOffset); 619 path.lineTo((float) (adjusted.getMaxX()), y); 620 path.closePath(); 621 } else { 622 throw new IllegalStateException(); 623 } 624 g2.setPaint(marker.getPaint()); 625 g2.fill(path); 626 g2.setPaint(marker.getOutlinePaint()); 627 g2.draw(path); 628 629 String label = marker.getLabel(); 630 RectangleAnchor anchor = marker.getLabelAnchor(); 631 if (label != null) { 632 Font labelFont = marker.getLabelFont(); 633 g2.setFont(labelFont); 634 g2.setPaint(marker.getLabelPaint()); 635 Point2D coordinates = calculateRangeMarkerTextAnchorPoint( 636 g2, orientation, dataArea, path.getBounds2D(), 637 marker.getLabelOffset(), LengthAdjustmentType.EXPAND, 638 anchor); 639 TextUtilities.drawAlignedString(label, g2, 640 (float) coordinates.getX(), (float) coordinates.getY(), 641 marker.getLabelTextAnchor()); 642 } 643 644 } 645 else { 646 super.drawRangeMarker(g2, plot, axis, marker, adjusted); 647 // TODO: draw the interval marker with a 3D effect 648 } 649 } 650 651 /** 652 * Draws a 3D bar to represent one data item. 653 * 654 * @param g2 the graphics device. 655 * @param state the renderer state. 656 * @param dataArea the area for plotting the data. 657 * @param plot the plot. 658 * @param domainAxis the domain axis. 659 * @param rangeAxis the range axis. 660 * @param dataset the dataset. 661 * @param row the row index (zero-based). 662 * @param column the column index (zero-based). 663 * @param pass the pass index. 664 */ 665 @Override 666 public void drawItem(Graphics2D g2, CategoryItemRendererState state, 667 Rectangle2D dataArea, CategoryPlot plot, CategoryAxis domainAxis, 668 ValueAxis rangeAxis, CategoryDataset dataset, int row, int column, 669 int pass) { 670 671 // nothing is drawn if the row index is not included in the list with 672 // the indices of the visible rows... 673 int visibleRow = state.getVisibleSeriesIndex(row); 674 if (visibleRow < 0) { 675 return; 676 } 677 678 // check the value we are plotting... 679 Number dataValue = dataset.getValue(row, column); 680 if (dataValue == null) { 681 return; 682 } 683 684 double value = dataValue.doubleValue(); 685 686 Rectangle2D adjusted = new Rectangle2D.Double(dataArea.getX(), 687 dataArea.getY() + getYOffset(), 688 dataArea.getWidth() - getXOffset(), 689 dataArea.getHeight() - getYOffset()); 690 691 PlotOrientation orientation = plot.getOrientation(); 692 693 double barW0 = calculateBarW0(plot, orientation, adjusted, domainAxis, 694 state, visibleRow, column); 695 double[] barL0L1 = calculateBarL0L1(value); 696 if (barL0L1 == null) { 697 return; // the bar is not visible 698 } 699 700 RectangleEdge edge = plot.getRangeAxisEdge(); 701 double transL0 = rangeAxis.valueToJava2D(barL0L1[0], adjusted, edge); 702 double transL1 = rangeAxis.valueToJava2D(barL0L1[1], adjusted, edge); 703 double barL0 = Math.min(transL0, transL1); 704 double barLength = Math.abs(transL1 - transL0); 705 706 // draw the bar... 707 Rectangle2D bar; 708 if (orientation == PlotOrientation.HORIZONTAL) { 709 bar = new Rectangle2D.Double(barL0, barW0, barLength, 710 state.getBarWidth()); 711 } 712 else { 713 bar = new Rectangle2D.Double(barW0, barL0, state.getBarWidth(), 714 barLength); 715 } 716 Paint itemPaint = getItemPaint(row, column); 717 g2.setPaint(itemPaint); 718 g2.fill(bar); 719 720 double x0 = bar.getMinX(); 721 double x1 = x0 + getXOffset(); 722 double x2 = bar.getMaxX(); 723 double x3 = x2 + getXOffset(); 724 725 double y0 = bar.getMinY() - getYOffset(); 726 double y1 = bar.getMinY(); 727 double y2 = bar.getMaxY() - getYOffset(); 728 double y3 = bar.getMaxY(); 729 730 GeneralPath bar3dRight = null; 731 GeneralPath bar3dTop; 732 if (barLength > 0.0) { 733 bar3dRight = new GeneralPath(); 734 bar3dRight.moveTo((float) x2, (float) y3); 735 bar3dRight.lineTo((float) x2, (float) y1); 736 bar3dRight.lineTo((float) x3, (float) y0); 737 bar3dRight.lineTo((float) x3, (float) y2); 738 bar3dRight.closePath(); 739 740 g2.setPaint(PaintAlpha.darker(itemPaint)); 741 g2.fill(bar3dRight); 742 } 743 744 bar3dTop = new GeneralPath(); 745 bar3dTop.moveTo((float) x0, (float) y1); 746 bar3dTop.lineTo((float) x1, (float) y0); 747 bar3dTop.lineTo((float) x3, (float) y0); 748 bar3dTop.lineTo((float) x2, (float) y1); 749 bar3dTop.closePath(); 750 g2.fill(bar3dTop); 751 752 if (isDrawBarOutline() 753 && state.getBarWidth() > BAR_OUTLINE_WIDTH_THRESHOLD) { 754 g2.setStroke(getItemOutlineStroke(row, column)); 755 g2.setPaint(getItemOutlinePaint(row, column)); 756 g2.draw(bar); 757 if (bar3dRight != null) { 758 g2.draw(bar3dRight); 759 } 760 g2.draw(bar3dTop); 761 } 762 763 CategoryItemLabelGenerator generator 764 = getItemLabelGenerator(row, column); 765 if (generator != null && isItemLabelVisible(row, column)) { 766 drawItemLabel(g2, dataset, row, column, plot, generator, bar, 767 (value < 0.0)); 768 } 769 770 // add an item entity, if this information is being collected 771 EntityCollection entities = state.getEntityCollection(); 772 if (entities != null) { 773 GeneralPath barOutline = new GeneralPath(); 774 barOutline.moveTo((float) x0, (float) y3); 775 barOutline.lineTo((float) x0, (float) y1); 776 barOutline.lineTo((float) x1, (float) y0); 777 barOutline.lineTo((float) x3, (float) y0); 778 barOutline.lineTo((float) x3, (float) y2); 779 barOutline.lineTo((float) x2, (float) y3); 780 barOutline.closePath(); 781 addItemEntity(entities, dataset, row, column, barOutline); 782 } 783 784 } 785 786 /** 787 * Tests this renderer for equality with an arbitrary object. 788 * 789 * @param obj the object (<code>null</code> permitted). 790 * 791 * @return A boolean. 792 */ 793 @Override 794 public boolean equals(Object obj) { 795 if (obj == this) { 796 return true; 797 } 798 if (!(obj instanceof BarRenderer3D)) { 799 return false; 800 } 801 BarRenderer3D that = (BarRenderer3D) obj; 802 if (this.xOffset != that.xOffset) { 803 return false; 804 } 805 if (this.yOffset != that.yOffset) { 806 return false; 807 } 808 if (!PaintUtilities.equal(this.wallPaint, that.wallPaint)) { 809 return false; 810 } 811 return super.equals(obj); 812 } 813 814 /** 815 * Provides serialization support. 816 * 817 * @param stream the output stream. 818 * 819 * @throws IOException if there is an I/O error. 820 */ 821 private void writeObject(ObjectOutputStream stream) throws IOException { 822 stream.defaultWriteObject(); 823 SerialUtilities.writePaint(this.wallPaint, stream); 824 } 825 826 /** 827 * Provides serialization support. 828 * 829 * @param stream the input stream. 830 * 831 * @throws IOException if there is an I/O error. 832 * @throws ClassNotFoundException if there is a classpath problem. 833 */ 834 private void readObject(ObjectInputStream stream) 835 throws IOException, ClassNotFoundException { 836 stream.defaultReadObject(); 837 this.wallPaint = SerialUtilities.readPaint(stream); 838 } 839 840}