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