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 052 package org.jfree.chart.renderer.category; 053 054 import java.awt.Color; 055 import java.awt.Graphics2D; 056 import java.awt.Paint; 057 import java.awt.Stroke; 058 import java.awt.geom.Rectangle2D; 059 import java.io.IOException; 060 import java.io.ObjectInputStream; 061 import java.io.ObjectOutputStream; 062 import java.io.Serializable; 063 064 import org.jfree.chart.axis.CategoryAxis; 065 import org.jfree.chart.axis.ValueAxis; 066 import org.jfree.chart.entity.CategoryItemEntity; 067 import org.jfree.chart.entity.EntityCollection; 068 import org.jfree.chart.event.RendererChangeEvent; 069 import org.jfree.chart.labels.CategoryItemLabelGenerator; 070 import org.jfree.chart.labels.CategoryToolTipGenerator; 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. 081 */ 082 public 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 fireChangeEvent(); 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 fireChangeEvent(); 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, and sends a {@link RendererChangeEvent} to 187 * all registered listeners. 188 * 189 * @param percent the percent. 190 * 191 * @see #getStartPercent() 192 */ 193 public void setStartPercent(double percent) { 194 this.startPercent = percent; 195 fireChangeEvent(); 196 } 197 198 /** 199 * Returns the position of the end of the progress indicator, as a 200 * percentage of the bar width. 201 * 202 * @return The end percent. 203 * 204 * @see #setEndPercent(double) 205 */ 206 public double getEndPercent() { 207 return this.endPercent; 208 } 209 210 /** 211 * Sets the position of the end of the progress indicator, as a percentage 212 * of the bar width, and sends a {@link RendererChangeEvent} to all 213 * registered listeners. 214 * 215 * @param percent the percent. 216 * 217 * @see #getEndPercent() 218 */ 219 public void setEndPercent(double percent) { 220 this.endPercent = percent; 221 fireChangeEvent(); 222 } 223 224 /** 225 * Draws the bar for a single (series, category) data item. 226 * 227 * @param g2 the graphics device. 228 * @param state the renderer state. 229 * @param dataArea the data area. 230 * @param plot the plot. 231 * @param domainAxis the domain axis. 232 * @param rangeAxis the range axis. 233 * @param dataset the dataset. 234 * @param row the row index (zero-based). 235 * @param column the column index (zero-based). 236 * @param pass the pass index. 237 */ 238 public void drawItem(Graphics2D g2, 239 CategoryItemRendererState state, 240 Rectangle2D dataArea, 241 CategoryPlot plot, 242 CategoryAxis domainAxis, 243 ValueAxis rangeAxis, 244 CategoryDataset dataset, 245 int row, 246 int column, 247 int pass) { 248 249 if (dataset instanceof GanttCategoryDataset) { 250 GanttCategoryDataset gcd = (GanttCategoryDataset) dataset; 251 drawTasks(g2, state, dataArea, plot, domainAxis, rangeAxis, gcd, 252 row, column); 253 } 254 else { // let the superclass handle it... 255 super.drawItem(g2, state, dataArea, plot, domainAxis, rangeAxis, 256 dataset, row, column, pass); 257 } 258 259 } 260 261 /** 262 * Draws the tasks/subtasks for one item. 263 * 264 * @param g2 the graphics device. 265 * @param state the renderer state. 266 * @param dataArea the data plot area. 267 * @param plot the plot. 268 * @param domainAxis the domain axis. 269 * @param rangeAxis the range axis. 270 * @param dataset the data. 271 * @param row the row index (zero-based). 272 * @param column the column index (zero-based). 273 */ 274 protected void drawTasks(Graphics2D g2, 275 CategoryItemRendererState state, 276 Rectangle2D dataArea, 277 CategoryPlot plot, 278 CategoryAxis domainAxis, 279 ValueAxis rangeAxis, 280 GanttCategoryDataset dataset, 281 int row, 282 int column) { 283 284 int count = dataset.getSubIntervalCount(row, column); 285 if (count == 0) { 286 drawTask(g2, state, dataArea, plot, domainAxis, rangeAxis, 287 dataset, row, column); 288 } 289 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 324 if (plot.getOrientation() == PlotOrientation.HORIZONTAL) { 325 bar = new Rectangle2D.Double(translatedValue0, rectStart, 326 rectLength, rectBreadth); 327 } 328 else if (plot.getOrientation() == PlotOrientation.VERTICAL) { 329 bar = new Rectangle2D.Double(rectStart, translatedValue0, 330 rectBreadth, rectLength); 331 } 332 333 Rectangle2D completeBar = null; 334 Rectangle2D incompleteBar = null; 335 Number percent = dataset.getPercentComplete(row, column, 336 subinterval); 337 double start = getStartPercent(); 338 double end = getEndPercent(); 339 if (percent != null) { 340 double p = percent.doubleValue(); 341 if (plot.getOrientation() == PlotOrientation.HORIZONTAL) { 342 completeBar = new Rectangle2D.Double(translatedValue0, 343 rectStart + start * rectBreadth, rectLength * p, 344 rectBreadth * (end - start)); 345 incompleteBar = new Rectangle2D.Double(translatedValue0 346 + rectLength * p, rectStart + start * rectBreadth, 347 rectLength * (1 - p), rectBreadth * (end - start)); 348 } 349 else if (plot.getOrientation() == PlotOrientation.VERTICAL) { 350 completeBar = new Rectangle2D.Double(rectStart + start 351 * rectBreadth, translatedValue0 + rectLength 352 * (1 - p), rectBreadth * (end - start), 353 rectLength * p); 354 incompleteBar = new Rectangle2D.Double(rectStart + start 355 * rectBreadth, translatedValue0, rectBreadth 356 * (end - start), rectLength * (1 - p)); 357 } 358 359 } 360 361 Paint seriesPaint = getItemPaint(row, column); 362 g2.setPaint(seriesPaint); 363 g2.fill(bar); 364 if (completeBar != null) { 365 g2.setPaint(getCompletePaint()); 366 g2.fill(completeBar); 367 } 368 if (incompleteBar != null) { 369 g2.setPaint(getIncompletePaint()); 370 g2.fill(incompleteBar); 371 } 372 if (isDrawBarOutline() 373 && state.getBarWidth() > BAR_OUTLINE_WIDTH_THRESHOLD) { 374 g2.setStroke(getItemStroke(row, column)); 375 g2.setPaint(getItemOutlinePaint(row, column)); 376 g2.draw(bar); 377 } 378 379 // collect entity and tool tip information... 380 if (state.getInfo() != null) { 381 EntityCollection entities = state.getEntityCollection(); 382 if (entities != null) { 383 String tip = null; 384 if (getToolTipGenerator(row, column) != null) { 385 tip = getToolTipGenerator(row, column).generateToolTip( 386 dataset, row, column); 387 } 388 String url = null; 389 if (getItemURLGenerator(row, column) != null) { 390 url = getItemURLGenerator(row, column).generateURL( 391 dataset, row, column); 392 } 393 CategoryItemEntity entity = new CategoryItemEntity( 394 bar, tip, url, dataset, dataset.getRowKey(row), 395 dataset.getColumnKey(column)); 396 entities.add(entity); 397 } 398 } 399 } 400 } 401 402 /** 403 * Draws a single task. 404 * 405 * @param g2 the graphics device. 406 * @param state the renderer state. 407 * @param dataArea the data plot area. 408 * @param plot the plot. 409 * @param domainAxis the domain axis. 410 * @param rangeAxis the range axis. 411 * @param dataset the data. 412 * @param row the row index (zero-based). 413 * @param column the column index (zero-based). 414 */ 415 protected void drawTask(Graphics2D g2, 416 CategoryItemRendererState state, 417 Rectangle2D dataArea, 418 CategoryPlot plot, 419 CategoryAxis domainAxis, 420 ValueAxis rangeAxis, 421 GanttCategoryDataset dataset, 422 int row, 423 int column) { 424 425 PlotOrientation orientation = plot.getOrientation(); 426 427 RectangleEdge rangeAxisLocation = plot.getRangeAxisEdge(); 428 429 // Y0 430 Number value0 = dataset.getEndValue(row, column); 431 if (value0 == null) { 432 return; 433 } 434 double java2dValue0 = rangeAxis.valueToJava2D(value0.doubleValue(), 435 dataArea, rangeAxisLocation); 436 437 // Y1 438 Number value1 = dataset.getStartValue(row, column); 439 if (value1 == null) { 440 return; 441 } 442 double java2dValue1 = rangeAxis.valueToJava2D(value1.doubleValue(), 443 dataArea, rangeAxisLocation); 444 445 if (java2dValue1 < java2dValue0) { 446 double temp = java2dValue1; 447 java2dValue1 = java2dValue0; 448 java2dValue0 = temp; 449 Number tempNum = value1; 450 value1 = value0; 451 value0 = tempNum; 452 } 453 454 double rectStart = calculateBarW0(plot, orientation, dataArea, 455 domainAxis, state, row, column); 456 double rectBreadth = state.getBarWidth(); 457 double rectLength = Math.abs(java2dValue1 - java2dValue0); 458 459 Rectangle2D bar = null; 460 if (orientation == PlotOrientation.HORIZONTAL) { 461 bar = new Rectangle2D.Double(java2dValue0, rectStart, rectLength, 462 rectBreadth); 463 } 464 else if (orientation == PlotOrientation.VERTICAL) { 465 bar = new Rectangle2D.Double(rectStart, java2dValue1, rectBreadth, 466 rectLength); 467 } 468 469 Rectangle2D completeBar = null; 470 Rectangle2D incompleteBar = null; 471 Number percent = dataset.getPercentComplete(row, column); 472 double start = getStartPercent(); 473 double end = getEndPercent(); 474 if (percent != null) { 475 double p = percent.doubleValue(); 476 if (plot.getOrientation() == PlotOrientation.HORIZONTAL) { 477 completeBar = new Rectangle2D.Double(java2dValue0, 478 rectStart + start * rectBreadth, rectLength * p, 479 rectBreadth * (end - start)); 480 incompleteBar = new Rectangle2D.Double(java2dValue0 481 + rectLength * p, rectStart + start * rectBreadth, 482 rectLength * (1 - p), rectBreadth * (end - start)); 483 } 484 else if (plot.getOrientation() == PlotOrientation.VERTICAL) { 485 completeBar = new Rectangle2D.Double(rectStart + start 486 * rectBreadth, java2dValue1 + rectLength * (1 - p), 487 rectBreadth * (end - start), rectLength * p); 488 incompleteBar = new Rectangle2D.Double(rectStart + start 489 * rectBreadth, java2dValue1, rectBreadth * (end 490 - start), rectLength * (1 - p)); 491 } 492 493 } 494 495 Paint seriesPaint = getItemPaint(row, column); 496 g2.setPaint(seriesPaint); 497 g2.fill(bar); 498 499 if (completeBar != null) { 500 g2.setPaint(getCompletePaint()); 501 g2.fill(completeBar); 502 } 503 if (incompleteBar != null) { 504 g2.setPaint(getIncompletePaint()); 505 g2.fill(incompleteBar); 506 } 507 508 // draw the outline... 509 if (isDrawBarOutline() 510 && state.getBarWidth() > BAR_OUTLINE_WIDTH_THRESHOLD) { 511 Stroke stroke = getItemOutlineStroke(row, column); 512 Paint paint = getItemOutlinePaint(row, column); 513 if (stroke != null && paint != null) { 514 g2.setStroke(stroke); 515 g2.setPaint(paint); 516 g2.draw(bar); 517 } 518 } 519 520 CategoryItemLabelGenerator generator = getItemLabelGenerator(row, 521 column); 522 if (generator != null && isItemLabelVisible(row, column)) { 523 drawItemLabel(g2, dataset, row, column, plot, generator, bar, 524 false); 525 } 526 527 // collect entity and tool tip information... 528 if (state.getInfo() != null) { 529 EntityCollection entities = state.getEntityCollection(); 530 if (entities != null) { 531 String tip = null; 532 CategoryToolTipGenerator tipster = getToolTipGenerator(row, 533 column); 534 if (tipster != null) { 535 tip = tipster.generateToolTip(dataset, row, column); 536 } 537 String url = null; 538 if (getItemURLGenerator(row, column) != null) { 539 url = getItemURLGenerator(row, column).generateURL( 540 dataset, row, column); 541 } 542 CategoryItemEntity entity = new CategoryItemEntity(bar, tip, 543 url, dataset, dataset.getRowKey(row), 544 dataset.getColumnKey(column)); 545 entities.add(entity); 546 } 547 } 548 549 } 550 551 /** 552 * Tests this renderer for equality with an arbitrary object. 553 * 554 * @param obj the object (<code>null</code> permitted). 555 * 556 * @return A boolean. 557 */ 558 public boolean equals(Object obj) { 559 if (obj == this) { 560 return true; 561 } 562 if (!(obj instanceof GanttRenderer)) { 563 return false; 564 } 565 GanttRenderer that = (GanttRenderer) obj; 566 if (!PaintUtilities.equal(this.completePaint, that.completePaint)) { 567 return false; 568 } 569 if (!PaintUtilities.equal(this.incompletePaint, that.incompletePaint)) { 570 return false; 571 } 572 if (this.startPercent != that.startPercent) { 573 return false; 574 } 575 if (this.endPercent != that.endPercent) { 576 return false; 577 } 578 return super.equals(obj); 579 } 580 581 /** 582 * Provides serialization support. 583 * 584 * @param stream the output stream. 585 * 586 * @throws IOException if there is an I/O error. 587 */ 588 private void writeObject(ObjectOutputStream stream) throws IOException { 589 stream.defaultWriteObject(); 590 SerialUtilities.writePaint(this.completePaint, stream); 591 SerialUtilities.writePaint(this.incompletePaint, stream); 592 } 593 594 /** 595 * Provides serialization support. 596 * 597 * @param stream the input stream. 598 * 599 * @throws IOException if there is an I/O error. 600 * @throws ClassNotFoundException if there is a classpath problem. 601 */ 602 private void readObject(ObjectInputStream stream) 603 throws IOException, ClassNotFoundException { 604 stream.defaultReadObject(); 605 this.completePaint = SerialUtilities.readPaint(stream); 606 this.incompletePaint = SerialUtilities.readPaint(stream); 607 } 608 609 }