Gantt chart - multiple bars per task

A discussion forum for JFreeChart (a 2D chart library for the Java platform).
Locked
nuclex
Posts: 2
Joined: Thu Oct 25, 2007 4:19 pm

Gantt chart - multiple bars per task

Post by nuclex » Thu Oct 25, 2007 4:22 pm

How could I draw multiple bars per task in Gantt chart?
Is it necessary to create new renderer or is there an easier approach?

I'm trying to accomplish something like this:

Code: Select all

i22.tinypic.com/34ye9h2.jpg
in JFreeChart with use of Gantt chart.

david.gilbert
JFreeChart Project Leader
Posts: 11734
Joined: Fri Mar 14, 2003 10:29 am
antibot: No, of course not.
Contact:

Post by david.gilbert » Fri Oct 26, 2007 5:09 pm

Although this chart is relatively simple, there's no straightforward way to do it in JFreeChart. The two options probably worth looking at are:

(1) A Gantt chart with subtasks;

(2) An XYPlot with a SymbolAxis, and an XYBlockRenderer, and some ugly code to set the dataset up with the correct intervals to simulate bars.

Option (3), to write a custom renderer, is probably also worth looking into.
David Gilbert
JFreeChart Project Leader

:idea: Read my blog
:idea: Support JFree via the Github sponsorship program

Aniket_kedari
Posts: 17
Joined: Tue Jun 24, 2008 3:03 am

Hi Nuclex

Post by Aniket_kedari » Tue Aug 12, 2008 5:41 am

Hi nuclex
I am facing the same problem as you did.
Which solution you implemented?

david.gilbert
JFreeChart Project Leader
Posts: 11734
Joined: Fri Mar 14, 2003 10:29 am
antibot: No, of course not.
Contact:

Post by david.gilbert » Tue Aug 12, 2008 7:59 pm

I did some experimenting with something similar recently. Here is the dataset wrapper class that I used:

Code: Select all

/* ===========================================================
 * JFreeChart : a free chart library for the Java(tm) platform
 * ===========================================================
 *
 * (C) Copyright 2000-2008, by Object Refinery Limited and Contributors.
 *
 * Project Info:  http://www.jfree.org/jfreechart/index.html
 *
 * This library is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
 * (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
 * License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
 * USA.
 *
 * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
 * in the United States and other countries.]
 *
 * ------------------
 * XYTaskDataset.java
 * ------------------
 * (C) Copyright 2008, by Object Refinery Limited.
 *
 * Original Author:  David Gilbert (for Object Refinery Limited);
 * Contributor(s):   -;
 *
 * Changes
 * -------
 * 12-Aug-2008 : Version 1, not yet included in JFreeChart (DG);
 *
 */


import java.util.Date;

import org.jfree.chart.renderer.xy.XYBarRenderer;
import org.jfree.data.gantt.Task;
import org.jfree.data.gantt.TaskSeries;
import org.jfree.data.gantt.TaskSeriesCollection;
import org.jfree.data.general.DatasetChangeEvent;
import org.jfree.data.general.DatasetChangeListener;
import org.jfree.data.time.TimePeriod;
import org.jfree.data.xy.AbstractXYDataset;
import org.jfree.data.xy.IntervalXYDataset;

/**
 * A dataset implementation that wraps a {@link TaskSeriesCollection} and
 * presents it as an {@link IntervalXYDataset}, allowing a set of tasks to
 * be displayed using an {@link XYBarRenderer}.  This is a very specialised
 * dataset implementation---before using it, you should take some time to
 * understand the use-cases that it is designed for.
 * <br><br>
 * In this implementation, the start and end times of the underlying tasks
 * are returned as x-intervals, and the y-value for all items in a given series
 * is just the series index (that is, an integer).  This may seem back-to-front
 * (and the task times could certainly be returned as y-intervals instead), but
 * this allows the dataset to be used in a combined plot along with a regular
 * time series chart (see the xxx demo for an example).
 */
