/* GanttProject is an opensource project management tool. License: GPL3 Copyright (C) 2010-2011 Dmitry Barashev, GanttProject Team This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ package net.sourceforge.ganttproject.chart; import java.awt.Color; import java.util.ArrayList; import java.util.Date; import java.util.Iterator; import java.util.List; import java.util.Map; import biz.ganttproject.core.chart.canvas.Canvas; import biz.ganttproject.core.chart.canvas.TextMetrics; import biz.ganttproject.core.chart.canvas.TextSelector; import biz.ganttproject.core.chart.canvas.Canvas.HAlignment; import biz.ganttproject.core.chart.canvas.Canvas.Label; import biz.ganttproject.core.chart.canvas.Canvas.Rectangle; import biz.ganttproject.core.chart.canvas.Canvas.Text; import biz.ganttproject.core.chart.canvas.Canvas.VAlignment; import biz.ganttproject.core.chart.grid.Offset; import biz.ganttproject.core.chart.grid.OffsetLookup; import net.sourceforge.ganttproject.resource.LoadDistribution; import net.sourceforge.ganttproject.resource.HumanResource; import net.sourceforge.ganttproject.resource.LoadDistribution.Load; import net.sourceforge.ganttproject.task.ResourceAssignment; import net.sourceforge.ganttproject.task.Task; /** * Renders resource load chart */ class ResourceLoadRenderer extends ChartRendererBase { private List<LoadDistribution> myDistributions; private final ResourceChart myResourcechart; private final ChartModelResource myModel; private final Canvas myTextCanvas; public ResourceLoadRenderer(ChartModelResource model, ResourceChart resourceChart) { super(model); myResourcechart = resourceChart; myModel = model; myTextCanvas = getPrimitiveContainer().newLayer(); } /** * Renders load distribution one by one, from top of the chart downwards If * some resource is expanded, calls rendering of the load details */ @Override public void render() { getPrimitiveContainer().setOffset(0, getConfig().getHeaderHeight() - myModel.getVerticalOffset()); beforeProcessingTimeFrames(); int ypos = 0; for (LoadDistribution distribution : myDistributions) { List<Load> loads = distribution.getLoads(); renderLoads(distribution.getDaysOff(), ypos); renderLoads(loads, ypos); if (myResourcechart.isExpanded(distribution.getResource())) { renderLoadDetails(distribution, ypos); ypos += calculateGap(distribution.getResource()); } ypos += getConfig().getRowHeight(); Canvas.Line nextLine = getPrimitiveContainer().createLine(0, ypos, (int) getChartModel().getBounds().getWidth(), ypos); nextLine.setForegroundColor(Color.GRAY); } } /** * Renders resource load details, that is, tasks where the resource is * assigned to, with that resource load percentage */ private void renderLoadDetails(LoadDistribution distribution, int ypos) { int yPos2 = ypos; Map<Task, List<Load>> task2loads = distribution.getSeparatedTaskLoads(); ResourceAssignment[] assignments = distribution.getResource().getAssignments(); for (int i = 0; i < assignments.length; i++) { ResourceAssignment assignment = assignments[i]; List<Load> nextLoads = task2loads.get(assignment.getTask()); yPos2 += getConfig().getRowHeight(); if (nextLoads == null) { continue; } buildTasksLoadsRectangles(nextLoads, yPos2); } } /** * Renders the list of loads in a single chart row Preconditions: loads come * from the same distribution and are ordered by their time offsets */ private void renderLoads(List<Load> loads, int ypos) { Load prevLoad = null; Load curLoad = null; List<Offset> offsets = getDefaultOffsets(); String suffix = ""; for (int curIndex = 1; curIndex < loads.size(); curIndex++) { curLoad = loads.get(curIndex); prevLoad = loads.get(curIndex - 1); if (prevLoad.load != 0) { renderLoads(prevLoad, curLoad, offsets, ypos, suffix); suffix = ""; } else if (curLoad.load > 0) { suffix = ".first"; } } } /** * Renders prevLoad, with curLoad serving as a load right border marker and * style hint */ private void renderLoads(Load prevLoad, Load curLoad, List<Offset> offsets, int ypos, String suffix) { final Date prevEnd = curLoad.startDate; final Date prevStart = prevLoad.startDate; Rectangle nextRect = createRectangle(offsets, prevStart, prevEnd, ypos); if (nextRect == null) { return; } String style; if (prevLoad.isResourceUnavailable()) { style = "dayoff"; } else { suffix += curLoad.load == 0 ? ".last" : ""; if (prevLoad.load < 100f) { style = "load.underload"; } else if (prevLoad.load > 100f) { style = "load.overload"; } else { style = "load.normal"; } style += suffix; } nextRect.setStyle(style); nextRect.setModelObject(new ResourceLoad(prevLoad.load)); createLoadText(nextRect, prevLoad); } /** * Renders a list of loads in a single chart row Precondition: loads belong to * the same pair (resource,task) and are ordered by their time values */ private void buildTasksLoadsRectangles(List<Load> partition, int ypos) { List<Offset> offsets = getDefaultOffsets(); Iterator<Load> loads = partition.iterator(); while (loads.hasNext()) { final Load nextLoad = loads.next(); final Date nextStart = nextLoad.startDate; final Date nextEnd = nextLoad.endDate; final Rectangle nextRect = createRectangle(offsets, nextStart, nextEnd, ypos); if (nextRect == null) { continue; } String style; if (nextLoad.load < 100f) { style = "load.underload"; } else if (nextLoad.load > 100f) { style = "load.overload"; } else { style = "load.normal"; } style += ".first.last"; nextRect.setStyle(style); nextRect.setModelObject(new ResourceLoad(nextLoad.load)); createLoadText(nextRect, nextLoad); } } private void createLoadText(final Rectangle rect, final Load load) { final Text loadLabel = myTextCanvas.createText(rect.getMiddleX(), rect.getTopY(), ""); loadLabel.setSelector(new TextSelector() { @Override public Label[] getLabels(TextMetrics textLengthCalculator) { int loadInt = Math.round(load.load); String loadStr = loadInt + "%"; int emsLength = textLengthCalculator.getTextLength(loadStr); boolean displayLoad = (loadInt != 100 && emsLength <= rect.getWidth()); return displayLoad ? new Label[] {loadLabel.createLabel(loadStr, rect.getWidth())} : new Label[0]; } }); loadLabel.setAlignment(HAlignment.CENTER, VAlignment.TOP); loadLabel.setStyle("text.resource.load"); } private Rectangle createRectangle(List<Offset> offsets, Date start, Date end, int ypos) { if (start.after(getChartEndDate()) || end.compareTo(getChartStartDate()) <= 0) { return null; } OffsetLookup offsetLookup = new OffsetLookup(); int[] bounds = offsetLookup.getBounds(start, end, offsets); return getPrimitiveContainer().createRectangle(bounds[0], ypos, bounds[1] - bounds[0], getConfig().getRowHeight()); } private Date getChartStartDate() { return getChartModel().getStartDate(); } private Date getChartEndDate() { return getChartModel().getBottomUnitOffsets().get(getChartModel().getBottomUnitOffsets().size() - 1).getOffsetEnd(); } private List<Offset> getDefaultOffsets() { return getChartModel().getDefaultUnitOffsets(); } public void beforeProcessingTimeFrames() { myDistributions = new ArrayList<LoadDistribution>(); getPrimitiveContainer().clear(); HumanResource[] resources = ((ChartModelResource) getChartModel()).getVisibleResources(); for (HumanResource resource : resources) { LoadDistribution nextDistribution = resource.getLoadDistribution(); myDistributions.add(nextDistribution); } } /** * Class to use as Model object to display the load percentage in the * rectangle. * * @author bbaranne */ static class ResourceLoad { private float load; ResourceLoad(float load) { this.load = load; } public float getLoad() { return load; } @Override public String toString() { return Float.toString(load); } } private int calculateGap(HumanResource resource) { return resource.getAssignments().length * getConfig().getRowHeight(); } }