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 }