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