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