001    /* ===========================================================
002     * JFreeChart : a free chart library for the Java(tm) platform
003     * ===========================================================
004     *
005     * (C) Copyright 2000-2011, 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     * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. 
025     * Other names may be trademarks of their respective owners.]
026     *
027     * ------------------
028     * GanttRenderer.java
029     * ------------------
030     * (C) Copyright 2003-2009, 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     * 24-Jun-2008 : Added new barPainter mechanism (DG);
050     * 26-Jun-2008 : Added crosshair support (DG);
051     * 19-May-2009 : Fixed FindBugs warnings, patch by Michal Wozniak (DG);
052     * 
053     */
054    
055    package org.jfree.chart.renderer.category;
056    
057    import java.awt.Color;
058    import java.awt.Graphics2D;
059    import java.awt.Paint;
060    import java.awt.Stroke;
061    import java.awt.geom.Rectangle2D;
062    import java.io.IOException;
063    import java.io.ObjectInputStream;
064    import java.io.ObjectOutputStream;
065    import java.io.Serializable;
066    
067    import org.jfree.chart.axis.CategoryAxis;
068    import org.jfree.chart.axis.ValueAxis;
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.plot.CategoryPlot;
073    import org.jfree.chart.plot.PlotOrientation;
074    import org.jfree.data.category.CategoryDataset;
075    import org.jfree.data.gantt.GanttCategoryDataset;
076    import org.jfree.io.SerialUtilities;
077    import org.jfree.ui.RectangleEdge;
078    import org.jfree.util.PaintUtilities;
079    
080    /**
081     * A renderer for simple Gantt charts.  The example shown
082     * here is generated by the <code>GanttDemo1.java</code> program
083     * included in the JFreeChart Demo Collection:
084     * <br><br>
085     * <img src="../../../../../images/GanttRendererSample.png"
086     * alt="GanttRendererSample.png" />
087     */
088    public class GanttRenderer extends IntervalBarRenderer
089            implements Serializable {
090    
091        /** For serialization. */
092        private static final long serialVersionUID = -4010349116350119512L;
093    
094        /** The paint for displaying the percentage complete. */
095        private transient Paint completePaint;
096    
097        /** The paint for displaying the incomplete part of a task. */
098        private transient Paint incompletePaint;
099    
100        /**
101         * Controls the starting edge of the progress indicator (expressed as a
102         * percentage of the overall bar width).
103         */
104        private double startPercent;
105    
106        /**
107         * Controls the ending edge of the progress indicator (expressed as a
108         * percentage of the overall bar width).
109         */
110        private double endPercent;
111    
112        /**
113         * Creates a new renderer.
114         */
115        public GanttRenderer() {
116            super();
117            setIncludeBaseInRange(false);
118            this.completePaint = Color.green;
119            this.incompletePaint = Color.red;
120            this.startPercent = 0.35;
121            this.endPercent = 0.65;
122        }
123    
124        /**
125         * Returns the paint used to show the percentage complete.
126         *
127         * @return The paint (never <code>null</code>.
128         *
129         * @see #setCompletePaint(Paint)
130         */
131        public Paint getCompletePaint() {
132            return this.completePaint;
133        }
134    
135        /**
136         * Sets the paint used to show the percentage complete and sends a
137         * {@link RendererChangeEvent} to all registered listeners.
138         *
139         * @param paint  the paint (<code>null</code> not permitted).
140         *
141         * @see #getCompletePaint()
142         */
143        public void setCompletePaint(Paint paint) {
144            if (paint == null) {
145                throw new IllegalArgumentException("Null 'paint' argument.");
146            }
147            this.completePaint = paint;
148            fireChangeEvent();
149        }
150    
151        /**
152         * Returns the paint used to show the percentage incomplete.
153         *
154         * @return The paint (never <code>null</code>).
155         *
156         * @see #setCompletePaint(Paint)
157         */
158        public Paint getIncompletePaint() {
159            return this.incompletePaint;
160        }
161    
162        /**
163         * Sets the paint used to show the percentage incomplete and sends a
164         * {@link RendererChangeEvent} to all registered listeners.
165         *
166         * @param paint  the paint (<code>null</code> not permitted).
167         *
168         * @see #getIncompletePaint()
169         */
170        public void setIncompletePaint(Paint paint) {
171            if (paint == null) {
172                throw new IllegalArgumentException("Null 'paint' argument.");
173            }
174            this.incompletePaint = paint;
175            fireChangeEvent();
176        }
177    
178        /**
179         * Returns the position of the start of the progress indicator, as a
180         * percentage of the bar width.
181         *
182         * @return The start percent.
183         *
184         * @see #setStartPercent(double)
185         */
186        public double getStartPercent() {
187            return this.startPercent;
188        }
189    
190        /**
191         * Sets the position of the start of the progress indicator, as a
192         * percentage of the bar width, and sends a {@link RendererChangeEvent} to
193         * all registered listeners.
194         *
195         * @param percent  the percent.
196         *
197         * @see #getStartPercent()
198         */
199        public void setStartPercent(double percent) {
200            this.startPercent = percent;
201            fireChangeEvent();
202        }
203    
204        /**
205         * Returns the position of the end of the progress indicator, as a
206         * percentage of the bar width.
207         *
208         * @return The end percent.
209         *
210         * @see #setEndPercent(double)
211         */
212        public double getEndPercent() {
213            return this.endPercent;
214        }
215    
216        /**
217         * Sets the position of the end of the progress indicator, as a percentage
218         * of the bar width, and sends a {@link RendererChangeEvent} to all
219         * registered listeners.
220         *
221         * @param percent  the percent.
222         *
223         * @see #getEndPercent()
224         */
225        public void setEndPercent(double percent) {
226            this.endPercent = percent;
227            fireChangeEvent();
228        }
229    
230        /**
231         * Draws the bar for a single (series, category) data item.
232         *
233         * @param g2  the graphics device.
234         * @param state  the renderer state.
235         * @param dataArea  the data area.
236         * @param plot  the plot.
237         * @param domainAxis  the domain axis.
238         * @param rangeAxis  the range axis.
239         * @param dataset  the dataset.
240         * @param row  the row index (zero-based).
241         * @param column  the column index (zero-based).
242         * @param pass  the pass index.
243         */
244        public void drawItem(Graphics2D g2,
245                             CategoryItemRendererState state,
246                             Rectangle2D dataArea,
247                             CategoryPlot plot,
248                             CategoryAxis domainAxis,
249                             ValueAxis rangeAxis,
250                             CategoryDataset dataset,
251                             int row,
252                             int column,
253                             int pass) {
254    
255             if (dataset instanceof GanttCategoryDataset) {
256                 GanttCategoryDataset gcd = (GanttCategoryDataset) dataset;
257                 drawTasks(g2, state, dataArea, plot, domainAxis, rangeAxis, gcd,
258                         row, column);
259             }
260             else {  // let the superclass handle it...
261                 super.drawItem(g2, state, dataArea, plot, domainAxis, rangeAxis,
262                         dataset, row, column, pass);
263             }
264    
265         }
266    
267        /**
268         * Draws the tasks/subtasks for one item.
269         *
270         * @param g2  the graphics device.
271         * @param state  the renderer state.
272         * @param dataArea  the data plot area.
273         * @param plot  the plot.
274         * @param domainAxis  the domain axis.
275         * @param rangeAxis  the range axis.
276         * @param dataset  the data.
277         * @param row  the row index (zero-based).
278         * @param column  the column index (zero-based).
279         */
280        protected void drawTasks(Graphics2D g2,
281                                 CategoryItemRendererState state,
282                                 Rectangle2D dataArea,
283                                 CategoryPlot plot,
284                                 CategoryAxis domainAxis,
285                                 ValueAxis rangeAxis,
286                                 GanttCategoryDataset dataset,
287                                 int row,
288                                 int column) {
289    
290            int count = dataset.getSubIntervalCount(row, column);
291            if (count == 0) {
292                drawTask(g2, state, dataArea, plot, domainAxis, rangeAxis,
293                        dataset, row, column);
294            }
295    
296            PlotOrientation orientation = plot.getOrientation();
297            for (int subinterval = 0; subinterval < count; subinterval++) {
298    
299                RectangleEdge rangeAxisLocation = plot.getRangeAxisEdge();
300    
301                // value 0
302                Number value0 = dataset.getStartValue(row, column, subinterval);
303                if (value0 == null) {
304                    return;
305                }
306                double translatedValue0 = rangeAxis.valueToJava2D(
307                        value0.doubleValue(), dataArea, rangeAxisLocation);
308    
309                // value 1
310                Number value1 = dataset.getEndValue(row, column, subinterval);
311                if (value1 == null) {
312                    return;
313                }
314                double translatedValue1 = rangeAxis.valueToJava2D(
315                        value1.doubleValue(), dataArea, rangeAxisLocation);
316    
317                if (translatedValue1 < translatedValue0) {
318                    double temp = translatedValue1;
319                    translatedValue1 = translatedValue0;
320                    translatedValue0 = temp;
321                }
322    
323                double rectStart = calculateBarW0(plot, plot.getOrientation(),
324                        dataArea, domainAxis, state, row, column);
325                double rectLength = Math.abs(translatedValue1 - translatedValue0);
326                double rectBreadth = state.getBarWidth();
327    
328                // DRAW THE BARS...
329                Rectangle2D bar = null;
330                RectangleEdge barBase = null;
331                if (plot.getOrientation() == PlotOrientation.HORIZONTAL) {
332                    bar = new Rectangle2D.Double(translatedValue0, rectStart,
333                            rectLength, rectBreadth);
334                    barBase = RectangleEdge.LEFT;
335                }
336                else if (plot.getOrientation() == PlotOrientation.VERTICAL) {
337                    bar = new Rectangle2D.Double(rectStart, translatedValue0,
338                            rectBreadth, rectLength);
339                    barBase = RectangleEdge.BOTTOM;
340                }
341    
342                Rectangle2D completeBar = null;
343                Rectangle2D incompleteBar = null;
344                Number percent = dataset.getPercentComplete(row, column,
345                        subinterval);
346                double start = getStartPercent();
347                double end = getEndPercent();
348                if (percent != null) {
349                    double p = percent.doubleValue();
350                    if (orientation == PlotOrientation.HORIZONTAL) {
351                        completeBar = new Rectangle2D.Double(translatedValue0,
352                                rectStart + start * rectBreadth, rectLength * p,
353                                rectBreadth * (end - start));
354                        incompleteBar = new Rectangle2D.Double(translatedValue0
355                                + rectLength * p, rectStart + start * rectBreadth,
356                                rectLength * (1 - p), rectBreadth * (end - start));
357                    }
358                    else if (orientation == PlotOrientation.VERTICAL) {
359                        completeBar = new Rectangle2D.Double(rectStart + start
360                                * rectBreadth, translatedValue0 + rectLength
361                                * (1 - p), rectBreadth * (end - start),
362                                rectLength * p);
363                        incompleteBar = new Rectangle2D.Double(rectStart + start
364                                * rectBreadth, translatedValue0, rectBreadth
365                                * (end - start), rectLength * (1 - p));
366                    }
367    
368                }
369    
370                if (getShadowsVisible()) {
371                    getBarPainter().paintBarShadow(g2, this, row, column, bar,
372                            barBase, true);
373                }
374                getBarPainter().paintBar(g2, this, row, column, bar, barBase);
375    
376                if (completeBar != null) {
377                    g2.setPaint(getCompletePaint());
378                    g2.fill(completeBar);
379                }
380                if (incompleteBar != null) {
381                    g2.setPaint(getIncompletePaint());
382                    g2.fill(incompleteBar);
383                }
384                if (isDrawBarOutline()
385                        && state.getBarWidth() > BAR_OUTLINE_WIDTH_THRESHOLD) {
386                    g2.setStroke(getItemStroke(row, column));
387                    g2.setPaint(getItemOutlinePaint(row, column));
388                    g2.draw(bar);
389                }
390    
391                if (subinterval == count - 1) {
392                    // submit the current data point as a crosshair candidate
393                    int datasetIndex = plot.indexOf(dataset);
394                    Comparable columnKey = dataset.getColumnKey(column);
395                    Comparable rowKey = dataset.getRowKey(row);
396                    double xx = domainAxis.getCategorySeriesMiddle(columnKey,
397                            rowKey, dataset, getItemMargin(), dataArea,
398                            plot.getDomainAxisEdge());
399                    updateCrosshairValues(state.getCrosshairState(),
400                            dataset.getRowKey(row), dataset.getColumnKey(column),
401                            value1.doubleValue(), datasetIndex, xx,
402                            translatedValue1, orientation);
403    
404                }
405                // collect entity and tool tip information...
406                if (state.getInfo() != null) {
407                    EntityCollection entities = state.getEntityCollection();
408                    if (entities != null) {
409                        addItemEntity(entities, dataset, row, column, bar);
410                    }
411                }
412            }
413        }
414    
415        /**
416         * Draws a single task.
417         *
418         * @param g2  the graphics device.
419         * @param state  the renderer state.
420         * @param dataArea  the data plot area.
421         * @param plot  the plot.
422         * @param domainAxis  the domain axis.
423         * @param rangeAxis  the range axis.
424         * @param dataset  the data.
425         * @param row  the row index (zero-based).
426         * @param column  the column index (zero-based).
427         */
428        protected void drawTask(Graphics2D g2,
429                                CategoryItemRendererState state,
430                                Rectangle2D dataArea,
431                                CategoryPlot plot,
432                                CategoryAxis domainAxis,
433                                ValueAxis rangeAxis,
434                                GanttCategoryDataset dataset,
435                                int row,
436                                int column) {
437    
438            PlotOrientation orientation = plot.getOrientation();
439            RectangleEdge rangeAxisLocation = plot.getRangeAxisEdge();
440    
441            // Y0
442            Number value0 = dataset.getEndValue(row, column);
443            if (value0 == null) {
444                return;
445            }
446            double java2dValue0 = rangeAxis.valueToJava2D(value0.doubleValue(),
447                    dataArea, rangeAxisLocation);
448    
449            // Y1
450            Number value1 = dataset.getStartValue(row, column);
451            if (value1 == null) {
452                return;
453            }
454            double java2dValue1 = rangeAxis.valueToJava2D(value1.doubleValue(),
455                    dataArea, rangeAxisLocation);
456    
457            if (java2dValue1 < java2dValue0) {
458                double temp = java2dValue1;
459                java2dValue1 = java2dValue0;
460                java2dValue0 = temp;
461                value1 = value0;
462            }
463    
464            double rectStart = calculateBarW0(plot, orientation, dataArea,
465                    domainAxis, state, row, column);
466            double rectBreadth = state.getBarWidth();
467            double rectLength = Math.abs(java2dValue1 - java2dValue0);
468    
469            Rectangle2D bar = null;
470            RectangleEdge barBase = null;
471            if (orientation == PlotOrientation.HORIZONTAL) {
472                bar = new Rectangle2D.Double(java2dValue0, rectStart, rectLength,
473                        rectBreadth);
474                barBase = RectangleEdge.LEFT;
475            }
476            else if (orientation == PlotOrientation.VERTICAL) {
477                bar = new Rectangle2D.Double(rectStart, java2dValue1, rectBreadth,
478                        rectLength);
479                barBase = RectangleEdge.BOTTOM;
480            }
481    
482            Rectangle2D completeBar = null;
483            Rectangle2D incompleteBar = null;
484            Number percent = dataset.getPercentComplete(row, column);
485            double start = getStartPercent();
486            double end = getEndPercent();
487            if (percent != null) {
488                double p = percent.doubleValue();
489                if (plot.getOrientation() == PlotOrientation.HORIZONTAL) {
490                    completeBar = new Rectangle2D.Double(java2dValue0,
491                            rectStart + start * rectBreadth, rectLength * p,
492                            rectBreadth * (end - start));
493                    incompleteBar = new Rectangle2D.Double(java2dValue0
494                            + rectLength * p, rectStart + start * rectBreadth,
495                            rectLength * (1 - p), rectBreadth * (end - start));
496                }
497                else if (plot.getOrientation() == PlotOrientation.VERTICAL) {
498                    completeBar = new Rectangle2D.Double(rectStart + start
499                            * rectBreadth, java2dValue1 + rectLength * (1 - p),
500                            rectBreadth * (end - start), rectLength * p);
501                    incompleteBar = new Rectangle2D.Double(rectStart + start
502                            * rectBreadth, java2dValue1, rectBreadth * (end
503                            - start), rectLength * (1 - p));
504                }
505    
506            }
507    
508            if (getShadowsVisible()) {
509                getBarPainter().paintBarShadow(g2, this, row, column, bar,
510                        barBase, true);
511            }
512            getBarPainter().paintBar(g2, this, row, column, bar, barBase);
513    
514            if (completeBar != null) {
515                g2.setPaint(getCompletePaint());
516                g2.fill(completeBar);
517            }
518            if (incompleteBar != null) {
519                g2.setPaint(getIncompletePaint());
520                g2.fill(incompleteBar);
521            }
522    
523            // draw the outline...
524            if (isDrawBarOutline()
525                    && state.getBarWidth() > BAR_OUTLINE_WIDTH_THRESHOLD) {
526                Stroke stroke = getItemOutlineStroke(row, column);
527                Paint paint = getItemOutlinePaint(row, column);
528                if (stroke != null && paint != null) {
529                    g2.setStroke(stroke);
530                    g2.setPaint(paint);
531                    g2.draw(bar);
532                }
533            }
534    
535            CategoryItemLabelGenerator generator = getItemLabelGenerator(row,
536                    column);
537            if (generator != null && isItemLabelVisible(row, column)) {
538                drawItemLabel(g2, dataset, row, column, plot, generator, bar,
539                        false);
540            }
541    
542            // submit the current data point as a crosshair candidate
543            int datasetIndex = plot.indexOf(dataset);
544            Comparable columnKey = dataset.getColumnKey(column);
545            Comparable rowKey = dataset.getRowKey(row);
546            double xx = domainAxis.getCategorySeriesMiddle(columnKey, rowKey,
547                    dataset, getItemMargin(), dataArea, plot.getDomainAxisEdge());
548            updateCrosshairValues(state.getCrosshairState(),
549                    dataset.getRowKey(row), dataset.getColumnKey(column),
550                    value1.doubleValue(), datasetIndex, xx, java2dValue1,
551                    orientation);
552    
553            // collect entity and tool tip information...
554            EntityCollection entities = state.getEntityCollection();
555            if (entities != null) {
556                addItemEntity(entities, dataset, row, column, bar);
557            }
558        }
559    
560        /**
561         * Returns the Java2D coordinate for the middle of the specified data item.
562         *
563         * @param rowKey  the row key.
564         * @param columnKey  the column key.
565         * @param dataset  the dataset.
566         * @param axis  the axis.
567         * @param area  the drawing area.
568         * @param edge  the edge along which the axis lies.
569         *
570         * @return The Java2D coordinate.
571         *
572         * @since 1.0.11
573         */
574        public double getItemMiddle(Comparable rowKey, Comparable columnKey,
575                CategoryDataset dataset, CategoryAxis axis, Rectangle2D area,
576                RectangleEdge edge) {
577            return axis.getCategorySeriesMiddle(columnKey, rowKey, dataset,
578                    getItemMargin(), area, edge);
579        }
580    
581        /**
582         * Tests this renderer for equality with an arbitrary object.
583         *
584         * @param obj  the object (<code>null</code> permitted).
585         *
586         * @return A boolean.
587         */
588        public boolean equals(Object obj) {
589            if (obj == this) {
590                return true;
591            }
592            if (!(obj instanceof GanttRenderer)) {
593                return false;
594            }
595            GanttRenderer that = (GanttRenderer) obj;
596            if (!PaintUtilities.equal(this.completePaint, that.completePaint)) {
597                return false;
598            }
599            if (!PaintUtilities.equal(this.incompletePaint, that.incompletePaint)) {
600                return false;
601            }
602            if (this.startPercent != that.startPercent) {
603                return false;
604            }
605            if (this.endPercent != that.endPercent) {
606                return false;
607            }
608            return super.equals(obj);
609        }
610    
611        /**
612         * Provides serialization support.
613         *
614         * @param stream  the output stream.
615         *
616         * @throws IOException  if there is an I/O error.
617         */
618        private void writeObject(ObjectOutputStream stream) throws IOException {
619            stream.defaultWriteObject();
620            SerialUtilities.writePaint(this.completePaint, stream);
621            SerialUtilities.writePaint(this.incompletePaint, stream);
622        }
623    
624        /**
625         * Provides serialization support.
626         *
627         * @param stream  the input stream.
628         *
629         * @throws IOException  if there is an I/O error.
630         * @throws ClassNotFoundException  if there is a classpath problem.
631         */
632        private void readObject(ObjectInputStream stream)
633            throws IOException, ClassNotFoundException {
634            stream.defaultReadObject();
635            this.completePaint = SerialUtilities.readPaint(stream);
636            this.incompletePaint = SerialUtilities.readPaint(stream);
637        }
638    
639    }