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.
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            PlotOrientation orientation = plot.getOrientation();
291            for (int subinterval = 0; subinterval < count; subinterval++) {
292    
293                RectangleEdge rangeAxisLocation = plot.getRangeAxisEdge();
294    
295                // value 0
296                Number value0 = dataset.getStartValue(row, column, subinterval);
297                if (value0 == null) {
298                    return;
299                }
300                double translatedValue0 = rangeAxis.valueToJava2D(
301                        value0.doubleValue(), dataArea, rangeAxisLocation);
302    
303                // value 1
304                Number value1 = dataset.getEndValue(row, column, subinterval);
305                if (value1 == null) {
306                    return;
307                }
308                double translatedValue1 = rangeAxis.valueToJava2D(
309                        value1.doubleValue(), dataArea, rangeAxisLocation);
310    
311                if (translatedValue1 < translatedValue0) {
312                    double temp = translatedValue1;
313                    translatedValue1 = translatedValue0;
314                    translatedValue0 = temp;
315                }
316    
317                double rectStart = calculateBarW0(plot, plot.getOrientation(),
318                        dataArea, domainAxis, state, row, column);
319                double rectLength = Math.abs(translatedValue1 - translatedValue0);
320                double rectBreadth = state.getBarWidth();
321    
322                // DRAW THE BARS...
323                Rectangle2D bar = null;
324                RectangleEdge barBase = null;
325                if (plot.getOrientation() == PlotOrientation.HORIZONTAL) {
326                    bar = new Rectangle2D.Double(translatedValue0, rectStart,
327                            rectLength, rectBreadth);
328                    barBase = RectangleEdge.LEFT;
329                }
330                else if (plot.getOrientation() == PlotOrientation.VERTICAL) {
331                    bar = new Rectangle2D.Double(rectStart, translatedValue0,
332                            rectBreadth, rectLength);
333                    barBase = RectangleEdge.BOTTOM;
334                }
335    
336                Rectangle2D completeBar = null;
337                Rectangle2D incompleteBar = null;
338                Number percent = dataset.getPercentComplete(row, column,
339                        subinterval);
340                double start = getStartPercent();
341                double end = getEndPercent();
342                if (percent != null) {
343                    double p = percent.doubleValue();
344                    if (orientation == PlotOrientation.HORIZONTAL) {
345                        completeBar = new Rectangle2D.Double(translatedValue0,
346                                rectStart + start * rectBreadth, rectLength * p,
347                                rectBreadth * (end - start));
348                        incompleteBar = new Rectangle2D.Double(translatedValue0
349                                + rectLength * p, rectStart + start * rectBreadth,
350                                rectLength * (1 - p), rectBreadth * (end - start));
351                    }
352                    else if (orientation == PlotOrientation.VERTICAL) {
353                        completeBar = new Rectangle2D.Double(rectStart + start
354                                * rectBreadth, translatedValue0 + rectLength
355                                * (1 - p), rectBreadth * (end - start),
356                                rectLength * p);
357                        incompleteBar = new Rectangle2D.Double(rectStart + start
358                                * rectBreadth, translatedValue0, rectBreadth
359                                * (end - start), rectLength * (1 - p));
360                    }
361    
362                }
363    
364                if (getShadowsVisible()) {
365                    getBarPainter().paintBarShadow(g2, this, row, column, bar,
366                            barBase, true);
367                }
368                getBarPainter().paintBar(g2, this, row, column, bar, barBase);
369    
370                if (completeBar != null) {
371                    g2.setPaint(getCompletePaint());
372                    g2.fill(completeBar);
373                }
374                if (incompleteBar != null) {
375                    g2.setPaint(getIncompletePaint());
376                    g2.fill(incompleteBar);
377                }
378                if (isDrawBarOutline()
379                        && state.getBarWidth() > BAR_OUTLINE_WIDTH_THRESHOLD) {
380                    g2.setStroke(getItemStroke(row, column));
381                    g2.setPaint(getItemOutlinePaint(row, column));
382                    g2.draw(bar);
383                }
384    
385                if (subinterval == count - 1) {
386                    // submit the current data point as a crosshair candidate
387                    int datasetIndex = plot.indexOf(dataset);
388                    Comparable columnKey = dataset.getColumnKey(column);
389                    Comparable rowKey = dataset.getRowKey(row);
390                    double xx = domainAxis.getCategorySeriesMiddle(columnKey,
391                            rowKey, dataset, getItemMargin(), dataArea,
392                            plot.getDomainAxisEdge());
393                    updateCrosshairValues(state.getCrosshairState(),
394                            dataset.getRowKey(row), dataset.getColumnKey(column),
395                            value1.doubleValue(), datasetIndex, xx,
396                            translatedValue1, orientation);
397    
398                }
399                // collect entity and tool tip information...
400                if (state.getInfo() != null) {
401                    EntityCollection entities = state.getEntityCollection();
402                    if (entities != null) {
403                        addItemEntity(entities, dataset, row, column, bar);
404                    }
405                }
406            }
407        }
408    
409        /**
410         * Draws a single task.
411         *
412         * @param g2  the graphics device.
413         * @param state  the renderer state.
414         * @param dataArea  the data plot area.
415         * @param plot  the plot.
416         * @param domainAxis  the domain axis.
417         * @param rangeAxis  the range axis.
418         * @param dataset  the data.
419         * @param row  the row index (zero-based).
420         * @param column  the column index (zero-based).
421         */
422        protected void drawTask(Graphics2D g2,
423                                CategoryItemRendererState state,
424                                Rectangle2D dataArea,
425                                CategoryPlot plot,
426                                CategoryAxis domainAxis,
427                                ValueAxis rangeAxis,
428                                GanttCategoryDataset dataset,
429                                int row,
430                                int column) {
431    
432            PlotOrientation orientation = plot.getOrientation();
433            RectangleEdge rangeAxisLocation = plot.getRangeAxisEdge();
434    
435            // Y0
436            Number value0 = dataset.getEndValue(row, column);
437            if (value0 == null) {
438                return;
439            }
440            double java2dValue0 = rangeAxis.valueToJava2D(value0.doubleValue(),
441                    dataArea, rangeAxisLocation);
442    
443            // Y1
444            Number value1 = dataset.getStartValue(row, column);
445            if (value1 == null) {
446                return;
447            }
448            double java2dValue1 = rangeAxis.valueToJava2D(value1.doubleValue(),
449                    dataArea, rangeAxisLocation);
450    
451            if (java2dValue1 < java2dValue0) {
452                double temp = java2dValue1;
453                java2dValue1 = java2dValue0;
454                java2dValue0 = temp;
455                Number tempNum = value1;
456                value1 = value0;
457                value0 = tempNum;
458            }
459    
460            double rectStart = calculateBarW0(plot, orientation, dataArea,
461                    domainAxis, state, row, column);
462            double rectBreadth = state.getBarWidth();
463            double rectLength = Math.abs(java2dValue1 - java2dValue0);
464    
465            Rectangle2D bar = null;
466            RectangleEdge barBase = null;
467            if (orientation == PlotOrientation.HORIZONTAL) {
468                bar = new Rectangle2D.Double(java2dValue0, rectStart, rectLength,
469                        rectBreadth);
470                barBase = RectangleEdge.LEFT;
471            }
472            else if (orientation == PlotOrientation.VERTICAL) {
473                bar = new Rectangle2D.Double(rectStart, java2dValue1, rectBreadth,
474                        rectLength);
475                barBase = RectangleEdge.BOTTOM;
476            }
477    
478            Rectangle2D completeBar = null;
479            Rectangle2D incompleteBar = null;
480            Number percent = dataset.getPercentComplete(row, column);
481            double start = getStartPercent();
482            double end = getEndPercent();
483            if (percent != null) {
484                double p = percent.doubleValue();
485                if (plot.getOrientation() == PlotOrientation.HORIZONTAL) {
486                    completeBar = new Rectangle2D.Double(java2dValue0,
487                            rectStart + start * rectBreadth, rectLength * p,
488                            rectBreadth * (end - start));
489                    incompleteBar = new Rectangle2D.Double(java2dValue0
490                            + rectLength * p, rectStart + start * rectBreadth,
491                            rectLength * (1 - p), rectBreadth * (end - start));
492                }
493                else if (plot.getOrientation() == PlotOrientation.VERTICAL) {
494                    completeBar = new Rectangle2D.Double(rectStart + start
495                            * rectBreadth, java2dValue1 + rectLength * (1 - p),
496                            rectBreadth * (end - start), rectLength * p);
497                    incompleteBar = new Rectangle2D.Double(rectStart + start
498                            * rectBreadth, java2dValue1, rectBreadth * (end
499                            - start), rectLength * (1 - p));
500                }
501    
502            }
503    
504            if (getShadowsVisible()) {
505                getBarPainter().paintBarShadow(g2, this, row, column, bar,
506                        barBase, true);
507            }
508            getBarPainter().paintBar(g2, this, row, column, bar, barBase);
509    
510            if (completeBar != null) {
511                g2.setPaint(getCompletePaint());
512                g2.fill(completeBar);
513            }
514            if (incompleteBar != null) {
515                g2.setPaint(getIncompletePaint());
516                g2.fill(incompleteBar);
517            }
518    
519            // draw the outline...
520            if (isDrawBarOutline()
521                    && state.getBarWidth() > BAR_OUTLINE_WIDTH_THRESHOLD) {
522                Stroke stroke = getItemOutlineStroke(row, column);
523                Paint paint = getItemOutlinePaint(row, column);
524                if (stroke != null && paint != null) {
525                    g2.setStroke(stroke);
526                    g2.setPaint(paint);
527                    g2.draw(bar);
528                }
529            }
530    
531            CategoryItemLabelGenerator generator = getItemLabelGenerator(row,
532                    column);
533            if (generator != null && isItemLabelVisible(row, column)) {
534                drawItemLabel(g2, dataset, row, column, plot, generator, bar,
535                        false);
536            }
537    
538            // submit the current data point as a crosshair candidate
539            int datasetIndex = plot.indexOf(dataset);
540            Comparable columnKey = dataset.getColumnKey(column);
541            Comparable rowKey = dataset.getRowKey(row);
542            double xx = domainAxis.getCategorySeriesMiddle(columnKey, rowKey,
543                    dataset, getItemMargin(), dataArea, plot.getDomainAxisEdge());
544            updateCrosshairValues(state.getCrosshairState(),
545                    dataset.getRowKey(row), dataset.getColumnKey(column),
546                    value1.doubleValue(), datasetIndex, xx, java2dValue1,
547                    orientation);
548    
549            // collect entity and tool tip information...
550            EntityCollection entities = state.getEntityCollection();
551            if (entities != null) {
552                addItemEntity(entities, dataset, row, column, bar);
553            }
554        }
555    
556        /**
557         * Returns the Java2D coordinate for the middle of the specified data item.
558         *
559         * @param rowKey  the row key.
560         * @param columnKey  the column key.
561         * @param dataset  the dataset.
562         * @param axis  the axis.
563         * @param area  the drawing area.
564         * @param edge  the edge along which the axis lies.
565         *
566         * @return The Java2D coordinate.
567         *
568         * @since 1.0.11
569         */
570        public double getItemMiddle(Comparable rowKey, Comparable columnKey,
571                CategoryDataset dataset, CategoryAxis axis, Rectangle2D area,
572                RectangleEdge edge) {
573            return axis.getCategorySeriesMiddle(columnKey, rowKey, dataset,
574                    getItemMargin(), area, edge);
575        }
576    
577        /**
578         * Tests this renderer for equality with an arbitrary object.
579         *
580         * @param obj  the object (<code>null</code> permitted).
581         *
582         * @return A boolean.
583         */
584        public boolean equals(Object obj) {
585            if (obj == this) {
586                return true;
587            }
588            if (!(obj instanceof GanttRenderer)) {
589                return false;
590            }
591            GanttRenderer that = (GanttRenderer) obj;
592            if (!PaintUtilities.equal(this.completePaint, that.completePaint)) {
593                return false;
594            }
595            if (!PaintUtilities.equal(this.incompletePaint, that.incompletePaint)) {
596                return false;
597            }
598            if (this.startPercent != that.startPercent) {
599                return false;
600            }
601            if (this.endPercent != that.endPercent) {
602                return false;
603            }
604            return super.equals(obj);
605        }
606    
607        /**
608         * Provides serialization support.
609         *
610         * @param stream  the output stream.
611         *
612         * @throws IOException  if there is an I/O error.
613         */
614        private void writeObject(ObjectOutputStream stream) throws IOException {
615            stream.defaultWriteObject();
616            SerialUtilities.writePaint(this.completePaint, stream);
617            SerialUtilities.writePaint(this.incompletePaint, stream);
618        }
619    
620        /**
621         * Provides serialization support.
622         *
623         * @param stream  the input stream.
624         *
625         * @throws IOException  if there is an I/O error.
626         * @throws ClassNotFoundException  if there is a classpath problem.
627         */
628        private void readObject(ObjectInputStream stream)
629            throws IOException, ClassNotFoundException {
630            stream.defaultReadObject();
631            this.completePaint = SerialUtilities.readPaint(stream);
632            this.incompletePaint = SerialUtilities.readPaint(stream);
633        }
634    
635    }