/*
Copyright 2003-2012 Dmitry Barashev, GanttProject Team
This file is part of GanttProject, an opensource project management tool.
GanttProject 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.
GanttProject 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 GanttProject. If not, see <http://www.gnu.org/licenses/>.
*/
package net.sourceforge.ganttproject.chart;
import biz.ganttproject.core.chart.canvas.Canvas;
import biz.ganttproject.core.chart.canvas.Canvas.Polygon;
import biz.ganttproject.core.chart.canvas.Canvas.Rectangle;
import biz.ganttproject.core.chart.grid.OffsetList;
import biz.ganttproject.core.chart.render.AlphaRenderingOption;
import biz.ganttproject.core.chart.render.ShapeConstants;
import biz.ganttproject.core.chart.render.ShapePaint;
import biz.ganttproject.core.chart.scene.BarChartActivity;
import biz.ganttproject.core.chart.scene.BarChartConnector;
import biz.ganttproject.core.chart.scene.gantt.Connector;
import biz.ganttproject.core.chart.scene.gantt.DependencySceneBuilder;
import biz.ganttproject.core.chart.scene.gantt.TaskActivitySceneBuilder;
import biz.ganttproject.core.chart.scene.gantt.TaskLabelSceneBuilder;
import biz.ganttproject.core.option.DefaultEnumerationOption;
import biz.ganttproject.core.option.EnumerationOption;
import biz.ganttproject.core.option.GPOption;
import biz.ganttproject.core.option.GPOptionGroup;
import biz.ganttproject.core.time.TimeDuration;
import biz.ganttproject.core.time.TimeUnit;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.base.Strings;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import net.sourceforge.ganttproject.GanttPreviousStateTask;
import net.sourceforge.ganttproject.task.*;
import net.sourceforge.ganttproject.task.dependency.TaskDependency;
import net.sourceforge.ganttproject.task.dependency.TaskDependencyConstraint;
import javax.annotation.Nullable;
import java.awt.*;
import java.util.*;
import java.util.List;
/**
* Renders task rectangles, dependency lines and all task-related text strings
* in the gantt chart
*/
public class TaskRendererImpl2 extends ChartRendererBase {
private ChartModelImpl myModel;
private GPOptionGroup myLabelOptions;
private final TaskLabelSceneBuilder<Task> myLabelsRenderer;
private TaskActivitySceneBuilder.TaskApi<Task, TaskActivity> myTaskApi = new TaskActivitySceneBuilder.TaskApi<Task, TaskActivity>() {
@Override
public boolean isFirst(TaskActivity activity) {
return activity.isFirst();
}
@Override
public boolean isLast(TaskActivity activity) {
return activity.isLast();
}
@Override
public boolean isVoid(TaskActivity activity) {
return activity.getIntensity() == 0f;
}
@Override
public boolean isCriticalTask(Task task) {
return myModel.getChartUIConfiguration().isCriticalPathOn() && task.isCritical();
}
@Override
public boolean isProjectTask(Task task) {
return task.isProjectTask();
}
@Override
public boolean isMilestone(Task task) {
return ((TaskImpl)task).isLegacyMilestone();
}
@Override
public boolean hasNestedTasks(Task task) {
return getChartModel().getTaskManager().getTaskHierarchy().hasNestedTasks(task);
}
@Override
public Color getColor(Task task) {
return task.getColor();
}
@Override
public ShapePaint getShapePaint(Task task) {
if (task.getShape() == null) {
return ShapeConstants.TRANSPARENT;
}
return task.getShape();
}
@Override
public boolean hasNotes(Task task) {
return !Strings.isNullOrEmpty(task.getNotes());
}
};
class TaskActivityChartApi implements TaskActivitySceneBuilder.ChartApi {
TaskActivityChartApi() {
}
@Override
public Date getChartStartDate() {
return myModel.getOffsetAnchorDate();
}
@Override
public Date getEndDate() {
return getChartModel().getEndDate();
}
@Override
public OffsetList getBottomUnitOffsets() {
return getChartModel().getBottomUnitOffsets();
}
@Override
public int getRowHeight() {
return calculateRowHeight();
}
@Override
public int getBarHeight() {
return getRectangleHeight();
}
@Override
public int getViewportWidth() {
return myModel.getBounds().width;
}
@Override
public AlphaRenderingOption getWeekendOpacityOption() {
return myModel.getChartUIConfiguration().getWeekendAlphaValue();
}
}
private final TaskActivitySceneBuilder<Task, TaskActivity> myTaskActivityRenderer;
private final TaskActivitySceneBuilder<Task, TaskActivity> myBaselineActivityRenderer;
private final Canvas myLabelsLayer;
private TaskActivityChartApi myChartApi;
public TaskRendererImpl2(ChartModelImpl model) {
super(model);
this.myModel = model;
getPrimitiveContainer().setOffset(0, model.getChartUIConfiguration().getHeaderHeight());
getPrimitiveContainer().newLayer();
getPrimitiveContainer().newLayer();
getPrimitiveContainer().newLayer();
myLabelsLayer = getPrimitiveContainer().newLayer();
List<String> taskProperties = Lists.newArrayList("", "id", "taskDates", "name", "length", "advancement", "coordinator", "resources", "predecessors");
final DefaultEnumerationOption<String> topLabelOption = new DefaultEnumerationOption<String>("taskLabelUp", taskProperties);
final DefaultEnumerationOption<String> bottomLabelOption = new DefaultEnumerationOption<String>("taskLabelDown", taskProperties);
final DefaultEnumerationOption<String> leftLabelOption = new DefaultEnumerationOption<String>("taskLabelLeft", taskProperties);
final DefaultEnumerationOption<String> rightLabelOption = new DefaultEnumerationOption<String>("taskLabelRight", taskProperties);
myLabelsRenderer = new TaskLabelSceneBuilder<Task>(new TaskLabelSceneBuilder.TaskApi<Task>() {
TaskProperties myLabelFormatter = new TaskProperties(getChartModel().getTimeUnitStack());
@Override
public Object getProperty(Task task, String propertyID) {
return myLabelFormatter.getProperty(task, propertyID);
}
}, new TaskLabelSceneBuilder.InputApi() {
@Override
public EnumerationOption getTopLabelOption() {
return topLabelOption;
}
@Override
public EnumerationOption getBottomLabelOption() {
return bottomLabelOption;
}
@Override
public EnumerationOption getLeftLabelOption() {
return leftLabelOption;
}
@Override
public EnumerationOption getRightLabelOption() {
return rightLabelOption;
}
@Override
public int getFontSize() {
return getChartModel().getChartUIConfiguration().getBaseFontSize();
}
}, myLabelsLayer);
myLabelOptions = new ChartOptionGroup("ganttChartDetails",
new GPOption[] {topLabelOption, bottomLabelOption, leftLabelOption, rightLabelOption},
model.getOptionEventDispatcher());
myChartApi = new TaskActivityChartApi();
myTaskActivityRenderer = createTaskActivitySceneBuilder(getPrimitiveContainer(), myChartApi,
new TaskActivitySceneBuilder.Style(0));
myBaselineActivityRenderer = createTaskActivitySceneBuilder(
getPrimitiveContainer().getLayer(2), new TaskActivityChartApi(),
new TaskActivitySceneBuilder.Style(getRectangleHeight()));
}
private List<Task> getVisibleTasks() {
return ((ChartModelImpl) getChartModel()).getVisibleTasks();
}
/**
This class splits all tasks into 4 groups. One group is pure virtual: it contains
tasks which are hidden under some collapsed parent and hence are just filtered out.
The remaining groups are: tasks which are shown in the chart viewport, tasks above the viewport
and tasks below the viewport. We need tasks outside the viewport because we want to show
dependency lines which may connect them with tasks inside the viewport.
*/
static class VerticalPartitioning {
final List<Task> aboveViewport = Lists.newArrayList();
final List<Task> belowViewport = Lists.newArrayList();
final List<Task> insideViewport;
/**
* @param tasksInsideViewport partition with tasks inside viewport, with hidden tasks already filtered.
* Tasks must be ordered in their document order.
*/
VerticalPartitioning(List<Task> tasksInsideViewport) {
insideViewport = tasksInsideViewport;
}
/**
* Builds the remaining partitions.
*
* In this method we iterate through *all* the tasks in their document order. If we find some
* collapsed task then we filter out its children. Until we reach the first task in the vieport
* partition, we're above the viewport, then we skip the viewport partition and proceed to
* below viewport
*/
void build(TaskContainmentHierarchyFacade containment) {
List<Task> tasksInDocumentOrder = containment.getTasksInDocumentOrder();
final Task firstVisible = insideViewport.isEmpty() ? null : insideViewport.get(0);
final Task lastVisible = insideViewport.isEmpty() ? null : insideViewport.get(insideViewport.size() - 1);
List<Task> addTo = aboveViewport;
Task collapsedRoot = null;
for (Task nextTask : tasksInDocumentOrder) {
if (addTo == null) {
if (nextTask.equals(lastVisible)) {
addTo = belowViewport;
}
continue;
}
if (nextTask.equals(firstVisible)) {
addTo = null;
continue;
}
if (collapsedRoot != null) {
if (containment.areUnrelated(nextTask, collapsedRoot)) {
collapsedRoot = null;
} else {
continue;
}
}
addTo.add(nextTask);
if (!nextTask.getExpand()) {
assert collapsedRoot == null : "All tasks processed prior to this one must be expanded";
collapsedRoot = nextTask;
}
}
}
}
@Override
public void render() {
getPrimitiveContainer().clear();
getPrimitiveContainer().getLayer(0).clear();
getPrimitiveContainer().getLayer(1).clear();
getPrimitiveContainer().getLayer(2).clear();
getPrimitiveContainer().setOffset(0,
myModel.getChartUIConfiguration().getHeaderHeight() - myModel.getVerticalOffset());
getPrimitiveContainer().getLayer(2).setOffset(0,
myModel.getChartUIConfiguration().getHeaderHeight() - myModel.getVerticalOffset());
VerticalPartitioning vp = new VerticalPartitioning(getVisibleTasks());
vp.build(getChartModel().getTaskManager().getTaskHierarchy());
OffsetList defaultUnitOffsets = getChartModel().getDefaultUnitOffsets();
renderVisibleTasks(getVisibleTasks(), defaultUnitOffsets);
renderTasksAboveAndBelowViewport(vp.aboveViewport, vp.belowViewport, defaultUnitOffsets);
renderDependencies();
}
private class BarChartConnectorImpl implements BarChartConnector<Task, BarChartConnectorImpl> {
private final Task myTask;
private final TaskDependency myDep;
public BarChartConnectorImpl(Task task, TaskDependency d) {
myTask = Preconditions.checkNotNull(task);
myDep = Preconditions.checkNotNull(d);
}
@Override
public BarChartActivity<Task> getStart() {
TaskActivity startActivity = (TaskActivity) myDep.getStart();
List<TaskActivity> splitActivities = splitOnViewportBounds(Collections.singletonList(startActivity));
assert (splitActivities.size() > 0) : String.format("It is expected that split activities length is >= 1 for dep=%s", myDep.toString());
TaskDependencyConstraint.Type type = myDep.getConstraint().getType();
if (type == TaskDependencyConstraint.Type.finishfinish || type == TaskDependencyConstraint.Type.finishstart) {
return splitActivities.get(splitActivities.size() - 1);
} else {
return splitActivities.get(0);
}
}
@Override
public BarChartActivity<Task> getEnd() {
TaskActivity endActivity = (TaskActivity) myDep.getEnd();
List<TaskActivity> splitActivities = splitOnViewportBounds(Collections.singletonList(endActivity));
assert (splitActivities.size() > 0) : String.format("It is expected that split activities length is >= 1 for dep=%s", myDep.toString());
TaskDependencyConstraint.Type type = myDep.getConstraint().getType();
if (type == TaskDependencyConstraint.Type.finishfinish || type == TaskDependencyConstraint.Type.finishstart) {
return splitActivities.get(0);
} else {
return splitActivities.get(splitActivities.size() - 1);
}
}
@Override
public BarChartConnectorImpl getImpl() {
return this;
}
@Override
public Dimension getStartVector() {
TaskDependencyConstraint.Type type = myDep.getConstraint().getType();
if (type == TaskDependencyConstraint.Type.finishfinish || type == TaskDependencyConstraint.Type.finishstart) {
return Connector.Vector.EAST;
}
return Connector.Vector.WEST;
}
@Override
public Dimension getEndVector() {
TaskDependencyConstraint.Type type = myDep.getConstraint().getType();
if (type == TaskDependencyConstraint.Type.finishfinish || type == TaskDependencyConstraint.Type.startfinish) {
return Connector.Vector.EAST;
}
return Connector.Vector.WEST;
}
TaskDependency getDependency() {
return myDep;
}
}
private void renderDependencies() {
DependencySceneBuilder.ChartApi chartApi = new DependencySceneBuilder.ChartApi() {
@Override
public int getBarHeight() {
return getRectangleHeight();
}
};
DependencySceneBuilder.TaskApi<Task, BarChartConnectorImpl> taskApi = new DependencySceneBuilder.TaskApi<Task, BarChartConnectorImpl>() {
@Override
public boolean isMilestone(Task task) {
return task.isMilestone();
}
@Override
public Dimension getUnitVector(BarChartActivity<Task> activity, BarChartConnectorImpl connector) {
if (activity.equals(connector.getStart())) {
return connector.getStartVector();
} else if (activity.equals(connector.getEnd())) {
return connector.getEndVector();
} else {
assert false : String.format("Should not be here. activity=%s, connector=%s", activity, connector);
return null;
}
}
@Override
public String getStyle(BarChartConnectorImpl dependency) {
return dependency.getDependency().getHardness() == TaskDependency.Hardness.STRONG
? "dependency.line.hard" : "dependency.line.rubber";
}
@Override
public Iterable<BarChartConnectorImpl> getConnectors(Task task) {
TaskDependency[] deps = task.getDependencies().toArray();
List<BarChartConnectorImpl> result = Lists.newArrayListWithCapacity(deps.length);
for (TaskDependency d : deps) {
result.add(new BarChartConnectorImpl(task, d));
}
return result;
}
@Override
public List<Task> getTasks() {
return myModel.getVisibleTasks();
}
};
DependencySceneBuilder<Task, BarChartConnectorImpl> dependencyRenderer = new DependencySceneBuilder<>(
getPrimitiveContainer(), getPrimitiveContainer().getLayer(1), taskApi, chartApi);
dependencyRenderer.build();
}
private void renderTasksAboveAndBelowViewport(List<Task> tasksAboveViewport, List<Task> tasksBelowViewport,
OffsetList defaultUnitOffsets) {
for (Task nextAbove : tasksAboveViewport) {
List<TaskActivity> activities = /*nextAbove.isMilestone() ? Collections.<TaskActivity> singletonList(new MilestoneTaskFakeActivity(
nextAbove)) : */nextAbove.getActivities();
for (Canvas.Shape s : renderActivities(-1, nextAbove, activities, defaultUnitOffsets, false)) {
s.setVisible(false);
}
}
for (Task nextBelow : tasksBelowViewport) {
List<TaskActivity> activities = /*nextBelow.isMilestone() ? Collections.<TaskActivity> singletonList(new MilestoneTaskFakeActivity(
nextBelow)) : */nextBelow.getActivities();
List<Polygon> rectangles = renderActivities(getVisibleTasks().size() + 1, nextBelow, activities,
defaultUnitOffsets, false);
for (Polygon nextRectangle : rectangles) {
nextRectangle.setVisible(false);
}
}
}
private void renderVisibleTasks(List<Task> visibleTasks, OffsetList defaultUnitOffsets) {
List<Polygon> boundPolygons = Lists.newArrayList();
int rowNum = 0;
for (Task t : visibleTasks) {
boundPolygons.clear();
List<TaskActivity> activities = t.getActivities();
activities = splitOnViewportBounds(activities);
List<Polygon> rectangles = renderActivities(rowNum, t, activities, defaultUnitOffsets, true);
for (Polygon p : rectangles) {
if (p.getModelObject() != null) {
boundPolygons.add(p);
}
}
renderLabels(boundPolygons);
renderBaseline(t, rowNum, defaultUnitOffsets);
rowNum++;
Canvas.Line nextLine = getPrimitiveContainer().createLine(0, rowNum * getRowHeight(),
(int) getChartModel().getBounds().getWidth(), rowNum * getRowHeight());
nextLine.setForegroundColor(Color.GRAY);
}
}
/**
* Some parts of the renderer, e.g. progress bar rendering, don't like activities which cross
* the viewport borders. The reason is that we build shapes (specifically, rectangles) only for
* visible parts of activities. When activity crosses the viewport border, the invisible parts
* are no more than ~20px wide. However, progress bar needs to know pixel size of all shapes from
* the task beginning up to the point where progress bar should be terminated OR needs activities
* to be split exactly at the viewport border.
*
* @param activities
* @return
*/
private List<TaskActivity> splitOnViewportBounds(List<TaskActivity> activities) {
return TaskRendererImpl2.splitOnBounds(activities, getChartModel().getStartDate(), myChartApi.getEndDate());
}
/**
* This method scans the list of activities and splits activities crossing the borders
* of the given frame into parts "before" and "after" the border date. Activities which
* do not cross frame borders are left as is, and the relative order of activities is preserved.
*
* Normally no more than two activities from the input list are partitioned.
*
* @return input activities with those crossing frame borders partitioned into left and right parts
*/
static List<TaskActivity> splitOnBounds(List<TaskActivity> activities, Date frameStartDate, Date frameEndDate) {
Preconditions.checkArgument(frameEndDate.compareTo(frameStartDate) >= 0,
String.format("Invalid frame: start=%s end=%s", frameStartDate, frameEndDate));
List<TaskActivity> result = Lists.newArrayList();
Deque<TaskActivity> queue = new LinkedList<>(activities);
while (!queue.isEmpty()) {
TaskActivity head = queue.pollFirst();
if (head.getStart().compareTo(frameStartDate) < 0
&& head.getEnd().compareTo(frameStartDate) > 0) {
// Okay, this activity crosses frame start. Lets add its left part to the result
// and push back its right part
TaskActivity beforeViewport = new TaskActivityPart(head, head.getStart(), frameStartDate);
TaskActivity remaining = new TaskActivityPart(head, frameStartDate, head.getEnd());
result.add(beforeViewport);
queue.addFirst(remaining);
continue;
}
if (head.getStart().compareTo(frameEndDate) < 0
&& head.getEnd().compareTo(frameEndDate) > 0) {
// This activity crosses frame end date. Again, lets add its left part to the result
// and push back the remainder.
TaskActivity insideViewport = new TaskActivityPart(head, head.getStart(), frameEndDate);
TaskActivity remaining = new TaskActivityPart(head, frameEndDate, head.getEnd());
result.add(insideViewport);
queue.addFirst(remaining);
continue;
}
result.add(head);
}
return result;
}
private int getRowHeight() {
return calculateRowHeight();
}
private void renderBaseline(Task t, int rowNum, OffsetList defaultUnitOffsets) {
TaskActivitiesAlgorithm alg = new TaskActivitiesAlgorithm(getCalendar());
List<GanttPreviousStateTask> baseline = myModel.getBaseline();
if (baseline != null) {
for (GanttPreviousStateTask taskBaseline : baseline) {
if (taskBaseline.getId() == t.getTaskID()) {
Date startDate = taskBaseline.getStart().getTime();
TimeDuration duration = getChartModel().getTaskManager().createLength(taskBaseline.getDuration());
Date endDate = getCalendar().shiftDate(startDate, duration);
if (endDate.equals(t.getEnd().getTime())) {
return;
}
List<String> styles = new ArrayList<String>();
if (t.isMilestone()) {
styles.add("milestone");
}
if (endDate.compareTo(t.getEnd().getTime()) < 0) {
styles.add("later");
} else {
styles.add("earlier");
}
List<TaskActivity> baselineActivities = new ArrayList<TaskActivity>();
if (t.isMilestone()) {
baselineActivities.add(new MilestoneTaskFakeActivity(t, startDate, endDate));
} else {
alg.recalculateActivities(t, baselineActivities, startDate, endDate);
}
List<Polygon> baselineRectangles = myBaselineActivityRenderer.renderActivities(rowNum, baselineActivities,
defaultUnitOffsets);
for (int i = 0; i < baselineRectangles.size(); i++) {
Polygon r = baselineRectangles.get(i);
r.setStyle("previousStateTask");
for (String s : styles) {
r.addStyle(s);
}
if (i == 0) {
r.addStyle("start");
}
if (i == baselineRectangles.size() - 1) {
r.addStyle("end");
}
}
return;
}
}
}
}
private static Predicate<Canvas.Polygon> REMOVE_SUPERTASK_ENDINGS = new Predicate<Canvas.Polygon>() {
@Override
public boolean apply(@Nullable Canvas.Polygon shape) {
return !shape.hasStyle("task.ending");
}
};
private List<Polygon> renderActivities(final int rowNum, Task t, List<TaskActivity> activities,
OffsetList defaultUnitOffsets, boolean areVisible) {
List<Canvas.Polygon> rectangles = myTaskActivityRenderer.renderActivities(rowNum, activities, defaultUnitOffsets);
if (areVisible && !getChartModel().getTaskManager().getTaskHierarchy().hasNestedTasks(t) && !t.isMilestone() && !t.isProjectTask()) {
renderProgressBar(Lists.newArrayList(Iterables.filter(rectangles, REMOVE_SUPERTASK_ENDINGS)));
}
if (areVisible && myTaskApi.hasNotes(t)) {
Rectangle notes = getPrimitiveContainer().createRectangle(myModel.getBounds().width - 24, rowNum * getRowHeight() + getRowHeight()/2 - 8, 16, 16);
notes.setStyle("task.notesMark");
getPrimitiveContainer().bind(notes, t);
}
return rectangles;
}
private void renderLabels(List<Polygon> rectangles) {
if (!rectangles.isEmpty()) {
myLabelsRenderer.renderLabels(rectangles);
}
}
private void renderProgressBar(List<Polygon> rectangles) {
if (rectangles.isEmpty()) {
return;
}
final Canvas container = getPrimitiveContainer().getLayer(0);
final TimeUnit timeUnit = getChartModel().getTimeUnitStack().getDefaultTimeUnit();
final Task task = ((TaskActivity) rectangles.get(0).getModelObject()).getOwner();
float length = task.getDuration().getLength(timeUnit);
float completed = task.getCompletionPercentage() * length / 100f;
Polygon lastProgressRectangle = null;
for (Polygon nextRectangle : rectangles) {
final TaskActivity nextActivity = (TaskActivity) nextRectangle.getModelObject();
final float nextLength = nextActivity.getDuration().getLength(timeUnit);
final int nextProgressBarLength;
if (completed > nextLength || nextActivity.getIntensity() == 0f) {
nextProgressBarLength = nextRectangle.getWidth();
if (nextActivity.getIntensity() > 0f) {
completed -= nextLength;
}
} else {
nextProgressBarLength = (int) (nextRectangle.getWidth() * (completed / nextLength));
completed = 0f;
}
final Rectangle nextProgressBar = container.createRectangle(nextRectangle.getLeftX(),
nextRectangle.getMiddleY() - 1, nextProgressBarLength, 3);
nextProgressBar.setStyle(completed == 0f ? "task.progress.end" : "task.progress");
getPrimitiveContainer().getLayer(0).bind(nextProgressBar, task);
if (completed == 0) {
lastProgressRectangle = nextRectangle;
break;
}
}
if (lastProgressRectangle == null) {
lastProgressRectangle = rectangles.get(rectangles.size() - 1);
}
// createDownSideText(lastProgressRectangle);
}
public GPOptionGroup getLabelOptions() {
return myLabelOptions;
}
int calculateRowHeight() {
int rowHeight = myLabelsRenderer.calculateRowHeight();
if (myModel.getBaseline() != null) {
rowHeight = rowHeight + 8;
}
int appFontSize = myModel.getProjectConfig().getAppFontSize().get();
return Math.max(appFontSize, rowHeight);
}
private int getRectangleHeight() {
return myLabelsRenderer.getFontHeight();
}
Canvas getLabelLayer() {
return myLabelsLayer;
}
private TaskActivitySceneBuilder<Task, TaskActivity> createTaskActivitySceneBuilder(
Canvas canvas, TaskActivitySceneBuilder.ChartApi chartApi, TaskActivitySceneBuilder.Style style) {
return new TaskActivitySceneBuilder<Task, TaskActivity>(myTaskApi, chartApi, canvas, myLabelsRenderer, style);
}
public static List<Rectangle> getTaskRectangles(Task t, ChartModelImpl chartModel) {
List<Rectangle> result = new ArrayList<Rectangle>();
List<TaskActivity> originalActivities = t.getActivities();
List<TaskActivity> splitOnBounds = TaskRendererImpl2.splitOnBounds(originalActivities, chartModel.getStartDate(), chartModel.getEndDate());
for (TaskActivity activity : splitOnBounds) {
assert activity != null : "Got null activity in task="+t;
Canvas.Shape graphicPrimitive = chartModel.getGraphicPrimitive(activity);
assert graphicPrimitive != null : "Got null for activity="+activity;
assert graphicPrimitive instanceof Rectangle;
result.add((Rectangle) graphicPrimitive);
}
return result;
}
}