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