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