001 /* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2011, 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 * GanttRenderer.java 029 * ------------------ 030 * (C) Copyright 2003-2009, by Object Refinery Limited. 031 * 032 * Original Author: David Gilbert (for Object Refinery Limited); 033 * Contributor(s): -; 034 * 035 * Changes 036 * ------- 037 * 16-Sep-2003 : Version 1 (DG); 038 * 23-Sep-2003 : Fixed Checkstyle issues (DG); 039 * 21-Oct-2003 : Bar width moved into CategoryItemRendererState (DG); 040 * 03-Feb-2004 : Added get/set methods for attributes (DG); 041 * 12-Aug-2004 : Fixed rendering problem with maxBarWidth attribute (DG); 042 * 05-Nov-2004 : Modified drawItem() signature (DG); 043 * 20-Apr-2005 : Renamed CategoryLabelGenerator 044 * --> CategoryItemLabelGenerator (DG); 045 * 01-Dec-2005 : Fix for bug 1369954, drawBarOutline flag ignored (DG); 046 * ------------- JFREECHART 1.0.x -------------------------------------------- 047 * 17-Jan-2006 : Set includeBaseInRange flag to false (DG); 048 * 20-Mar-2007 : Implemented equals() and fixed serialization (DG); 049 * 24-Jun-2008 : Added new barPainter mechanism (DG); 050 * 26-Jun-2008 : Added crosshair support (DG); 051 * 19-May-2009 : Fixed FindBugs warnings, patch by Michal Wozniak (DG); 052 * 053 */ 054 055 package org.jfree.chart.renderer.category; 056 057 import java.awt.Color; 058 import java.awt.Graphics2D; 059 import java.awt.Paint; 060 import java.awt.Stroke; 061 import java.awt.geom.Rectangle2D; 062 import java.io.IOException; 063 import java.io.ObjectInputStream; 064 import java.io.ObjectOutputStream; 065 import java.io.Serializable; 066 067 import org.jfree.chart.axis.CategoryAxis; 068 import org.jfree.chart.axis.ValueAxis; 069 import org.jfree.chart.entity.EntityCollection; 070 import org.jfree.chart.event.RendererChangeEvent; 071 import org.jfree.chart.labels.CategoryItemLabelGenerator; 072 import org.jfree.chart.plot.CategoryPlot; 073 import org.jfree.chart.plot.PlotOrientation; 074 import org.jfree.data.category.CategoryDataset; 075 import org.jfree.data.gantt.GanttCategoryDataset; 076 import org.jfree.io.SerialUtilities; 077 import org.jfree.ui.RectangleEdge; 078 import org.jfree.util.PaintUtilities; 079 080 /** 081 * A renderer for simple Gantt charts. The example shown 082 * here is generated by the <code>GanttDemo1.java</code> program 083 * included in the JFreeChart Demo Collection: 084 * <br><br> 085 * <img src="../../../../../images/GanttRendererSample.png" 086 * alt="GanttRendererSample.png" /> 087 */ 088 public class GanttRenderer extends IntervalBarRenderer 089 implements Serializable { 090 091 /** For serialization. */ 092 private static final long serialVersionUID = -4010349116350119512L; 093 094 /** The paint for displaying the percentage complete. */ 095 private transient Paint completePaint; 096 097 /** The paint for displaying the incomplete part of a task. */ 098 private transient Paint incompletePaint; 099 100 /** 101 * Controls the starting edge of the progress indicator (expressed as a 102 * percentage of the overall bar width). 103 */ 104 private double startPercent; 105 106 /** 107 * Controls the ending edge of the progress indicator (expressed as a 108 * percentage of the overall bar width). 109 */ 110 private double endPercent; 111 112 /** 113 * Creates a new renderer. 114 */ 115 public GanttRenderer() { 116 super(); 117 setIncludeBaseInRange(false); 118 this.completePaint = Color.green; 119 this.incompletePaint = Color.red; 120 this.startPercent = 0.35; 121 this.endPercent = 0.65; 122 } 123 124 /** 125 * Returns the paint used to show the percentage complete. 126 * 127 * @return The paint (never <code>null</code>. 128 * 129 * @see #setCompletePaint(Paint) 130 */ 131 public Paint getCompletePaint() { 132 return this.completePaint; 133 } 134 135 /** 136 * Sets the paint used to show the percentage complete and sends a 137 * {@link RendererChangeEvent} to all registered listeners. 138 * 139 * @param paint the paint (<code>null</code> not permitted). 140 * 141 * @see #getCompletePaint() 142 */ 143 public void setCompletePaint(Paint paint) { 144 if (paint == null) { 145 throw new IllegalArgumentException("Null 'paint' argument."); 146 } 147 this.completePaint = paint; 148 fireChangeEvent(); 149 } 150 151 /** 152 * Returns the paint used to show the percentage incomplete. 153 * 154 * @return The paint (never <code>null</code>). 155 * 156 * @see #setCompletePaint(Paint) 157 */ 158 public Paint getIncompletePaint() { 159 return this.incompletePaint; 160 } 161 162 /** 163 * Sets the paint used to show the percentage incomplete and sends a 164 * {@link RendererChangeEvent} to all registered listeners. 165 * 166 * @param paint the paint (<code>null</code> not permitted). 167 * 168 * @see #getIncompletePaint() 169 */ 170 public void setIncompletePaint(Paint paint) { 171 if (paint == null) { 172 throw new IllegalArgumentException("Null 'paint' argument."); 173 } 174 this.incompletePaint = paint; 175 fireChangeEvent(); 176 } 177 178 /** 179 * Returns the position of the start of the progress indicator, as a 180 * percentage of the bar width. 181 * 182 * @return The start percent. 183 * 184 * @see #setStartPercent(double) 185 */ 186 public double getStartPercent() { 187 return this.startPercent; 188 } 189 190 /** 191 * Sets the position of the start of the progress indicator, as a 192 * percentage of the bar width, and sends a {@link RendererChangeEvent} to 193 * all registered listeners. 194 * 195 * @param percent the percent. 196 * 197 * @see #getStartPercent() 198 */ 199 public void setStartPercent(double percent) { 200 this.startPercent = percent; 201 fireChangeEvent(); 202 } 203 204 /** 205 * Returns the position of the end of the progress indicator, as a 206 * percentage of the bar width. 207 * 208 * @return The end percent. 209 * 210 * @see #setEndPercent(double) 211 */ 212 public double getEndPercent() { 213 return this.endPercent; 214 } 215 216 /** 217 * Sets the position of the end of the progress indicator, as a percentage 218 * of the bar width, and sends a {@link RendererChangeEvent} to all 219 * registered listeners. 220 * 221 * @param percent the percent. 222 * 223 * @see #getEndPercent() 224 */ 225 public void setEndPercent(double percent) { 226 this.endPercent = percent; 227 fireChangeEvent(); 228 } 229 230 /** 231 * Draws the bar for a single (series, category) data item. 232 * 233 * @param g2 the graphics device. 234 * @param state the renderer state. 235 * @param dataArea the data area. 236 * @param plot the plot. 237 * @param domainAxis the domain axis. 238 * @param rangeAxis the range axis. 239 * @param dataset the dataset. 240 * @param row the row index (zero-based). 241 * @param column the column index (zero-based). 242 * @param pass the pass index. 243 */ 244 public void drawItem(Graphics2D g2, 245 CategoryItemRendererState state, 246 Rectangle2D dataArea, 247 CategoryPlot plot, 248 CategoryAxis domainAxis, 249 ValueAxis rangeAxis, 250 CategoryDataset dataset, 251 int row, 252 int column, 253 int pass) { 254 255 if (dataset instanceof GanttCategoryDataset) { 256 GanttCategoryDataset gcd = (GanttCategoryDataset) dataset; 257 drawTasks(g2, state, dataArea, plot, domainAxis, rangeAxis, gcd, 258 row, column); 259 } 260 else { // let the superclass handle it... 261 super.drawItem(g2, state, dataArea, plot, domainAxis, rangeAxis, 262 dataset, row, column, pass); 263 } 264 265 } 266 267 /** 268 * Draws the tasks/subtasks for one item. 269 * 270 * @param g2 the graphics device. 271 * @param state the renderer state. 272 * @param dataArea the data plot area. 273 * @param plot the plot. 274 * @param domainAxis the domain axis. 275 * @param rangeAxis the range axis. 276 * @param dataset the data. 277 * @param row the row index (zero-based). 278 * @param column the column index (zero-based). 279 */ 280 protected void drawTasks(Graphics2D g2, 281 CategoryItemRendererState state, 282 Rectangle2D dataArea, 283 CategoryPlot plot, 284 CategoryAxis domainAxis, 285 ValueAxis rangeAxis, 286 GanttCategoryDataset dataset, 287 int row, 288 int column) { 289 290 int count = dataset.getSubIntervalCount(row, column); 291 if (count == 0) { 292 drawTask(g2, state, dataArea, plot, domainAxis, rangeAxis, 293 dataset, row, column); 294 } 295 296 PlotOrientation orientation = plot.getOrientation(); 297 for (int subinterval = 0; subinterval < count; subinterval++) { 298 299 RectangleEdge rangeAxisLocation = plot.getRangeAxisEdge(); 300 301 // value 0 302 Number value0 = dataset.getStartValue(row, column, subinterval); 303 if (value0 == null) { 304 return; 305 } 306 double translatedValue0 = rangeAxis.valueToJava2D( 307 value0.doubleValue(), dataArea, rangeAxisLocation); 308 309 // value 1 310 Number value1 = dataset.getEndValue(row, column, subinterval); 311 if (value1 == null) { 312 return; 313 } 314 double translatedValue1 = rangeAxis.valueToJava2D( 315 value1.doubleValue(), dataArea, rangeAxisLocation); 316 317 if (translatedValue1 < translatedValue0) { 318 double temp = translatedValue1; 319 translatedValue1 = translatedValue0; 320 translatedValue0 = temp; 321 } 322 323 double rectStart = calculateBarW0(plot, plot.getOrientation(), 324 dataArea, domainAxis, state, row, column); 325 double rectLength = Math.abs(translatedValue1 - translatedValue0); 326 double rectBreadth = state.getBarWidth(); 327 328 // DRAW THE BARS... 329 Rectangle2D bar = null; 330 RectangleEdge barBase = null; 331 if (plot.getOrientation() == PlotOrientation.HORIZONTAL) { 332 bar = new Rectangle2D.Double(translatedValue0, rectStart, 333 rectLength, rectBreadth); 334 barBase = RectangleEdge.LEFT; 335 } 336 else if (plot.getOrientation() == PlotOrientation.VERTICAL) { 337 bar = new Rectangle2D.Double(rectStart, translatedValue0, 338 rectBreadth, rectLength); 339 barBase = RectangleEdge.BOTTOM; 340 } 341 342 Rectangle2D completeBar = null; 343 Rectangle2D incompleteBar = null; 344 Number percent = dataset.getPercentComplete(row, column, 345 subinterval); 346 double start = getStartPercent(); 347 double end = getEndPercent(); 348 if (percent != null) { 349 double p = percent.doubleValue(); 350 if (orientation == PlotOrientation.HORIZONTAL) { 351 completeBar = new Rectangle2D.Double(translatedValue0, 352 rectStart + start * rectBreadth, rectLength * p, 353 rectBreadth * (end - start)); 354 incompleteBar = new Rectangle2D.Double(translatedValue0 355 + rectLength * p, rectStart + start * rectBreadth, 356 rectLength * (1 - p), rectBreadth * (end - start)); 357 } 358 else if (orientation == PlotOrientation.VERTICAL) { 359 completeBar = new Rectangle2D.Double(rectStart + start 360 * rectBreadth, translatedValue0 + rectLength 361 * (1 - p), rectBreadth * (end - start), 362 rectLength * p); 363 incompleteBar = new Rectangle2D.Double(rectStart + start 364 * rectBreadth, translatedValue0, rectBreadth 365 * (end - start), rectLength * (1 - p)); 366 } 367 368 } 369 370 if (getShadowsVisible()) { 371 getBarPainter().paintBarShadow(g2, this, row, column, bar, 372 barBase, true); 373 } 374 getBarPainter().paintBar(g2, this, row, column, bar, barBase); 375 376 if (completeBar != null) { 377 g2.setPaint(getCompletePaint()); 378 g2.fill(completeBar); 379 } 380 if (incompleteBar != null) { 381 g2.setPaint(getIncompletePaint()); 382 g2.fill(incompleteBar); 383 } 384 if (isDrawBarOutline() 385 && state.getBarWidth() > BAR_OUTLINE_WIDTH_THRESHOLD) { 386 g2.setStroke(getItemStroke(row, column)); 387 g2.setPaint(getItemOutlinePaint(row, column)); 388 g2.draw(bar); 389 } 390 391 if (subinterval == count - 1) { 392 // submit the current data point as a crosshair candidate 393 int datasetIndex = plot.indexOf(dataset); 394 Comparable columnKey = dataset.getColumnKey(column); 395 Comparable rowKey = dataset.getRowKey(row); 396 double xx = domainAxis.getCategorySeriesMiddle(columnKey, 397 rowKey, dataset, getItemMargin(), dataArea, 398 plot.getDomainAxisEdge()); 399 updateCrosshairValues(state.getCrosshairState(), 400 dataset.getRowKey(row), dataset.getColumnKey(column), 401 value1.doubleValue(), datasetIndex, xx, 402 translatedValue1, orientation); 403 404 } 405 // collect entity and tool tip information... 406 if (state.getInfo() != null) { 407 EntityCollection entities = state.getEntityCollection(); 408 if (entities != null) { 409 addItemEntity(entities, dataset, row, column, bar); 410 } 411 } 412 } 413 } 414 415 /** 416 * Draws a single task. 417 * 418 * @param g2 the graphics device. 419 * @param state the renderer state. 420 * @param dataArea the data plot area. 421 * @param plot the plot. 422 * @param domainAxis the domain axis. 423 * @param rangeAxis the range axis. 424 * @param dataset the data. 425 * @param row the row index (zero-based). 426 * @param column the column index (zero-based). 427 */ 428 protected void drawTask(Graphics2D g2, 429 CategoryItemRendererState state, 430 Rectangle2D dataArea, 431 CategoryPlot plot, 432 CategoryAxis domainAxis, 433 ValueAxis rangeAxis, 434 GanttCategoryDataset dataset, 435 int row, 436 int column) { 437 438 PlotOrientation orientation = plot.getOrientation(); 439 RectangleEdge rangeAxisLocation = plot.getRangeAxisEdge(); 440 441 // Y0 442 Number value0 = dataset.getEndValue(row, column); 443 if (value0 == null) { 444 return; 445 } 446 double java2dValue0 = rangeAxis.valueToJava2D(value0.doubleValue(), 447 dataArea, rangeAxisLocation); 448 449 // Y1 450 Number value1 = dataset.getStartValue(row, column); 451 if (value1 == null) { 452 return; 453 } 454 double java2dValue1 = rangeAxis.valueToJava2D(value1.doubleValue(), 455 dataArea, rangeAxisLocation); 456 457 if (java2dValue1 < java2dValue0) { 458 double temp = java2dValue1; 459 java2dValue1 = java2dValue0; 460 java2dValue0 = temp; 461 value1 = value0; 462 } 463 464 double rectStart = calculateBarW0(plot, orientation, dataArea, 465 domainAxis, state, row, column); 466 double rectBreadth = state.getBarWidth(); 467 double rectLength = Math.abs(java2dValue1 - java2dValue0); 468 469 Rectangle2D bar = null; 470 RectangleEdge barBase = null; 471 if (orientation == PlotOrientation.HORIZONTAL) { 472 bar = new Rectangle2D.Double(java2dValue0, rectStart, rectLength, 473 rectBreadth); 474 barBase = RectangleEdge.LEFT; 475 } 476 else if (orientation == PlotOrientation.VERTICAL) { 477 bar = new Rectangle2D.Double(rectStart, java2dValue1, rectBreadth, 478 rectLength); 479 barBase = RectangleEdge.BOTTOM; 480 } 481 482 Rectangle2D completeBar = null; 483 Rectangle2D incompleteBar = null; 484 Number percent = dataset.getPercentComplete(row, column); 485 double start = getStartPercent(); 486 double end = getEndPercent(); 487 if (percent != null) { 488 double p = percent.doubleValue(); 489 if (plot.getOrientation() == PlotOrientation.HORIZONTAL) { 490 completeBar = new Rectangle2D.Double(java2dValue0, 491 rectStart + start * rectBreadth, rectLength * p, 492 rectBreadth * (end - start)); 493 incompleteBar = new Rectangle2D.Double(java2dValue0 494 + rectLength * p, rectStart + start * rectBreadth, 495 rectLength * (1 - p), rectBreadth * (end - start)); 496 } 497 else if (plot.getOrientation() == PlotOrientation.VERTICAL) { 498 completeBar = new Rectangle2D.Double(rectStart + start 499 * rectBreadth, java2dValue1 + rectLength * (1 - p), 500 rectBreadth * (end - start), rectLength * p); 501 incompleteBar = new Rectangle2D.Double(rectStart + start 502 * rectBreadth, java2dValue1, rectBreadth * (end 503 - start), rectLength * (1 - p)); 504 } 505 506 } 507 508 if (getShadowsVisible()) { 509 getBarPainter().paintBarShadow(g2, this, row, column, bar, 510 barBase, true); 511 } 512 getBarPainter().paintBar(g2, this, row, column, bar, barBase); 513 514 if (completeBar != null) { 515 g2.setPaint(getCompletePaint()); 516 g2.fill(completeBar); 517 } 518 if (incompleteBar != null) { 519 g2.setPaint(getIncompletePaint()); 520 g2.fill(incompleteBar); 521 } 522 523 // draw the outline... 524 if (isDrawBarOutline() 525 && state.getBarWidth() > BAR_OUTLINE_WIDTH_THRESHOLD) { 526 Stroke stroke = getItemOutlineStroke(row, column); 527 Paint paint = getItemOutlinePaint(row, column); 528 if (stroke != null && paint != null) { 529 g2.setStroke(stroke); 530 g2.setPaint(paint); 531 g2.draw(bar); 532 } 533 } 534 535 CategoryItemLabelGenerator generator = getItemLabelGenerator(row, 536 column); 537 if (generator != null && isItemLabelVisible(row, column)) { 538 drawItemLabel(g2, dataset, row, column, plot, generator, bar, 539 false); 540 } 541 542 // submit the current data point as a crosshair candidate 543 int datasetIndex = plot.indexOf(dataset); 544 Comparable columnKey = dataset.getColumnKey(column); 545 Comparable rowKey = dataset.getRowKey(row); 546 double xx = domainAxis.getCategorySeriesMiddle(columnKey, rowKey, 547 dataset, getItemMargin(), dataArea, plot.getDomainAxisEdge()); 548 updateCrosshairValues(state.getCrosshairState(), 549 dataset.getRowKey(row), dataset.getColumnKey(column), 550 value1.doubleValue(), datasetIndex, xx, java2dValue1, 551 orientation); 552 553 // collect entity and tool tip information... 554 EntityCollection entities = state.getEntityCollection(); 555 if (entities != null) { 556 addItemEntity(entities, dataset, row, column, bar); 557 } 558 } 559 560 /** 561 * Returns the Java2D coordinate for the middle of the specified data item. 562 * 563 * @param rowKey the row key. 564 * @param columnKey the column key. 565 * @param dataset the dataset. 566 * @param axis the axis. 567 * @param area the drawing area. 568 * @param edge the edge along which the axis lies. 569 * 570 * @return The Java2D coordinate. 571 * 572 * @since 1.0.11 573 */ 574 public double getItemMiddle(Comparable rowKey, Comparable columnKey, 575 CategoryDataset dataset, CategoryAxis axis, Rectangle2D area, 576 RectangleEdge edge) { 577 return axis.getCategorySeriesMiddle(columnKey, rowKey, dataset, 578 getItemMargin(), area, edge); 579 } 580 581 /** 582 * Tests this renderer for equality with an arbitrary object. 583 * 584 * @param obj the object (<code>null</code> permitted). 585 * 586 * @return A boolean. 587 */ 588 public boolean equals(Object obj) { 589 if (obj == this) { 590 return true; 591 } 592 if (!(obj instanceof GanttRenderer)) { 593 return false; 594 } 595 GanttRenderer that = (GanttRenderer) obj; 596 if (!PaintUtilities.equal(this.completePaint, that.completePaint)) { 597 return false; 598 } 599 if (!PaintUtilities.equal(this.incompletePaint, that.incompletePaint)) { 600 return false; 601 } 602 if (this.startPercent != that.startPercent) { 603 return false; 604 } 605 if (this.endPercent != that.endPercent) { 606 return false; 607 } 608 return super.equals(obj); 609 } 610 611 /** 612 * Provides serialization support. 613 * 614 * @param stream the output stream. 615 * 616 * @throws IOException if there is an I/O error. 617 */ 618 private void writeObject(ObjectOutputStream stream) throws IOException { 619 stream.defaultWriteObject(); 620 SerialUtilities.writePaint(this.completePaint, stream); 621 SerialUtilities.writePaint(this.incompletePaint, stream); 622 } 623 624 /** 625 * Provides serialization support. 626 * 627 * @param stream the input stream. 628 * 629 * @throws IOException if there is an I/O error. 630 * @throws ClassNotFoundException if there is a classpath problem. 631 */ 632 private void readObject(ObjectInputStream stream) 633 throws IOException, ClassNotFoundException { 634 stream.defaultReadObject(); 635 this.completePaint = SerialUtilities.readPaint(stream); 636 this.incompletePaint = SerialUtilities.readPaint(stream); 637 } 638 639 }