    001    /* ===========================================================
    002     * JFreeChart : a free chart library for the Java(tm) platform
    003     * ===========================================================
    004     *
    005     * (C) Copyright 2000-2006, 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-2006, by Object Refinery Limited.
    031     *
    032     * Original Author:  David Gilbert (for Object Refinery Limited);
    033     * Contributor(s):   -;
    034     *
    035     * $Id: GanttRenderer.java,v 1.6.2.4 2006/01/18 14:22:51 mungady Exp $
    036     *
    037     * Changes
    038     * -------
    039     * 16-Sep-2003 : Version 1 (DG);
    040     * 23-Sep-2003 : Fixed Checkstyle issues (DG);
    041     * 21-Oct-2003 : Bar width moved into CategoryItemRendererState (DG);
    042     * 03-Feb-2004 : Added get/set methods for attributes (DG);
    043     * 12-Aug-2004 : Fixed rendering problem with maxBarWidth attribute (DG);
    044     * 05-Nov-2004 : Modified drawItem() signature (DG);
    045     * 20-Apr-2005 : Renamed CategoryLabelGenerator
    046     *               --> CategoryItemLabelGenerator (DG);
    047     * 01-Dec-2005 : Fix for bug 1369954, drawBarOutline flag ignored (DG);
    048     * 17-Jan-2006 : Set includeBaseInRange flag to false (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.Serializable;
    060
    061    import org.jfree.chart.axis.CategoryAxis;
    062    import org.jfree.chart.axis.ValueAxis;
    063    import org.jfree.chart.entity.CategoryItemEntity;
    064    import org.jfree.chart.entity.EntityCollection;
    065    import org.jfree.chart.event.RendererChangeEvent;
    066    import org.jfree.chart.labels.CategoryItemLabelGenerator;
    067    import org.jfree.chart.labels.CategoryToolTipGenerator;
    068    import org.jfree.chart.plot.CategoryPlot;
    069    import org.jfree.chart.plot.PlotOrientation;
    070    import org.jfree.data.category.CategoryDataset;
    071    import org.jfree.data.gantt.GanttCategoryDataset;
    072    import org.jfree.ui.RectangleEdge;
    073
    074    /**
    075     * A renderer for simple Gantt charts.
    076     */
    077    public class GanttRenderer extends IntervalBarRenderer
    078                               implements Serializable {
    079
    080        /** For serialization. */
    081        private static final long serialVersionUID = -4010349116350119512L;
    082
    083        /** The paint for displaying the percentage complete. */
    084        private Paint completePaint;
    085
    086        /** The paint for displaying the incomplete part of a task. */
    087        private Paint incompletePaint;
    088
    089        /**
    090         * Controls the starting edge of the progress indicator (expressed as a
    091         * percentage of the overall bar width).
    092         */
    093        private double startPercent;
    094
    095        /**
    096         * Controls the ending edge of the progress indicator (expressed as a
    097         * percentage of the overall bar width).
    098         */
    099        private double endPercent;
    100
    101        /**
    102         * Creates a new renderer.
    103         */
    104        public GanttRenderer() {
    105            super();
    106            setIncludeBaseInRange(false);
    107            this.completePaint = Color.green;
    108            this.incompletePaint = Color.red;
    109            this.startPercent = 0.35;
    110            this.endPercent = 0.65;
    111        }
    112
    113        /**
    114         * Returns the paint used to show the percentage complete.
    115         *
    116         * @return The paint (never <code>null</code>.
    117         */
    118        public Paint getCompletePaint() {
    119            return this.completePaint;
    120        }
    121
    122        /**
    123         * Sets the paint used to show the percentage complete and sends a
    124         * {@link RendererChangeEvent} to all registered listeners.
    125         *
    126         * @param paint  the paint (<code>null</code> not permitted).
    127         */
    128        public void setCompletePaint(Paint paint) {
    129            if (paint == null) {
    130                throw new IllegalArgumentException("Null 'paint' argument.");
    131            }
    132            this.completePaint = paint;
    133            notifyListeners(new RendererChangeEvent(this));
    134        }
    135
    136        /**
    137         * Returns the paint used to show the percentage incomplete.
    138         *
    139         * @return The paint (never <code>null</code>).
    140         */
    141        public Paint getIncompletePaint() {
    142            return this.incompletePaint;
    143        }
    144
    145        /**
    146         * Sets the paint used to show the percentage incomplete and sends a
    147         * {@link RendererChangeEvent} to all registered listeners.
    148         *
    149         * @param paint  the paint (<code>null</code> not permitted).
    150         */
    151        public void setIncompletePaint(Paint paint) {
    152            if (paint == null) {
    153                throw new IllegalArgumentException("Null 'paint' argument.");
    154            }
    155            this.incompletePaint = paint;
    156            notifyListeners(new RendererChangeEvent(this));
    157        }
    158
    159        /**
    160         * Returns the position of the start of the progress indicator, as a
    161         * percentage of the bar width.
    162         *
    163         * @return The start percent.
    164         */
    165        public double getStartPercent() {
    166            return this.startPercent;
    167        }
    168
    169        /**
    170         * Sets the position of the start of the progress indicator, as a
    171         * percentage of the bar width.
    172         *
    173         * @param percent  the percent.
    174         */
    175        public void setStartPercent(double percent) {
    176            this.startPercent = percent;
    177            notifyListeners(new RendererChangeEvent(this));
    178        }
    179
    180        /**
    181         * Returns the position of the end of the progress indicator, as a
    182         * percentage of the bar width.
    183         *
    184         * @return The end percent.
    185         */
    186        public double getEndPercent() {
    187            return this.endPercent;
    188        }
    189
    190        /**
    191         * Sets the position of the end of the progress indicator, as a percentage
    192         * of the bar width.
    193         *
    194         * @param percent  the percent.
    195         */
    196        public void setEndPercent(double percent) {
    197            this.endPercent = percent;
    198            notifyListeners(new RendererChangeEvent(this));
    199        }
    200
    201        /**
    202         * Draws the bar for a single (series, category) data item.
    203         *
    204         * @param g2  the graphics device.
    205         * @param state  the renderer state.
    206         * @param dataArea  the data area.
    207         * @param plot  the plot.
    208         * @param domainAxis  the domain axis.
    209         * @param rangeAxis  the range axis.
    210         * @param dataset  the dataset.
    211         * @param row  the row index (zero-based).
    212         * @param column  the column index (zero-based).
    213         * @param pass  the pass index.
    214         */
    215        public void drawItem(Graphics2D g2,
    216                             CategoryItemRendererState state,
    217                             Rectangle2D dataArea,
    218                             CategoryPlot plot,
    219                             CategoryAxis domainAxis,
    220                             ValueAxis rangeAxis,
    221                             CategoryDataset dataset,
    222                             int row,
    223                             int column,
    224                             int pass) {
    225
    226             if (dataset instanceof GanttCategoryDataset) {
    227                 GanttCategoryDataset gcd = (GanttCategoryDataset) dataset;
    228                 drawTasks(
    229                    g2, state, dataArea, plot, domainAxis, rangeAxis, gcd,
    230                    row, column
    231                 );
    232             }
    233             else {  // let the superclass handle it...
    234                 super.drawItem(
    235                     g2, state, dataArea, plot, domainAxis, rangeAxis,
    236                     dataset, row, column, pass
    237                 );
    238             }
    239
    240         }
    241
    242        /**
    243         * Draws the tasks/subtasks for one item.
    244         *
    245         * @param g2  the graphics device.
    246         * @param state  the renderer state.
    247         * @param dataArea  the data plot area.
    248         * @param plot  the plot.
    249         * @param domainAxis  the domain axis.
    250         * @param rangeAxis  the range axis.
    251         * @param dataset  the data.
    252         * @param row  the row index (zero-based).
    253         * @param column  the column index (zero-based).
    254         */
    255        protected void drawTasks(Graphics2D g2,
    256                                 CategoryItemRendererState state,
    257                                 Rectangle2D dataArea,
    258                                 CategoryPlot plot,
    259                                 CategoryAxis domainAxis,
    260                                 ValueAxis rangeAxis,
    261                                 GanttCategoryDataset dataset,
    262                                 int row,
    263                                 int column) {
    264
    265            int count = dataset.getSubIntervalCount(row, column);
    266            if (count == 0) {
    267                drawTask(
    268                    g2, state, dataArea, plot, domainAxis, rangeAxis,
    269                    dataset, row, column
    270                );
    271            }
    272
    273            for (int subinterval = 0; subinterval < count; subinterval++) {
    274
    275                RectangleEdge rangeAxisLocation = plot.getRangeAxisEdge();
    276
    277                // value 0
    278                Number value0 = dataset.getStartValue(row, column, subinterval);
    279                if (value0 == null) {
    280                    return;
    281                }
    282                double translatedValue0 = rangeAxis.valueToJava2D(
    283                    value0.doubleValue(), dataArea, rangeAxisLocation
    284                );
    285
    286                // value 1
    287                Number value1 = dataset.getEndValue(row, column, subinterval);
    288                if (value1 == null) {
    289                    return;
    290                }
    291                double translatedValue1 = rangeAxis.valueToJava2D(
    292                    value1.doubleValue(), dataArea, rangeAxisLocation
    293                );
    294
    295                if (translatedValue1 < translatedValue0) {
    296                    double temp = translatedValue1;
    297                    translatedValue1 = translatedValue0;
    298                    translatedValue0 = temp;
    299                }
    300
    301                double rectStart = calculateBarW0(
    302                    plot, plot.getOrientation(), dataArea, domainAxis, state,
    303                    row, column
    304                );
    305                double rectLength = Math.abs(translatedValue1 - translatedValue0);
    306                double rectBreadth = state.getBarWidth();
    307
    308                // DRAW THE BARS...
    309                Rectangle2D bar = null;
    310
    311                if (plot.getOrientation() == PlotOrientation.HORIZONTAL) {
    312                    bar = new Rectangle2D.Double(
    313                        translatedValue0, rectStart, rectLength, rectBreadth
    314                    );
    315                }
    316                else if (plot.getOrientation() == PlotOrientation.VERTICAL) {
    317                    bar = new Rectangle2D.Double(
    318                        rectStart, translatedValue0, rectBreadth, rectLength
    319                    );
    320                }
    321
    322                Rectangle2D completeBar = null;
    323                Rectangle2D incompleteBar = null;
    324                Number percent = dataset.getPercentComplete(
    325                    row, column, subinterval
    326                );
    327                double start = getStartPercent();
    328                double end = getEndPercent();
    329                if (percent != null) {
    330                    double p = percent.doubleValue();
    331                    if (plot.getOrientation() == PlotOrientation.HORIZONTAL) {
    332                        completeBar = new Rectangle2D.Double(
    333                            translatedValue0,
    334                            rectStart + start * rectBreadth,
    335                            rectLength * p,
    336                            rectBreadth * (end - start)
    337                        );
    338                        incompleteBar = new Rectangle2D.Double(
    339                            translatedValue0 + rectLength * p,
    340                            rectStart + start * rectBreadth,
    341                            rectLength * (1 - p),
    342                            rectBreadth * (end - start)
    343                        );
    344                    }
    345                    else if (plot.getOrientation() == PlotOrientation.VERTICAL) {
    346                        completeBar = new Rectangle2D.Double(
    347                            rectStart + start * rectBreadth,
    348                            translatedValue0 + rectLength * (1 - p),
    349                            rectBreadth * (end - start),
    350                            rectLength * p
    351                        );
    352                        incompleteBar = new Rectangle2D.Double(
    353                            rectStart + start * rectBreadth,
    354                            translatedValue0,
    355                            rectBreadth * (end - start),
    356                            rectLength * (1 - p)
    357                        );
    358                    }
    359
    360                }
    361
    362                Paint seriesPaint = getItemPaint(row, column);
    363                g2.setPaint(seriesPaint);
    364                g2.fill(bar);
    365                if (completeBar != null) {
    366                    g2.setPaint(getCompletePaint());
    367                    g2.fill(completeBar);
    368                }
    369                if (incompleteBar != null) {
    370                    g2.setPaint(getIncompletePaint());
    371                    g2.fill(incompleteBar);
    372                }
    373                if (isDrawBarOutline()
    374                        && state.getBarWidth() > BAR_OUTLINE_WIDTH_THRESHOLD) {
    375                    g2.setStroke(getItemStroke(row, column));
    376                    g2.setPaint(getItemOutlinePaint(row, column));
    377                    g2.draw(bar);
    378                }
    379
    380                // collect entity and tool tip information...
    381                if (state.getInfo() != null) {
    382                    EntityCollection entities = state.getEntityCollection();
    383                    if (entities != null) {
    384                        String tip = null;
    385                        if (getToolTipGenerator(row, column) != null) {
    386                            tip = getToolTipGenerator(row, column).generateToolTip(
    387                                dataset, row, column
    388                            );
    389                        }
    390                        String url = null;
    391                        if (getItemURLGenerator(row, column) != null) {
    392                            url = getItemURLGenerator(row, column).generateURL(
    393                                dataset, row, column
    394                            );
    395                        }
    396                        CategoryItemEntity entity = new CategoryItemEntity(
    397                            bar, tip, url, dataset, row,
    398                            dataset.getColumnKey(column), column
    399                        );
    400                        entities.add(entity);
    401                    }
    402                }
    403            }
    404        }
    405
    406        /**
    407         * Draws a single task.
    408         *
    409         * @param g2  the graphics device.
    410         * @param state  the renderer state.
    411         * @param dataArea  the data plot area.
    412         * @param plot  the plot.
    413         * @param domainAxis  the domain axis.
    414         * @param rangeAxis  the range axis.
    415         * @param dataset  the data.
    416         * @param row  the row index (zero-based).
    417         * @param column  the column index (zero-based).
    418         */
    419        protected void drawTask(Graphics2D g2,
    420                                CategoryItemRendererState state,
    421                                Rectangle2D dataArea,
    422                                CategoryPlot plot,
    423                                CategoryAxis domainAxis,
    424                                ValueAxis rangeAxis,
    425                                GanttCategoryDataset dataset,
    426                                int row,
    427                                int column) {
    428
    429            PlotOrientation orientation = plot.getOrientation();
    430
    431            RectangleEdge rangeAxisLocation = plot.getRangeAxisEdge();
    432
    433            // Y0
    434            Number value0 = dataset.getEndValue(row, column);
    435            if (value0 == null) {
    436                return;
    437            }
    438            double java2dValue0 = rangeAxis.valueToJava2D(
    439                value0.doubleValue(), dataArea, rangeAxisLocation
    440            );
    441
    442            // Y1
    443            Number value1 = dataset.getStartValue(row, column);
    444            if (value1 == null) {
    445                return;
    446            }
    447            double java2dValue1 = rangeAxis.valueToJava2D(
    448                value1.doubleValue(), dataArea, rangeAxisLocation
    449            );
    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(
    461                plot, orientation, dataArea, domainAxis, state, row, column
    462            );
    463            double rectBreadth = state.getBarWidth();
    464            double rectLength = Math.abs(java2dValue1 - java2dValue0);
    465
    466            Rectangle2D bar = null;
    467            if (orientation == PlotOrientation.HORIZONTAL) {
    468                bar = new Rectangle2D.Double(
    469                    java2dValue0, rectStart, rectLength, rectBreadth
    470                );
    471            }
    472            else if (orientation == PlotOrientation.VERTICAL) {
    473                bar = new Rectangle2D.Double(
    474                    rectStart, java2dValue1, rectBreadth, rectLength
    475                );
    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(
    487                        java2dValue0,
    488                        rectStart + start * rectBreadth,
    489                        rectLength * p,
    490                        rectBreadth * (end - start)
    491                    );
    492                    incompleteBar = new Rectangle2D.Double(
    493                        java2dValue0 + rectLength * p,
    494                        rectStart + start * rectBreadth,
    495                        rectLength * (1 - p),
    496                        rectBreadth * (end - start)
    497                    );
    498                }
    499                else if (plot.getOrientation() == PlotOrientation.VERTICAL) {
    500                    completeBar = new Rectangle2D.Double(
    501                        rectStart + start * rectBreadth,
    502                        java2dValue1 + rectLength * (1 - p),
    503                        rectBreadth * (end - start),
    504                        rectLength * p
    505                    );
    506                    incompleteBar = new Rectangle2D.Double(
    507                        rectStart + start * rectBreadth,
    508                        java2dValue1,
    509                        rectBreadth * (end - start),
    510                        rectLength * (1 - p)
    511                    );
    512                }
    513
    514            }
    515
    516            Paint seriesPaint = getItemPaint(row, column);
    517            g2.setPaint(seriesPaint);
    518            g2.fill(bar);
    519
    520            if (completeBar != null) {
    521                g2.setPaint(getCompletePaint());
    522                g2.fill(completeBar);
    523            }
    524            if (incompleteBar != null) {
    525                g2.setPaint(getIncompletePaint());
    526                g2.fill(incompleteBar);
    527            }
    528
    529            // draw the outline...
    530            if (isDrawBarOutline()
    531                    && state.getBarWidth() > BAR_OUTLINE_WIDTH_THRESHOLD) {
    532                Stroke stroke = getItemOutlineStroke(row, column);
    533                Paint paint = getItemOutlinePaint(row, column);
    534                if (stroke != null && paint != null) {
    535                    g2.setStroke(stroke);
    536                    g2.setPaint(paint);
    537                    g2.draw(bar);
    538                }
    539            }
    540
    541            CategoryItemLabelGenerator generator
    542                = getItemLabelGenerator(row, column);
    543            if (generator != null && isItemLabelVisible(row, column)) {
    544                drawItemLabel(
    545                    g2, dataset, row, column, plot, generator, bar, false
    546                );
    547            }
    548
    549            // collect entity and tool tip information...
    550            if (state.getInfo() != null) {
    551                EntityCollection entities = state.getEntityCollection();
    552                if (entities != null) {
    553                    String tip = null;
    554                    CategoryToolTipGenerator tipster = getToolTipGenerator(
    555                        row, column
    556                    );
    557                    if (tipster != null) {
    558                        tip = tipster.generateToolTip(dataset, row, column);
    559                    }
    560                    String url = null;
    561                    if (getItemURLGenerator(row, column) != null) {
    562                        url = getItemURLGenerator(row, column).generateURL(
    563                            dataset, row, column
    564                        );
    565                    }
    566                    CategoryItemEntity entity = new CategoryItemEntity(
    567                        bar, tip, url, dataset, row,
    568                        dataset.getColumnKey(column), column
    569                    );
    570                    entities.add(entity);
    571                }
    572            }
    573
    574        }
    575
    576    }