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