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. 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 PlotOrientation orientation = plot.getOrientation(); 291 for (int subinterval = 0; subinterval < count; subinterval++) { 292 293 RectangleEdge rangeAxisLocation = plot.getRangeAxisEdge(); 294 295 // value 0 296 Number value0 = dataset.getStartValue(row, column, subinterval); 297 if (value0 == null) { 298 return; 299 } 300 double translatedValue0 = rangeAxis.valueToJava2D( 301 value0.doubleValue(), dataArea, rangeAxisLocation); 302 303 // value 1 304 Number value1 = dataset.getEndValue(row, column, subinterval); 305 if (value1 == null) { 306 return; 307 } 308 double translatedValue1 = rangeAxis.valueToJava2D( 309 value1.doubleValue(), dataArea, rangeAxisLocation); 310 311 if (translatedValue1 < translatedValue0) { 312 double temp = translatedValue1; 313 translatedValue1 = translatedValue0; 314 translatedValue0 = temp; 315 } 316 317 double rectStart = calculateBarW0(plot, plot.getOrientation(), 318 dataArea, domainAxis, state, row, column); 319 double rectLength = Math.abs(translatedValue1 - translatedValue0); 320 double rectBreadth = state.getBarWidth(); 321 322 // DRAW THE BARS... 323 Rectangle2D bar = null; 324 RectangleEdge barBase = null; 325 if (plot.getOrientation() == PlotOrientation.HORIZONTAL) { 326 bar = new Rectangle2D.Double(translatedValue0, rectStart, 327 rectLength, rectBreadth); 328 barBase = RectangleEdge.LEFT; 329 } 330 else if (plot.getOrientation() == PlotOrientation.VERTICAL) { 331 bar = new Rectangle2D.Double(rectStart, translatedValue0, 332 rectBreadth, rectLength); 333 barBase = RectangleEdge.BOTTOM; 334 } 335 336 Rectangle2D completeBar = null; 337 Rectangle2D incompleteBar = null; 338 Number percent = dataset.getPercentComplete(row, column, 339 subinterval); 340 double start = getStartPercent(); 341 double end = getEndPercent(); 342 if (percent != null) { 343 double p = percent.doubleValue(); 344 if (orientation == PlotOrientation.HORIZONTAL) { 345 completeBar = new Rectangle2D.Double(translatedValue0, 346 rectStart + start * rectBreadth, rectLength * p, 347 rectBreadth * (end - start)); 348 incompleteBar = new Rectangle2D.Double(translatedValue0 349 + rectLength * p, rectStart + start * rectBreadth, 350 rectLength * (1 - p), rectBreadth * (end - start)); 351 } 352 else if (orientation == PlotOrientation.VERTICAL) { 353 completeBar = new Rectangle2D.Double(rectStart + start 354 * rectBreadth, translatedValue0 + rectLength 355 * (1 - p), rectBreadth * (end - start), 356 rectLength * p); 357 incompleteBar = new Rectangle2D.Double(rectStart + start 358 * rectBreadth, translatedValue0, rectBreadth 359 * (end - start), rectLength * (1 - p)); 360 } 361 362 } 363 364 if (getShadowsVisible()) { 365 getBarPainter().paintBarShadow(g2, this, row, column, bar, 366 barBase, true); 367 } 368 getBarPainter().paintBar(g2, this, row, column, bar, barBase); 369 370 if (completeBar != null) { 371 g2.setPaint(getCompletePaint()); 372 g2.fill(completeBar); 373 } 374 if (incompleteBar != null) { 375 g2.setPaint(getIncompletePaint()); 376 g2.fill(incompleteBar); 377 } 378 if (isDrawBarOutline() 379 && state.getBarWidth() > BAR_OUTLINE_WIDTH_THRESHOLD) { 380 g2.setStroke(getItemStroke(row, column)); 381 g2.setPaint(getItemOutlinePaint(row, column)); 382 g2.draw(bar); 383 } 384 385 if (subinterval == count - 1) { 386 // submit the current data point as a crosshair candidate 387 int datasetIndex = plot.indexOf(dataset); 388 Comparable columnKey = dataset.getColumnKey(column); 389 Comparable rowKey = dataset.getRowKey(row); 390 double xx = domainAxis.getCategorySeriesMiddle(columnKey, 391 rowKey, dataset, getItemMargin(), dataArea, 392 plot.getDomainAxisEdge()); 393 updateCrosshairValues(state.getCrosshairState(), 394 dataset.getRowKey(row), dataset.getColumnKey(column), 395 value1.doubleValue(), datasetIndex, xx, 396 translatedValue1, orientation); 397 398 } 399 // collect entity and tool tip information... 400 if (state.getInfo() != null) { 401 EntityCollection entities = state.getEntityCollection(); 402 if (entities != null) { 403 addItemEntity(entities, dataset, row, column, bar); 404 } 405 } 406 } 407 } 408 409 /** 410 * Draws a single task. 411 * 412 * @param g2 the graphics device. 413 * @param state the renderer state. 414 * @param dataArea the data plot area. 415 * @param plot the plot. 416 * @param domainAxis the domain axis. 417 * @param rangeAxis the range axis. 418 * @param dataset the data. 419 * @param row the row index (zero-based). 420 * @param column the column index (zero-based). 421 */ 422 protected void drawTask(Graphics2D g2, 423 CategoryItemRendererState state, 424 Rectangle2D dataArea, 425 CategoryPlot plot, 426 CategoryAxis domainAxis, 427 ValueAxis rangeAxis, 428 GanttCategoryDataset dataset, 429 int row, 430 int column) { 431 432 PlotOrientation orientation = plot.getOrientation(); 433 RectangleEdge rangeAxisLocation = plot.getRangeAxisEdge(); 434 435 // Y0 436 Number value0 = dataset.getEndValue(row, column); 437 if (value0 == null) { 438 return; 439 } 440 double java2dValue0 = rangeAxis.valueToJava2D(value0.doubleValue(), 441 dataArea, rangeAxisLocation); 442 443 // Y1 444 Number value1 = dataset.getStartValue(row, column); 445 if (value1 == null) { 446 return; 447 } 448 double java2dValue1 = rangeAxis.valueToJava2D(value1.doubleValue(), 449 dataArea, rangeAxisLocation); 450 451 if (java2dValue1 < java2dValue0) { 452 double temp = java2dValue1; 453 java2dValue1 = java2dValue0; 454 java2dValue0 = temp; 455 Number tempNum = value1; 456 value1 = value0; 457 value0 = tempNum; 458 } 459 460 double rectStart = calculateBarW0(plot, orientation, dataArea, 461 domainAxis, state, row, column); 462 double rectBreadth = state.getBarWidth(); 463 double rectLength = Math.abs(java2dValue1 - java2dValue0); 464 465 Rectangle2D bar = null; 466 RectangleEdge barBase = null; 467 if (orientation == PlotOrientation.HORIZONTAL) { 468 bar = new Rectangle2D.Double(java2dValue0, rectStart, rectLength, 469 rectBreadth); 470 barBase = RectangleEdge.LEFT; 471 } 472 else if (orientation == PlotOrientation.VERTICAL) { 473 bar = new Rectangle2D.Double(rectStart, java2dValue1, rectBreadth, 474 rectLength); 475 barBase = RectangleEdge.BOTTOM; 476 } 477 478 Rectangle2D completeBar = null; 479 Rectangle2D incompleteBar = null; 480 Number percent = dataset.getPercentComplete(row, column); 481 double start = getStartPercent(); 482 double end = getEndPercent(); 483 if (percent != null) { 484 double p = percent.doubleValue(); 485 if (plot.getOrientation() == PlotOrientation.HORIZONTAL) { 486 completeBar = new Rectangle2D.Double(java2dValue0, 487 rectStart + start * rectBreadth, rectLength * p, 488 rectBreadth * (end - start)); 489 incompleteBar = new Rectangle2D.Double(java2dValue0 490 + rectLength * p, rectStart + start * rectBreadth, 491 rectLength * (1 - p), rectBreadth * (end - start)); 492 } 493 else if (plot.getOrientation() == PlotOrientation.VERTICAL) { 494 completeBar = new Rectangle2D.Double(rectStart + start 495 * rectBreadth, java2dValue1 + rectLength * (1 - p), 496 rectBreadth * (end - start), rectLength * p); 497 incompleteBar = new Rectangle2D.Double(rectStart + start 498 * rectBreadth, java2dValue1, rectBreadth * (end 499 - start), rectLength * (1 - p)); 500 } 501 502 } 503 504 if (getShadowsVisible()) { 505 getBarPainter().paintBarShadow(g2, this, row, column, bar, 506 barBase, true); 507 } 508 getBarPainter().paintBar(g2, this, row, column, bar, barBase); 509 510 if (completeBar != null) { 511 g2.setPaint(getCompletePaint()); 512 g2.fill(completeBar); 513 } 514 if (incompleteBar != null) { 515 g2.setPaint(getIncompletePaint()); 516 g2.fill(incompleteBar); 517 } 518 519 // draw the outline... 520 if (isDrawBarOutline() 521 && state.getBarWidth() > BAR_OUTLINE_WIDTH_THRESHOLD) { 522 Stroke stroke = getItemOutlineStroke(row, column); 523 Paint paint = getItemOutlinePaint(row, column); 524 if (stroke != null && paint != null) { 525 g2.setStroke(stroke); 526 g2.setPaint(paint); 527 g2.draw(bar); 528 } 529 } 530 531 CategoryItemLabelGenerator generator = getItemLabelGenerator(row, 532 column); 533 if (generator != null && isItemLabelVisible(row, column)) { 534 drawItemLabel(g2, dataset, row, column, plot, generator, bar, 535 false); 536 } 537 538 // submit the current data point as a crosshair candidate 539 int datasetIndex = plot.indexOf(dataset); 540 Comparable columnKey = dataset.getColumnKey(column); 541 Comparable rowKey = dataset.getRowKey(row); 542 double xx = domainAxis.getCategorySeriesMiddle(columnKey, rowKey, 543 dataset, getItemMargin(), dataArea, plot.getDomainAxisEdge()); 544 updateCrosshairValues(state.getCrosshairState(), 545 dataset.getRowKey(row), dataset.getColumnKey(column), 546 value1.doubleValue(), datasetIndex, xx, java2dValue1, 547 orientation); 548 549 // collect entity and tool tip information... 550 EntityCollection entities = state.getEntityCollection(); 551 if (entities != null) { 552 addItemEntity(entities, dataset, row, column, bar); 553 } 554 } 555 556 /** 557 * Returns the Java2D coordinate for the middle of the specified data item. 558 * 559 * @param rowKey the row key. 560 * @param columnKey the column key. 561 * @param dataset the dataset. 562 * @param axis the axis. 563 * @param area the drawing area. 564 * @param edge the edge along which the axis lies. 565 * 566 * @return The Java2D coordinate. 567 * 568 * @since 1.0.11 569 */ 570 public double getItemMiddle(Comparable rowKey, Comparable columnKey, 571 CategoryDataset dataset, CategoryAxis axis, Rectangle2D area, 572 RectangleEdge edge) { 573 return axis.getCategorySeriesMiddle(columnKey, rowKey, dataset, 574 getItemMargin(), area, edge); 575 } 576 577 /** 578 * Tests this renderer for equality with an arbitrary object. 579 * 580 * @param obj the object (<code>null</code> permitted). 581 * 582 * @return A boolean. 583 */ 584 public boolean equals(Object obj) { 585 if (obj == this) { 586 return true; 587 } 588 if (!(obj instanceof GanttRenderer)) { 589 return false; 590 } 591 GanttRenderer that = (GanttRenderer) obj; 592 if (!PaintUtilities.equal(this.completePaint, that.completePaint)) { 593 return false; 594 } 595 if (!PaintUtilities.equal(this.incompletePaint, that.incompletePaint)) { 596 return false; 597 } 598 if (this.startPercent != that.startPercent) { 599 return false; 600 } 601 if (this.endPercent != that.endPercent) { 602 return false; 603 } 604 return super.equals(obj); 605 } 606 607 /** 608 * Provides serialization support. 609 * 610 * @param stream the output stream. 611 * 612 * @throws IOException if there is an I/O error. 613 */ 614 private void writeObject(ObjectOutputStream stream) throws IOException { 615 stream.defaultWriteObject(); 616 SerialUtilities.writePaint(this.completePaint, stream); 617 SerialUtilities.writePaint(this.incompletePaint, stream); 618 } 619 620 /** 621 * Provides serialization support. 622 * 623 * @param stream the input stream. 624 * 625 * @throws IOException if there is an I/O error. 626 * @throws ClassNotFoundException if there is a classpath problem. 627 */ 628 private void readObject(ObjectInputStream stream) 629 throws IOException, ClassNotFoundException { 630 stream.defaultReadObject(); 631 this.completePaint = SerialUtilities.readPaint(stream); 632 this.incompletePaint = SerialUtilities.readPaint(stream); 633 } 634 635 }