    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
    055package org.jfree.chart.renderer.category;
    056
    057import java.awt.Color;
    058import java.awt.Graphics2D;
    059import java.awt.Paint;
    060import java.awt.Stroke;
    061import java.awt.geom.Rectangle2D;
    062import java.io.IOException;
    063import java.io.ObjectInputStream;
    064import java.io.ObjectOutputStream;
    065import java.io.Serializable;
    066
    067import org.jfree.chart.axis.CategoryAxis;
    068import org.jfree.chart.axis.ValueAxis;
    069import org.jfree.chart.entity.EntityCollection;
    070import org.jfree.chart.event.RendererChangeEvent;
    071import org.jfree.chart.labels.CategoryItemLabelGenerator;
    072import org.jfree.chart.plot.CategoryPlot;
    073import org.jfree.chart.plot.PlotOrientation;
    074import org.jfree.data.category.CategoryDataset;
    075import org.jfree.data.gantt.GanttCategoryDataset;
    076import org.jfree.io.SerialUtilities;
    077import org.jfree.ui.RectangleEdge;
    078import 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 */
    088public 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}