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