public class XYTaskDataset extends AbstractXYDataset
        implements IntervalXYDataset, DatasetChangeListener {

	/** The underlying tasks. */
	private TaskSeriesCollection underlying;

	/** The y-interval width. */
	private double yIntervalWidth;

	/**
	 * Creates a new dataset based on the supplied collection of tasks.
	 *
	 * @param tasks  the underlying dataset (<code>null</code> not permitted).
	 */
	public XYTaskDataset(TaskSeriesCollection tasks) {
		if (tasks == null) {
			throw new IllegalArgumentException("Null 'tasks' argument.");
		}
		this.underlying = tasks;
		this.yIntervalWidth = 0.8;
		this.underlying.addChangeListener(this);
	}

	/**
	 * Returns the number of series in the dataset.
	 *
	 * @return The series count.
	 */
	public int getSeriesCount() {
		return this.underlying.getSeriesCount();
	}

    /**
     * Returns the name of a series.
     *
     * @param series  the series index (zero-based).
     *
     * @return The name of a series.
     */
	public Comparable getSeriesKey(int series) {
		return this.underlying.getSeriesKey(series);
	}

	/**
	 * Returns the number of items (tasks) in the specified series.
	 *
     * @param series  the series index (zero-based).
     *
     * @return The item count.
     */
	public int getItemCount(int series) {
		return this.underlying.getSeries(series).getItemCount();
	}

	/**
	 * Returns the x-value for the specified series.  This is a millisecond
	 * value in the middle of the specified item (task) within the requested
	 * series.
	 *
	 * @param series  the series index.
	 * @param item  the item index.
	 *
	 * @return The x-value (in milliseconds).
	 */
	public Number getX(int series, int item) {
		TaskSeries s = this.underlying.getSeries(series);
		Task t = s.get(item);
		TimePeriod duration = t.getDuration();
		Date start = duration.getStart();
		Date end = duration.getEnd();
		long mid = (start.getTime() / 2L) + (end.getTime() / 2L);
		return new Long(mid);
	}

	/**
	 * Returns the y-value for the specified series/item.  In this
	 * implementation, we return the series index as the y-value (this means
	 * that every item in the series has a constant integer value).
	 *
	 * @param series  the series index.
	 * @param item  the item index.
	 *
	 * @return The y-value.
	 */
	public Number getY(int series, int item) {
		return new Integer(series);
	}

	/**
	 * Returns the starting date/time for the specified item (task) in the
	 * given series, measured in milliseconds since 1-Jan-1970 (as in
	 * java.util.Date).
	 *
	 * @param series  the series index.
	 * @param item  the item (or task) index.
	 *
	 * @return The start date/time.
	 */
	public Number getStartX(int series, int item) {
		return new Double(getStartXValue(series, item));
	}

	/**
	 * Returns the starting date/time for the specified item (task) in the
	 * given series, measured in milliseconds since 1-Jan-1970 (as in
	 * java.util.Date).
	 *
	 * @param series  the series index.
	 * @param item  the item (or task) index.
	 *
	 * @return The start date/time.
	 */
	public double getStartXValue(int series, int item) {
		TaskSeries s = this.underlying.getSeries(series);
		Task t = s.get(item);
		TimePeriod duration = t.getDuration();
		Date start = duration.getStart();
		return start.getTime();
	}

	/**
	 * Returns the ending date/time for the specified item (task) in the
	 * given series, measured in milliseconds since 1-Jan-1970 (as in
	 * java.util.Date).
	 *
	 * @param series  the series index.
	 * @param item  the item (or task) index.
	 *
	 * @return The end date/time.
	 */
	public Number getEndX(int series, int item) {
		return new Double(getEndXValue(series, item));
	}

	/**
	 * Returns the ending date/time for the specified item (task) in the
	 * given series, measured in milliseconds since 1-Jan-1970 (as in
	 * java.util.Date).
	 *
	 * @param series  the series index.
	 * @param item  the item (or task) index.
	 *
	 * @return The end date/time.
	 */
	public double getEndXValue(int series, int item) {
		TaskSeries s = this.underlying.getSeries(series);
		Task t = s.get(item);
		TimePeriod duration = t.getDuration();
		Date end = duration.getEnd();
		return end.getTime();
    }

	/**
	 * Returns the starting value of the y-interval for an item in the
	 * given series.
	 *
	 * @param series  the series index.
	 * @param item  the item (or task) index.
	 *
	 * @return The y-interval start.
	 */
	public Number getStartY(int series, int item) {
		return new Double(series - this.yIntervalWidth / 2.0);
	}

	/**
	 * Returns the starting value of the y-interval for an item in the
	 * given series.
	 *
	 * @param series  the series index.
	 * @param item  the item (or task) index.
	 *
	 * @return The y-interval start.
	 */
	public double getStartYValue(int series, int item) {
		return series - this.yIntervalWidth / 2.0;
	}

	/**
	 * Returns the ending value of the y-interval for an item in the
	 * given series.
	 *
	 * @param series  the series index.
	 * @param item  the item (or task) index.
	 *
	 * @return The y-interval end.
	 */
	public Number getEndY(int series, int item) {
		return new Double(series + this.yIntervalWidth / 2.0);
	}

	/**
	 * Returns the ending value of the y-interval for an item in the
	 * given series.
	 *
	 * @param series  the series index.
	 * @param item  the item (or task) index.
	 *
	 * @return The y-interval end.
	 */
	public double getEndYValue(int series, int item) {
		return series + this.yIntervalWidth / 2.0;
	}

	/**
	 * Receives a change event from the underlying dataset and responds by
	 * firing a change event for this dataset.
	 *
	 * @param event  the event.
	 */
	public void datasetChanged(DatasetChangeEvent event) {
		fireDatasetChanged();
	}

}
...and a demo that uses it:

