    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     * $Id: GanttRenderer.java,v 1.6.2.5 2007/03/20 21:21:05 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     * ------------- JFREECHART 1.0.x --------------------------------------------
    049     * 17-Jan-2006 : Set includeBaseInRange flag to false (DG);
    050     * 20-Mar-2007 : Implemented equals() and fixed serialization (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.CategoryItemEntity;
    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.labels.CategoryToolTipGenerator;
    073    import org.jfree.chart.plot.CategoryPlot;
    074    import org.jfree.chart.plot.PlotOrientation;
    075    import org.jfree.data.category.CategoryDataset;
    076    import org.jfree.data.gantt.GanttCategoryDataset;
    077    import org.jfree.io.SerialUtilities;
    078    import org.jfree.ui.RectangleEdge;
    079    import org.jfree.util.PaintUtilities;
    080
    081    /**
    082     * A renderer for simple Gantt charts.
    083     */
    084    public class GanttRenderer extends IntervalBarRenderer
    085                               implements Serializable {
    086
    087        /** For serialization. */
    088        private static final long serialVersionUID = -4010349116350119512L;
    089
    090        /** The paint for displaying the percentage complete. */
    091        private transient Paint completePaint;
    092
    093        /** The paint for displaying the incomplete part of a task. */
    094        private transient Paint incompletePaint;
    095
    096        /**
    097         * Controls the starting edge of the progress indicator (expressed as a
    098         * percentage of the overall bar width).
    099         */
    100        private double startPercent;
    101
    102        /**
    103         * Controls the ending edge of the progress indicator (expressed as a
    104         * percentage of the overall bar width).
    105         */
    106        private double endPercent;
    107
    108        /**
    109         * Creates a new renderer.
    110         */
    111        public GanttRenderer() {
    112            super();
    113            setIncludeBaseInRange(false);
    114            this.completePaint = Color.green;
    115            this.incompletePaint = Color.red;
    116            this.startPercent = 0.35;
    117            this.endPercent = 0.65;
    118        }
    119
    120        /**
    121         * Returns the paint used to show the percentage complete.
    122         *
    123         * @return The paint (never <code>null</code>.
    124         *
    125         * @see #setCompletePaint(Paint)
    126         */
    127        public Paint getCompletePaint() {
    128            return this.completePaint;
    129        }
    130
    131        /**
    132         * Sets the paint used to show the percentage complete and sends a
    133         * {@link RendererChangeEvent} to all registered listeners.
    134         *
    135         * @param paint  the paint (<code>null</code> not permitted).
    136         *
    137         * @see #getCompletePaint()
    138         */
    139        public void setCompletePaint(Paint paint) {
    140            if (paint == null) {
    141                throw new IllegalArgumentException("Null 'paint' argument.");
    142            }
    143            this.completePaint = paint;
    144            notifyListeners(new RendererChangeEvent(this));
    145        }
    146
    147        /**
    148         * Returns the paint used to show the percentage incomplete.
    149         *
    150         * @return The paint (never <code>null</code>).
    151         *
    152         * @see #setCompletePaint(Paint)
    153         */
    154        public Paint getIncompletePaint() {
    155            return this.incompletePaint;
    156        }
    157
    158        /**
    159         * Sets the paint used to show the percentage incomplete and sends a
    160         * {@link RendererChangeEvent} to all registered listeners.
    161         *
    162         * @param paint  the paint (<code>null</code> not permitted).
    163         *
    164         * @see #getIncompletePaint()
    165         */
    166        public void setIncompletePaint(Paint paint) {
    167            if (paint == null) {
    168                throw new IllegalArgumentException("Null 'paint' argument.");
    169            }
    170            this.incompletePaint = paint;
    171            notifyListeners(new RendererChangeEvent(this));
    172        }
    173
    174        /**
    175         * Returns the position of the start of the progress indicator, as a
    176         * percentage of the bar width.
    177         *
    178         * @return The start percent.
    179         *
    180         * @see #setStartPercent(double)
    181         */
    182        public double getStartPercent() {
    183            return this.startPercent;
    184        }
    185
    186        /**
    187         * Sets the position of the start of the progress indicator, as a
    188         * percentage of the bar width.
    189         *
    190         * @param percent  the percent.
    191         *
    192         * @see #getStartPercent()
    193         */
    194        public void setStartPercent(double percent) {
    195            this.startPercent = percent;
    196            notifyListeners(new RendererChangeEvent(this));
    197        }
    198
    199        /**
    200         * Returns the position of the end of the progress indicator, as a
    201         * percentage of the bar width.
    202         *
    203         * @return The end percent.
    204         *
    205         * @see #setEndPercent(double)
    206         */
    207        public double getEndPercent() {
    208            return this.endPercent;
    209        }
    210
    211        /**
    212         * Sets the position of the end of the progress indicator, as a percentage
    213         * of the bar width.
    214         *
    215         * @param percent  the percent.
    216         *
    217         * @see #getEndPercent()
    218         */
    219        public void setEndPercent(double percent) {
    220            this.endPercent = percent;
    221            notifyListeners(new RendererChangeEvent(this));
    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, row,
    395                                dataset.getColumnKey(column), 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, row, dataset.getColumnKey(column),
    544                            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    }