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