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}