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