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