Code: Select all

/* ----------------
 * XYGanttTest.java
 * ----------------
 * (C) Copyright 2008, by Object Refinery Limited.
 *
 */

//package demo;

import java.awt.Color;

import javax.swing.JPanel;

import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.DateAxis;
import org.jfree.chart.axis.SymbolAxis;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.XYBarRenderer;
import org.jfree.data.gantt.Task;
import org.jfree.data.gantt.TaskSeries;
import org.jfree.data.gantt.TaskSeriesCollection;
import org.jfree.data.time.Day;
import org.jfree.data.time.Hour;
import org.jfree.data.xy.IntervalXYDataset;
import org.jfree.ui.ApplicationFrame;
import org.jfree.ui.RefineryUtilities;

/**
 * A proof-of-concept for an XYPlot-based chart showing one or more series
 * of tasks.  This is similar to the CategoryPlot-based Gantt chart, but will
 * permit combining the plot with a time series chart.
 */
public class XYGanttTest extends ApplicationFrame {

    /**
     * Constructs the demo application.
     *
     * @param title  the frame title.
     */
    public XYGanttTest(String title) {
        super(title);
        JPanel chartPanel = createDemoPanel();
        chartPanel.setPreferredSize(new java.awt.Dimension(500, 300));
        setContentPane(chartPanel);
    }

    private static JFreeChart createChart(IntervalXYDataset dataset) {
        JFreeChart chart = ChartFactory.createXYBarChart("XYGanttTest",
                "Date/Time", true, "Resource", dataset, PlotOrientation.VERTICAL,
                true, false, false);

        chart.setBackgroundPaint(Color.white);

        XYPlot plot = (XYPlot) chart.getPlot();
        plot.setRangeAxis(new DateAxis("Date/Time"));
        SymbolAxis yAxis = new SymbolAxis("Series", new String[] {"37_D",
        		"37_R", "38_D", "38_R", "40"});
        yAxis.setGridBandsVisible(false);
        plot.setRangeAxis(yAxis);
        XYBarRenderer renderer = (XYBarRenderer) plot.getRenderer();
        renderer.setUseYInterval(true);
        plot.setRenderer(renderer);
        plot.setBackgroundPaint(Color.lightGray);
        plot.setDomainGridlinePaint(Color.white);
        plot.setRangeGridlinePaint(Color.white);

        return chart;
    }

    /**
     * Creates a panel for the demo.
     *
     * @return A panel.
     */
    public static JPanel createDemoPanel() {
        return new ChartPanel(createChart(createDataset()));
    }

    private static IntervalXYDataset createDataset() {
        return new XYTaskDataset(createTasks());
    }

	private static TaskSeriesCollection createTasks() {
		TaskSeriesCollection dataset = new TaskSeriesCollection();
		TaskSeries s1 = new TaskSeries("37_D");
		s1.add(new Task("T1a", new Hour(11, new Day())));
		s1.add(new Task("T1b", new Hour(14, new Day())));
		s1.add(new Task("T1c", new Hour(16, new Day())));
		TaskSeries s2 = new TaskSeries("37_R");
		s2.add(new Task("T2a", new Hour(13, new Day())));
		s2.add(new Task("T2b", new Hour(19, new Day())));
		s2.add(new Task("T2c", new Hour(21, new Day())));
		TaskSeries s3 = new TaskSeries("38_D");
		s3.add(new Task("T3a", new Hour(13, new Day())));
		s3.add(new Task("T3b", new Hour(19, new Day())));
		s3.add(new Task("T3c", new Hour(21, new Day())));
		TaskSeries s4 = new TaskSeries("38_R");
		s4.add(new Task("T4a", new Day()));
		TaskSeries s5 = new TaskSeries("40");
		s5.add(new Task("T5a", new Day()));
		dataset.add(s1);
		dataset.add(s2);
		dataset.add(s3);
		dataset.add(s4);
		dataset.add(s5);
		return dataset;
	}

    /**
     * Starting point for the demonstration application.
     *
     * @param args  ignored.
     */
    public static void main(String[] args) {
    	XYGanttTest demo = new XYGanttTest("JFreeChart : XYGanttTest.java");
        demo.pack();
        RefineryUtilities.centerFrameOnScreen(demo);
        demo.setVisible(true);
    }

}
If you have any feedback on the dataset wrapper, I'd like to polish it up a bit before incorporating it into a future JFreeChart release.
David Gilbert
JFreeChart Project Leader

:idea: Read my blog
:idea: Support JFree via the Github sponsorship program

Locked