com.opendoorlogistics.components.gantt.GanttChartComponent.java Source code

Java tutorial

Introduction

Here is the source code for com.opendoorlogistics.components.gantt.GanttChartComponent.java

Source

/*******************************************************************************
 * Copyright (c) 2014 Open Door Logistics (www.opendoorlogistics.com)
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Lesser Public License v3
 * which accompanies this distribution, and is available at http://www.gnu.org/licenses/lgpl.txt
 ******************************************************************************/
package com.opendoorlogistics.components.gantt;

import java.awt.Color;
import java.awt.Paint;
import java.awt.Shape;
import java.awt.geom.Ellipse2D;
import java.io.Serializable;
import java.text.DateFormat;
import java.text.FieldPosition;
import java.text.ParsePosition;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.swing.Icon;
import javax.swing.JPanel;

import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.LegendItem;
import org.jfree.chart.LegendItemCollection;
import org.jfree.chart.StandardChartTheme;
import org.jfree.chart.axis.CategoryAxis;
import org.jfree.chart.axis.DateAxis;
import org.jfree.chart.entity.CategoryItemEntity;
import org.jfree.chart.entity.EntityCollection;
import org.jfree.chart.labels.CategoryToolTipGenerator;
import org.jfree.chart.plot.CategoryPlot;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.renderer.category.CategoryItemRenderer;
import org.jfree.chart.renderer.category.GanttRenderer;
import org.jfree.chart.renderer.category.StandardBarPainter;
import org.jfree.chart.urls.CategoryURLGenerator;
import org.jfree.chart.util.ParamChecks;
import org.jfree.data.category.CategoryDataset;
import org.jfree.data.gantt.Task;
import org.jfree.data.gantt.TaskSeries;
import org.jfree.data.gantt.TaskSeriesCollection;

import com.opendoorlogistics.api.ODLApi;
import com.opendoorlogistics.api.StringConventions;
import com.opendoorlogistics.api.components.ComponentConfigurationEditorAPI;
import com.opendoorlogistics.api.components.ComponentControlLauncherApi;
import com.opendoorlogistics.api.components.ComponentControlLauncherApi.ControlLauncherCallback;
import com.opendoorlogistics.api.components.ComponentExecutionApi;
import com.opendoorlogistics.api.components.ODLComponent;
import com.opendoorlogistics.api.scripts.ScriptTemplatesBuilder;
import com.opendoorlogistics.api.standardcomponents.GanntChart;
import com.opendoorlogistics.api.tables.ODLDatastore;
import com.opendoorlogistics.api.tables.ODLDatastoreAlterable;
import com.opendoorlogistics.api.tables.ODLTable;
import com.opendoorlogistics.api.tables.ODLTableAlterable;
import com.opendoorlogistics.api.tables.ODLTableDefinition;
import com.opendoorlogistics.api.tables.ODLTime;
import com.opendoorlogistics.api.ui.Disposable;
import com.opendoorlogistics.core.utils.Colours;
import com.opendoorlogistics.core.utils.Colours.CalculateAverageColour;
import com.opendoorlogistics.utils.ui.Icons;

public class GanttChartComponent implements ODLComponent, GanntChart {
    private static class MySubtask extends Task {
        private final GanttItem item;

        MySubtask(GanttItem item, Date start, Date end) {
            super("", start, end);
            this.item = item;
        }

        GanttItem getItem() {
            return item;
        }
    }

    @Override
    public String getId() {
        return "com.opendoorlogistics.components.gantt";
    }

    @Override
    public String getName() {
        return "Gantt chart";
    }

    @Override
    public ODLDatastore<? extends ODLTableDefinition> getIODsDefinition(ODLApi api, Serializable configuration) {
        return GanttItem.beanMapping.getDefinition();
    }

