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