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