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