Диаграммы Гантта в JFreeChart

Диаграмма Гантта (Gantt chart) является популярным типом столбчатых диаграмм (гистограмм), который используется при планировании и отслеживании графика выполняемых работ какого-либо проекта. Первый формат диаграммы был разработан в 1910 году Генри Л. Ганттом.

По сути, диаграмма Гантта представляет набор полос, ориентированных вдоль временной оси абсцисс. Каждая полоса на диаграмме обозначает отдельную задачу, начало и окончание которой определяются концами полосы, а её протяженность определяет длительность работы. Перечень задач проекта располагаются на вертикальной оси диаграммы Гантта (ось ординат).

На диаграмме Гантта дополнительно могут быть отмечены проценты завершения, указатели последовательности и зависимости работ, метки ключевых моментов (вехи), метка текущего момента времени, прерывания работ и др.

Ключевым понятием диаграммы Гантта является «веха», определяющая значимый момент в ходе выполнения работ и являющаяся общей границей двух или нескольких задач. Вехи позволяют наглядно отобразить необходимость синхронизации, последовательности в выполнении различных работ. Вехи, также, как и другие границы на диаграмме, не являются календарными датами. Сдвиг «вехи» приводит к сдвигу всех последующих за вехой задач проекта.

Примеры создания диаграмм Гантта

Рассмотрим 2 примера создания диаграмм Гантта с использованием библиотеки JFreeChart и библиотеки Swing. В первом примере GanttChart1 отобразим двойной график - планирование задач и актуальное их выполнение за определенный период. Интерфейс приложения с диаграммой Гантта представлен на следующем скриншоте.

Во втором примере GanttChart2 построения диаграммы Гантта покажем график планируемых задач и процент их выполнения, т.е. состояние задачи. Интерфейс второго примера построения диаграммы Гантта представлен на следующем скриншоте.

Примечание :
1. Поскольку на диаграмме Гантта временные метки по умолчанию отображаются в формате "MMM-yyyy", при котором выводится краткое наименование месяца, то их значения будут зависеть от установленного на компьютере языка локализации. В примерах покажем, как можно изменить локализацию Locale.
2. Разместим ось абсцисс в нижней части диаграммы. По умолчанию она размещается сверху.
3. Во втором примере изменим цвет диаграммы.

Создание списка задач для диаграммы Гантта

Библиотека JFreeChart включает два конструктора задач Task для диаграммы Гантта. При определении Task необходимо в конструктор в качестве параметров передать наименование задачи и период ее выполнения.

public Task(String description, TimePeriod duration);
public Task(String description, Date start, Date end);

где :

  • description - описание задачи;
  • duration - период выполнения;
  • start - дата начала выполнения задачи;
  • end - дата завершения задачи.

Нулевые значения (null) не допускаются. Интерфейс TimePeriod включает всего 2 метода (public Date getStart(), public Date getEnd()). На практике (см. код примеров ниже) используется класс SimpleTimePeriod, реализующий интерфейсы TimePeriod, Comparable, Serializable.

Задачи и сроки выполнения

Основные наборы данных задач и методы создания коллекций разместим в отдельном классе DatasetGantt.java. Используем в примерах текстовые массивы списка задач tasks и периоды их выполнения dates_schedule (планирование), dates_actual (выполнение). Целесообразно, конечно, все эти данные хранить в базе данных. Но при демонстрации библиотеки JFreeChart по созданию диаграммы Гантта на это отвлекаться не будем.

private  String[]  tasks = {"Подготовка предложений", 
                            "Получение замечаний", 
                            "Анализ требований", 
                            "Проектирование", 
                            "Веха 1-го этапа", 
                            "Проектная реализация", 
                            "Замечания по интерфейсу", 
                            "Доработка интерфейса", 
                            "Доработанная реализация", 
                            "Тестирование", 
                            "Финальное внедрение", 
                            "Завершение"};
private String[][] dates_schedule = {{"2016-04-01", "2016-04-05"}, 
                                     {"2016-04-09", "2016-04-11"},
                                     {"2016-04-12", "2016-05-05"},
                                     {"2016-05-06", "2016-05-30"},
                                     {"2016-06-02", "2016-06-03"},
                                     {"2016-06-03", "2016-07-31"},
                                     {"2016-08-01", "2016-08-08"},
                                     {"2016-08-10", "2016-08-11"},
                                     {"2016-08-12", "2016-08-14"},
                                     {"2016-08-16", "2016-10-31"},
                                     {"2016-11-01", "2016-11-15"},
                                     {"2016-11-20", "2016-12-23"}};
