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