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