private String[][] dates_actual   = {{"2016-04-01", "2016-04-05"}, 
                                     {"2016-04-09", "2016-04-10"},
                                     {"2016-04-11", "2016-05-06"},
                                     {"2016-05-08", "2016-05-28"},
                                     {"2016-06-01", "2016-06-02"},
                                     {"2016-06-04", "2016-07-27"},
                                     {"2016-07-29", "2016-08-05"},
                                     {"2016-08-07", "2016-08-09"},
                                     {"2016-08-10", "2016-08-15"},
                                     {"2016-08-16", "2016-11-05"},
                                     {"2016-11-07", "2016-11-25"},
                                     {"2016-11-28", "2016-12-19"}};

Создание набора задач

Метод createDataset1 создаёт набор данных для первого примера. В коллекцию collection включаются набор планируемых задач s1 и реально выполняемых s2. При создании задачи текстовые значения даты конвертируются в объект Date с использованием класса SimpleDateFormat в методе date.

private SimpleDateFormat sdf;

private Date date(final String sdate)
{
    if (sdf == null)
        sdf = new SimpleDateFormat("yyyyy-MM-dd");

    Date date = null;
    try {
        date = sdf.parse(sdate);
    } catch (ParseException e) {
        e.printStackTrace();
    }
    return date;
}

public IntervalCategoryDataset createDataset1()
{
    final TaskSeries s1 = new TaskSeries("Планирование");
    for (int i = 0; i < tasks.length; i++) {
        s1.add(new Task(tasks[i], 
               new SimpleTimePeriod(date(dates_schedule[i][0]),
                                         date(dates_schedule[i][1]))));
    };

    final TaskSeries s2 = new TaskSeries("Выполнение");
    for (int i = 0; i < tasks.length; i++) {
        s2.add(new Task(tasks[i],
               new SimpleTimePeriod(date(dates_actual[i][0]), 
                                    date(dates_actual[i][1]))));
        };

    final TaskSeriesCollection collection = new TaskSeriesCollection();
    collection.add(s1);
    collection.add(s2);

    return collection;
}

Для второго примера в DatasetGantt дополнительно включается массив состояний задач states (процент выполнения задач) и массивы подзадач subtasks с датами их выполнения subdates. Метод createDataset2 создаёт набор данных для второго примера. Для определения процента выполнения задачи используется метод setPercentComplete().

Обратите внимание, что две задачи состоят из нескольких подзадач.

private  double[][] states = 
                      {{0.8}, 
                       {1.0}, 
                       {1.0, 1.0},
                       {1.0, 1.0, 0.5},
                       {0.7}, {0.2}, {0.0}, {0.0},
                       {0.0}, {0.0}, {0.0}, {0.0}};
private  String[][] subtasks = 
                      {{null},
                       {null},
                       {"Задача 3.1", "Задача 3.2"},
                       {"Задача 4.1", "Задача 4.2", "Задача 4.3"},
                       {null}, {null}, {null}, {null}, 
                       {null}, {null}, {null}, {null}};
private  String[][] subdates = 
                      {{null},
                       {null},
                       {"2016-04-12", "2016-04-27", 
                       "2016-04-30", "2016-05-05"},
                       {"2016-05-06", "2016-05-13", 
                       "2016-05-15", "2016-05-21",
                       "2016-05-23", "2016-05-30"},
                       {null}, {null}, {null}, {null}, 
                       {null}, {null}, {null}, {null}};


public IntervalCategoryDataset createDataset2()
{
    final TaskSeries series = new TaskSeries("Состояние задач");
    for (int i = 0; i < tasks.length; i++) {
        Task task = new Task(tasks[i], 
                    new SimpleTimePeriod(date(dates_schedule[i][0]),
                                         date(dates_schedule[i][1])));
        if (subtasks[i][0] == null) {
            task.setPercentComplete(states[i][0]);
        } else {
            for (int j = 0; j < subtasks[i].length; j++) {
                Task subtask = new Task(subtasks[i][j], 
                    new SimpleTimePeriod(date(subdates[i][j*2]), 
                                         date(subdates[i][j*2 + 1])));
                subtask.setPercentComplete(states[i][j]);
                task.addSubtask(subtask);
            }
        }
        series.add(task);
    };

    final TaskSeriesCollection collection = new TaskSeriesCollection();
    collection.add(series);

    return collection;
}