    @Override
    public ODLDatastore<? extends ODLTableDefinition> getOutputDsDefinition(ODLApi api, int mode,
            Serializable configuration) {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public void execute(final ComponentExecutionApi api, int mode, Object configuration,
            ODLDatastore<? extends ODLTable> ioDs, ODLDatastoreAlterable<? extends ODLTableAlterable> outputDs) {
        // Get items and sort by resource then date
        final StringConventions sc = api.getApi().stringConventions();
        List<GanttItem> items = GanttItem.beanMapping.getTableMapping(0).readObjectsFromTable(ioDs.getTableAt(0));

        // Rounding doubles to longs can create small errors where a start time is 1 millisecond after an end.
        // Set all start times to be <= end time
        for (GanttItem item : items) {
            if (item.getStart() == null || item.getEnd() == null) {
                throw new RuntimeException("Found Gannt item with null start or end time.");
            }

            if (item.getStart().getValue() > item.getEnd().getValue()) {
                item.setStart(item.getEnd());
            }
        }

        Collections.sort(items, new Comparator<GanttItem>() {

            @Override
            public int compare(GanttItem o1, GanttItem o2) {
                int diff = sc.compareStandardised(o1.getResourceId(), o2.getResourceId());
                if (diff == 0) {
                    diff = o1.getStart().compareTo(o2.getStart());
                }
                if (diff == 0) {
                    diff = o1.getEnd().compareTo(o2.getEnd());
                }
                if (diff == 0) {
                    diff = sc.compareStandardised(o1.getActivityId(), o2.getActivityId());
                }
                if (diff == 0) {
                    diff = Colours.compare(o1.getColor(), o2.getColor());
                }
                return diff;
            }
        });

        // Filter any zero duration items
        Iterator<GanttItem> it = items.iterator();
        while (it.hasNext()) {
            GanttItem item = it.next();
            if (item.getStart().compareTo(item.getEnd()) == 0) {
                it.remove();
            }
        }

        // Get average colour for each activity type
        Map<String, CalculateAverageColour> calcColourMap = api.getApi().stringConventions()
                .createStandardisedMap();
        for (GanttItem item : items) {
            CalculateAverageColour calc = calcColourMap.get(item.getActivityId());
            if (calc == null) {
                calc = new CalculateAverageColour();
                calcColourMap.put(item.getActivityId(), calc);
            }
            calc.add(item.getColor());
        }

        // Put into colour map
        Map<String, Color> colourMap = api.getApi().stringConventions().createStandardisedMap();
        for (Map.Entry<String, CalculateAverageColour> entry : calcColourMap.entrySet()) {
            colourMap.put(entry.getKey(), entry.getValue().getAverage());
        }

        // Split items by resource
        ArrayList<ArrayList<GanttItem>> splitByResource = new ArrayList<>();
        ArrayList<GanttItem> current = null;
        for (GanttItem item : items) {
            if (current == null
                    || sc.compareStandardised(current.get(0).getResourceId(), item.getResourceId()) != 0) {
                current = new ArrayList<>();
                splitByResource.add(current);
            }
            current.add(item);
        }

        // put into jfreechart's task data structure
        TaskSeries ts = new TaskSeries("Resources");
        for (ArrayList<GanttItem> resource : splitByResource) {
            // get earliest and latest time (last time may not be in the last item)
            ODLTime earliest = resource.get(0).getStart();
            ODLTime latest = null;
            for (GanttItem item : resource) {
                if (latest == null || latest.compareTo(item.getEnd()) < 0) {
                    latest = item.getEnd();
                }
            }

            Task task = new Task(resource.get(0).getResourceId(), new Date(earliest.getTotalMilliseconds()),
                    new Date(latest.getTotalMilliseconds()));

            // add all items as subtasks
            for (GanttItem item : resource) {
                task.addSubtask(new MySubtask(item, new Date(item.getStart().getTotalMilliseconds()),
                        new Date(item.getEnd().getTotalMilliseconds())));
            }

            ts.add(task);
        }
        TaskSeriesCollection collection = new TaskSeriesCollection();
        collection.add(ts);

        // Create the plot
        CategoryAxis categoryAxis = new CategoryAxis(null);
        DateAxis dateAxis = new DateAxis("Time");
        CategoryItemRenderer renderer = new MyRenderer(collection, colourMap);
        final CategoryPlot plot = new CategoryPlot(collection, categoryAxis, dateAxis, renderer);
        plot.setOrientation(PlotOrientation.HORIZONTAL);
        plot.getDomainAxis().setLabel(null);

        ((DateAxis) plot.getRangeAxis()).setDateFormatOverride(new DateFormat() {

            @Override
            public Date parse(String source, ParsePosition pos) {
                // TODO Auto-generated method stub
                return null;
            }

            @Override
            public StringBuffer format(Date date, StringBuffer toAppendTo, FieldPosition fieldPosition) {
                toAppendTo.append(new ODLTime(date.getTime()).toString());
                return toAppendTo;
            }
        });

        // Create the chart and apply the standard theme without shadows to it
        api.submitControlLauncher(new ControlLauncherCallback() {

            @Override
            public void launchControls(ComponentControlLauncherApi launcherApi) {
                JFreeChart chart = new JFreeChart(null, JFreeChart.DEFAULT_TITLE_FONT, plot, true);
                StandardChartTheme theme = new StandardChartTheme("standard theme", false);
                theme.setBarPainter(new StandardBarPainter());
                theme.apply(chart);

                class MyPanel extends ChartPanel implements Disposable {

                    public MyPanel(JFreeChart chart) {
                        super(chart);
                    }

                    @Override
                    public void dispose() {
                        // TODO Auto-generated method stub

                    }

                }
                MyPanel chartPanel = new MyPanel(chart);
                launcherApi.registerPanel("Resource Gantt", null, chartPanel, true);
            }
        });

    }

    /** @see http://stackoverflow.com/questions/8938690 */
    private static class MyRenderer extends GanttRenderer {

        private static final int PASS = 1; // currently have one pass
        private final List<Color> colours = new ArrayList<Color>();
        private final List<String> tooltips = new ArrayList<String>();
        private final TaskSeriesCollection model;
        private final Map<String, Color> colourMap;
        private int row;
        private int col;
        private int index;

        public MyRenderer(TaskSeriesCollection model, Map<String, Color> colourMap) {
            this.model = model;
            this.colourMap = colourMap;

        }

        /**
         * Override the method so we can set custom tooltips
         */
        @Override
        protected void addItemEntity(EntityCollection entities, CategoryDataset dataset, int row, int column,
                Shape hotspot) {
            ParamChecks.nullNotPermitted(hotspot, "hotspot");
            if (!getItemCreateEntity(row, column)) {
                return;
            }

            String tip = null;
            if ((index - 1) < tooltips.size()) {
                tip = tooltips.get(index - 1);
            }

            String url = null;
            CategoryURLGenerator urlster = getItemURLGenerator(row, column);
            if (urlster != null) {
                url = urlster.generateURL(dataset, row, column);
            }
            CategoryItemEntity entity = new CategoryItemEntity(hotspot, tip, url, dataset, dataset.getRowKey(row),
                    dataset.getColumnKey(column));
            entities.add(entity);
        }

        @Override
        public LegendItemCollection getLegendItems() {
            LegendItemCollection legendItemCollection = new LegendItemCollection();
            for (Map.Entry<String, Color> entry : colourMap.entrySet()) {
                legendItemCollection.add(new LegendItem(entry.getKey(), entry.getValue()));
            }
            return legendItemCollection;
        }

        @Override
        public Paint getItemPaint(int row, int col) {
            if (colours.isEmpty() || this.row != row || this.col != col) {
                initInfoPerResource(row, col);
                this.row = row;
                this.col = col;
                index = 0;
            }
            int colourIndex = index++ / PASS;
            // System.out.println(colourIndex + "/" + colours.size());
            return colours.get(colourIndex);
        }

        private void initInfoPerResource(int row, int col) {
            colours.clear();
            tooltips.clear();

            Color c = (Color) super.getItemPaint(row, col);
            float[] a = new float[3];
            Color.RGBtoHSB(c.getRed(), c.getGreen(), c.getBlue(), a);
            TaskSeries series = (TaskSeries) model.getRowKeys().get(row);
            List<Task> tasks = series.getTasks();
            Task resource = tasks.get(col);
            int taskCount = resource.getSubtaskCount();
            taskCount = Math.max(1, taskCount);
            for (int i = 0; i < taskCount; i++) {
                MySubtask subtask = (MySubtask) resource.getSubtask(i);
                Color colour = colourMap.get(subtask.getItem().getActivityId());
                colours.add(colour);
                tooltips.add(subtask.getItem().getName());
            }
        }
    }

    @Override
    public Class<? extends Serializable> getConfigClass() {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public JPanel createConfigEditorPanel(ComponentConfigurationEditorAPI api, int mode, Serializable config,
            boolean isFixedIO) {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public long getFlags(ODLApi api, int mode) {
        return ODLComponent.FLAG_ALLOW_USER_INTERACTION_WHEN_RUNNING
                | ODLComponent.FLAG_OUTPUT_WINDOWS_CAN_BE_SYNCHRONISED;
    }

    @Override
    public Icon getIcon(ODLApi api, int mode) {
        return Icons.loadFromStandardPath("gantt.png");
    }

    @Override
    public boolean isModeSupported(ODLApi api, int mode) {
        return mode == ODLComponent.MODE_DEFAULT;
    }

    @Override
    public void registerScriptTemplates(ScriptTemplatesBuilder templatesApi) {
        templatesApi.registerTemplate("Gantt chart", "Gantt chart", "Gantt chart",
                new GanttChartComponent().getIODsDefinition(templatesApi.getApi(), null), (Serializable) null);
    }

    @Override
    public String activityIdColumnName() {
        return "activity-id";
    }

    @Override
    public String resourceIdColumnName() {
        return "resource-id";
    }

    @Override
    public String startTimeColumnName() {
        return "start-time";
    }

    @Override
    public String endTimeColumnName() {
        return "end-time";
    }

    @Override
    public String colourSourceColumnName() {
        return "colour";
    }

    @Override
    public ODLDatastore<? extends ODLTableDefinition> getIODsDefinition() {
        return getIODsDefinition(null, null);
    }

}