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