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    
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.IOException;
060    import java.io.ObjectInputStream;
061    import java.io.ObjectOutputStream;
062    import java.io.Serializable;
063    
064    import org.jfree.chart.axis.CategoryAxis;
065    import org.jfree.chart.axis.ValueAxis;
066    import org.jfree.chart.entity.CategoryItemEntity;
067    import org.jfree.chart.entity.EntityCollection;
068    import org.jfree.chart.event.RendererChangeEvent;
069    import org.jfree.chart.labels.CategoryItemLabelGenerator;
070    import org.jfree.chart.labels.CategoryToolTipGenerator;
071    import org.jfree.chart.plot.CategoryPlot;
072    import org.jfree.chart.plot.PlotOrientation;
073    import org.jfree.data.category.CategoryDataset;
074    import org.jfree.data.gantt.GanttCategoryDataset;
075    import org.jfree.io.SerialUtilities;
076    import org.jfree.ui.RectangleEdge;
077    import org.jfree.util.PaintUtilities;
078    
079    /**
080     * A renderer for simple Gantt charts.
081     */
082    public 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            fireChangeEvent();
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            fireChangeEvent();
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, and sends a {@link RendererChangeEvent} to
187         * all registered listeners.
188         *
189         * @param percent  the percent.
190         *
191         * @see #getStartPercent()
192         */
193        public void setStartPercent(double percent) {
194            this.startPercent = percent;
195            fireChangeEvent();
196        }
197    
198        /**
199         * Returns the position of the end of the progress indicator, as a
200         * percentage of the bar width.
201         *
202         * @return The end percent.
203         *
204         * @see #setEndPercent(double)
205         */
206        public double getEndPercent() {
207            return this.endPercent;
208        }
209    
210        /**
211         * Sets the position of the end of the progress indicator, as a percentage
212         * of the bar width, and sends a {@link RendererChangeEvent} to all
213         * registered listeners.
214         *
215         * @param percent  the percent.
216         *
217         * @see #getEndPercent()
218         */
219        public void setEndPercent(double percent) {
220            this.endPercent = percent;
221            fireChangeEvent();
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, dataset.getRowKey(row),
395                                dataset.getColumnKey(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, dataset.getRowKey(row),
544                            dataset.getColumnKey(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    }