Создание диаграмм Гантта методом createGanttChart

Для создания диаграммы Гантта необходимо использовать метод createGanttChart класса JFreeChart.Factory. Библиотека JFreeChart предлагает два статических «перегруженных» метода :

JFreeChart createGanttChart(String title,
                            String categoryAxisLabel,
                            String dateAxisLabel,
                            IntervalCategoryDataset dataset);
JFreeChart createGanttChart(String title,
                            String categoryAxisLabel,
                            String dateAxisLabel,
                            IntervalCategoryDataset dataset,
                            boolean legend,
                            boolean tooltips,
                            boolean urls);

Для создания диаграммы Гантта используем метод createChart, которому в качестве параметров передаются набор задач и заголовок. Метод createChart возвращает диаграмму Гантта в виде объекта JFreeChart.

private JFreeChart createChart(final IntervalCategoryDataset dataset,
                               final String title)
{
    final JFreeChart chart = ChartFactory.createGanttChart(
        title,   // chart title
        null,    // domain axis label
        null,    // range axis label
        dataset, // data
        true,    // include legend
        true,    // tooltips
        false    // urls
    );    
    return chart;    
}

В заключении осталось только разместить диаграмму Гантта в интерфейсе. Ниже представлен листинг конструктора второго примера GanttChart2 и метод main. Код конструктора первого примера не включает только строчки кода определения цвета диаграммы.

public GanttChart2(final String title)
{
    super(title);
    DatasetGantt ds = new DatasetGantt();
    JFreeChart chart = createChart(ds.createDataset1(), 
                                   "Проект приложения");
    // Формат представления даты
    DateAxis axis = (DateAxis) chart.getCategoryPlot()
                                    .getRangeAxis();
    DateFormat sdf = new SimpleDateFormat("MM.yyyy");
    axis.setDateFormatOverride(sdf);
	
    // Локализация меток
//  Locale locale = new Locale("ru");
//  axis.setLocale(locale);

    // Размещение оси абсцисс внизу
    chart.getCategoryPlot().setRangeAxisLocation(
	                                 AxisLocation.BOTTOM_OR_LEFT);
    // Цвет диаграммы
    final CategoryPlot plot = (CategoryPlot) chart.getPlot();
    final CategoryItemRenderer renderer = plot.getRenderer();
    renderer.setSeriesPaint(0, Color.blue);

    // Размещение диаграммы на панели
    final ChartPanel chartPanel = new ChartPanel(chart);
    chartPanel.setPreferredSize(new java.awt.Dimension(600, 450));
        
    setContentPane(chartPanel);
}

public static void main(final String[] args)
{
    final GanttChart2 demo = new GanttChart2("Задачи по проекту");
    demo.pack();
    RefineryUtilities.centerFrameOnScreen(demo);
    demo.setVisible(true);
}

После создания диаграммы chart определяется формат представления меток на временной оси axis. Поскольку перешли от текстового представления месяцев к цифровым, то код локализации закомментирован. Желающие могут посмотреть, как будут выглядеть метки для различных языков локализации.

По умолчанию метки временной оси абсцисс размещаются сверху. Это связано с тем, что в основе диаграммы Гантта лежит диаграмма BarChart с горизонтальной ориентацией PlotOrientation.HORIZONTAL. В этом случае диаграмма поворачивается на 90° по часовой стрелке, и ось RangeAxis перемещается в верхнюю часть диаграммы. Чтобы перенести её в нижнюю область диаграммы необходимо использовать метод setRangeAxisLocation объекта Plot.

Цветом диаграммы можно управлять. Для первого примера цвета диаграмм остались без изменения, т.е. используемые по умолчанию. Для второго примера цвет задач определен синим.

Скачать примеры

Исходные коды рассмотренных примеров создания диаграмм Гантта с использованием графической библиотеки JFreeChart в виде maven-проекта можно скачать здесь (18 Кб).

  Рейтинг@Mail.ru