Recode 'Task Completation Lead/Lag' using jqPlot

This commit is contained in:
Diego Pino 2012-04-26 16:45:36 +02:00
parent 3d8b044b17
commit 8b86147642
4 changed files with 529 additions and 250 deletions

View file

@ -33,6 +33,7 @@ import org.apache.commons.lang.StringUtils;
import org.libreplan.business.orders.entities.Order;
import org.libreplan.business.planner.entities.TaskStatusEnum;
import org.libreplan.web.common.Util;
import org.libreplan.web.dashboard.DashboardModel.Interval;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@ -43,7 +44,6 @@ import org.zkoss.zul.Grid;
import org.zkoss.zul.Label;
import org.zkoss.zul.Window;
/**
* @author Nacho Barrientos <nacho@igalia.com>
* @author Diego Pino García <dpino@igalia.com>
@ -59,7 +59,7 @@ public class DashboardController extends GenericForwardComposer {
private Window dashboardWindow;
private Grid gridTasksSummary;
private Div projectDashboardChartsDiv;
private Div projectDashboardNoTasksWarningDiv;
@ -69,246 +69,321 @@ public class DashboardController extends GenericForwardComposer {
@Override
public void doAfterCompose(org.zkoss.zk.ui.Component comp) throws Exception {
super.doAfterCompose(comp);
this.dashboardWindow = (Window)comp;
this.dashboardWindow = (Window) comp;
self.setAttribute("controller", this);
Util.createBindingsFor(this.dashboardWindow);
}
public void setCurrentOrder(Order order) {
dashboardModel.setCurrentOrder(order);
if (dashboardModel.tasksAvailable()) {
showCharts();
} else {
hideCharts();
}
if (this.dashboardWindow != null) {
renderGlobalProgress();
renderTaskStatus();
renderTasksSummary();
}
}
private void renderTasksSummary() {
Map<TaskStatusEnum, Integer> taskStatus = dashboardModel.calculateTaskStatus();
taskStatus("lblTasksFinished", taskStatus.get(TaskStatusEnum.FINISHED));
taskStatus("lblTasksBlocked", taskStatus.get(TaskStatusEnum.BLOCKED));
taskStatus("lblTasksInProgress", taskStatus.get(TaskStatusEnum.IN_PROGRESS));
taskStatus("lblTasksReadyToStart", taskStatus.get(TaskStatusEnum.READY_TO_START));
}
private void taskStatus(String key, Integer value) {
Label label = (Label) gridTasksSummary.getFellowIfAny(key);
if (label != null) {
label.setValue(String.format(_("%d tasks"), value));
}
}
private void renderTaskStatus() {
TaskStatus taskStatus = TaskStatus.create();
taskStatus.data(_("Finished"),
dashboardModel.getPercentageOfFinishedTasks());
taskStatus.data(_("In progress"),
dashboardModel.getPercentageOfInProgressTasks());
taskStatus.data(_("Ready to start"),
dashboardModel.getPercentageOfReadyToStartTasks());
taskStatus.data(_("Blocked"),
dashboardModel.getPercentageOfBlockedTasks());
taskStatus.render();
}
public void setCurrentOrder(Order order) {
dashboardModel.setCurrentOrder(order);
if (dashboardModel.tasksAvailable()) {
showCharts();
} else {
hideCharts();
}
if (this.dashboardWindow != null) {
renderGlobalProgress();
renderTaskStatus();
renderTaskCompletationLag();
renderTasksSummary();
}
}
private void renderGlobalProgress() {
GlobalProgress globalProgress = GlobalProgress.create();
private void renderTaskCompletationLag() {
Map<Interval, Integer> taskCompletationData = dashboardModel
.calculateTaskCompletation();
TaskCompletationLag taskCompletation = TaskCompletationLag.create();
for (Interval each : taskCompletationData.keySet()) {
Integer value = taskCompletationData.get(each);
taskCompletation.data(each.toString(), value);
}
taskCompletation.render();
}
private void renderTasksSummary() {
Map<TaskStatusEnum, Integer> taskStatus = dashboardModel
.calculateTaskStatus();
taskStatus("lblTasksFinished", taskStatus.get(TaskStatusEnum.FINISHED));
taskStatus("lblTasksBlocked", taskStatus.get(TaskStatusEnum.BLOCKED));
taskStatus("lblTasksInProgress",
taskStatus.get(TaskStatusEnum.IN_PROGRESS));
taskStatus("lblTasksReadyToStart",
taskStatus.get(TaskStatusEnum.READY_TO_START));
}
private void taskStatus(String key, Integer value) {
Label label = (Label) gridTasksSummary.getFellowIfAny(key);
if (label != null) {
label.setValue(String.format(_("%d tasks"), value));
}
}
private void renderTaskStatus() {
TaskStatus taskStatus = TaskStatus.create();
taskStatus.data(_("Finished"),
dashboardModel.getPercentageOfFinishedTasks());
taskStatus.data(_("In progress"),
dashboardModel.getPercentageOfInProgressTasks());
taskStatus.data(_("Ready to start"),
dashboardModel.getPercentageOfReadyToStartTasks());
taskStatus.data(_("Blocked"),
dashboardModel.getPercentageOfBlockedTasks());
taskStatus.render();
}
private void renderGlobalProgress() {
GlobalProgress globalProgress = GlobalProgress.create();
// Current values
globalProgress.current(GlobalProgress.CRITICAL_PATH_DURATION,
dashboardModel.getCriticalPathProgressByDuration());
globalProgress.current(GlobalProgress.CRITICAL_PATH_HOURS,
dashboardModel.getCriticalPathProgressByNumHours());
globalProgress.current(GlobalProgress.ALL_TASKS_HOURS,
dashboardModel.getAdvancePercentageByHours());
// Expected values
globalProgress.expected(GlobalProgress.CRITICAL_PATH_DURATION,
dashboardModel.getExpectedCriticalPathProgressByDuration());
globalProgress.expected(GlobalProgress.CRITICAL_PATH_HOURS,
dashboardModel.getExpectedCriticalPathProgressByNumHours());
globalProgress.expected(GlobalProgress.ALL_TASKS_HOURS,
dashboardModel.getExpectedAdvancePercentageByHours());
globalProgress.render();
}
private void showCharts() {
projectDashboardChartsDiv.setVisible(true);
projectDashboardNoTasksWarningDiv.setVisible(false);
}
// Current values
globalProgress.current(GlobalProgress.CRITICAL_PATH_DURATION,
dashboardModel.getCriticalPathProgressByDuration());
globalProgress.current(GlobalProgress.CRITICAL_PATH_HOURS,
dashboardModel.getCriticalPathProgressByNumHours());
globalProgress.current(GlobalProgress.ALL_TASKS_HOURS,
dashboardModel.getAdvancePercentageByHours());
// Expected values
globalProgress.expected(GlobalProgress.CRITICAL_PATH_DURATION,
dashboardModel.getExpectedCriticalPathProgressByDuration());
globalProgress.expected(GlobalProgress.CRITICAL_PATH_HOURS,
dashboardModel.getExpectedCriticalPathProgressByNumHours());
globalProgress.expected(GlobalProgress.ALL_TASKS_HOURS,
dashboardModel.getExpectedAdvancePercentageByHours());
globalProgress.render();
}
private void showCharts() {
projectDashboardChartsDiv.setVisible(true);
projectDashboardNoTasksWarningDiv.setVisible(false);
}
private void hideCharts() {
projectDashboardChartsDiv.setVisible(false);
projectDashboardNoTasksWarningDiv.setVisible(true);
}
/**
*
* @author Diego Pino García <dpino@igalia.com>
*
*
*/
static class GlobalProgress {
static class GlobalProgress {
public static final String ALL_TASKS_HOURS = _("All tasks (hours)");
public static final String ALL_TASKS_HOURS = _("All tasks (hours)");
public static final String CRITICAL_PATH_HOURS = _("Critical path (hours)");
public static final String CRITICAL_PATH_HOURS = _("Critical path (hours)");
public static final String CRITICAL_PATH_DURATION = _("Critical path (duration)");
public static final String CRITICAL_PATH_DURATION = _("Critical path (duration)");
private Map<String, BigDecimal> current = new LinkedHashMap<String, BigDecimal>();
private final Map<String, BigDecimal> current = new LinkedHashMap<String, BigDecimal>();
private Map<String, BigDecimal> expected = new LinkedHashMap<String, BigDecimal>();
private final Map<String, BigDecimal> expected = new LinkedHashMap<String, BigDecimal>();
private static List<Series> series = new ArrayList<Series>() {
{
add(Series.create(_("Current"), "blue"));
add(Series.create(_("Expected"), "red"));
}
};
private static List<Series> series = new ArrayList<Series>() {
{
add(Series.create(_("Current"), "#33c"));
add(Series.create(_("Expected"), "#c33"));
}
};
private GlobalProgress() {
private GlobalProgress() {
}
}
public void current(String key, BigDecimal value) {
current.put(key, value);
}
public void current(String key, BigDecimal value) {
current.put(key, value);
}
public void expected(String key, BigDecimal value) {
expected.put(key, value);
}
public void expected(String key, BigDecimal value) {
expected.put(key, value);
}
public static GlobalProgress create() {
return new GlobalProgress();
}
public static GlobalProgress create() {
return new GlobalProgress();
}
public String getPercentages() {
return String.format("'[%s, %s]'",
jsonifyPercentages(current.values()),
jsonifyPercentages(expected.values()));
}
public String getPercentages() {
return String.format("'[%s, %s]'",
jsonifyPercentages(current.values()),
jsonifyPercentages(expected.values()));
}
private String jsonifyPercentages(Collection<BigDecimal> array) {
List<String> result = new ArrayList<String>();
private String jsonifyPercentages(Collection<BigDecimal> array) {
List<String> result = new ArrayList<String>();
int i = 1;
for (BigDecimal each : array) {
result.add(String.format("[%.2f, %d]", each.doubleValue(), i++));
}
return String.format("[%s]", StringUtils.join(result, ","));
}
int i = 1;
for (BigDecimal each : array) {
result.add(String.format("[%.2f, %d]", each.doubleValue(), i++));
}
return String.format("[%s]", StringUtils.join(result, ","));
}
private String jsonify(Collection<?> list) {
Collection<String> result = new ArrayList<String>();
for (Object each : list) {
if (each.getClass() == String.class) {
result.add(String.format("\"%s\"", each.toString()));
} else {
result.add(String.format("%s", each.toString()));
}
}
return String.format("'[%s]'", StringUtils.join(result, ','));
}
private String jsonify(Collection<?> list) {
Collection<String> result = new ArrayList<String>();
for (Object each : list) {
if (each.getClass() == String.class) {
result.add(String.format("\"%s\"", each.toString()));
} else {
result.add(String.format("%s", each.toString()));
}
}
return String.format("'[%s]'", StringUtils.join(result, ','));
}
public String getSeries() {
return jsonify(series);
}
public String getSeries() {
return jsonify(series);
}
/**
* The order of the ticks is taken from the keys in current
*
* @return
*/
public String getTicks() {
return jsonify(current.keySet());
}
/**
* The order of the ticks is taken from the keys in current
*
* @return
*/
public String getTicks() {
return jsonify(current.keySet());
}
public void render() {
String command = String.format(
"global_progress.render(%s, %s, %s);", getPercentages(),
getTicks(), getSeries());
Clients.evalJavaScript(command);
}
public void render() {
String command = String.format(
"global_progress.render(%s, %s, %s);", getPercentages(),
getTicks(), getSeries());
Clients.evalJavaScript(command);
}
}
}
/**
*
* @author Diego Pino García <dpino@igalia.com>
*
*
*/
static class Series {
private String label;
private String color;
private Series() {
}
public static Series create(String label) {
Series series = new Series();
series.label = label;
return series;
}
public static Series create(String label, String color) {
Series series = new Series();
series.label = label;
series.color = color;
return series;
}
public String toString() {
return String.format("{\"label\": \"%s\", \"color\": \"%s\"}", label, color);
}
private String label;
private String color;
private Series() {
}
public static Series create(String label) {
Series series = new Series();
series.label = label;
return series;
}
public static Series create(String label, String color) {
Series series = new Series();
series.label = label;
series.color = color;
return series;
}
@Override
public String toString() {
return String.format("{\"label\": \"%s\", \"color\": \"%s\"}",
label, color);
}
}
/**
*
* @author Diego Pino García <dpino@igalia.com>
*
*
*/
static class TaskStatus {
private Map<String, BigDecimal> data = new LinkedHashMap<String, BigDecimal>();
private TaskStatus() {
}
public static TaskStatus create() {
return new TaskStatus();
}
private String getData() {
List<String> result = new ArrayList<String>();
TreeSet<String> keys = new TreeSet<String>(data.keySet());
for (String key : keys) {
BigDecimal value = data.get(key);
result.add(String.format("[\"%s\", %.2f]", key, value));
}
return String.format("'[%s]'", StringUtils.join(result, ","));
}
public void data(String key, BigDecimal value) {
data.put(key, value);
}
public void render() {
String command = String.format("task_status.render(%s);", getData());
Clients.evalJavaScript(command);
}
private final Map<String, BigDecimal> data = new LinkedHashMap<String, BigDecimal>();
private TaskStatus() {
}
public static TaskStatus create() {
return new TaskStatus();
}
private String getData() {
List<String> result = new ArrayList<String>();
TreeSet<String> keys = new TreeSet<String>(data.keySet());
for (String key : keys) {
BigDecimal value = data.get(key);
result.add(String.format("[\"%s\", %.2f]", key, value));
}
return String.format("'[%s]'", StringUtils.join(result, ","));
}
public void data(String key, BigDecimal value) {
data.put(key, value);
}
public void render() {
String command = String
.format("task_status.render(%s);", getData());
Clients.evalJavaScript(command);
}
}
static class TaskCompletationLag {
private final String id = "task_completation_lag";
private final Map<String, Integer> data = new LinkedHashMap<String, Integer>();
private TaskCompletationLag() {
}
public static TaskCompletationLag create() {
return new TaskCompletationLag();
}
public void data(String interval, Integer value) {
data.put(interval, value);
}
public void render() {
String _data = JSONHelper.values(data);
String ticks = JSONHelper.keys(data);
String command = String.format("%s.render(%s, %s);", id, _data,
ticks);
Clients.evalJavaScript(command);
}
}
static class JSONHelper {
public static String format(Map<String, Integer> data) {
List<String> result = new ArrayList<String>();
for (String key : data.keySet()) {
Integer value = data.get(data);
result.add(String.format("[\"%s\", %d]", key, value));
}
return String.format("'[%s]'", StringUtils.join(result, ','));
}
public static String keys(Map<String, ?> map) {
List<String> result = new ArrayList<String>();
for (String each : map.keySet()) {
result.add(String.format("\"%s\"", each));
}
return String.format("'[%s]'", StringUtils.join(result, ','));
}
public static String values(Map<?, Integer> map) {
List<String> result = new ArrayList<String>();
for (Integer each : map.values()) {
result.add(each.toString());
}
return String.format("'[%s]'", StringUtils.join(result, ','));
}
}
}

View file

@ -22,10 +22,13 @@ package org.libreplan.web.dashboard;
import java.math.BigDecimal;
import java.math.MathContext;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.joda.time.Days;
import org.joda.time.LocalDate;
@ -45,7 +48,7 @@ import org.springframework.stereotype.Component;
/**
* Model for UI operations related to Order Dashboard View
*
*
* @author Nacho Barrientos <nacho@igalia.com>
* @author Lorenzo Tilve Álvaro <ltilve@igalia.com>
*/
@ -64,12 +67,11 @@ public class DashboardModel implements IDashboardModel {
public static double LTC_STRETCHES_MIN_VALUE = 0;
public static double LTC_STRETCHES_MAX_VALUE = 0;
private Order currentOrder;
private Integer taskCount = null;
private Map<TaskStatusEnum, BigDecimal> taskStatusStats;
private Map<TaskDeadlineViolationStatusEnum, BigDecimal> taskDeadlineViolationStatusStats;
private final Map<TaskStatusEnum, BigDecimal> taskStatusStats;
private final Map<TaskDeadlineViolationStatusEnum, BigDecimal> taskDeadlineViolationStatusStats;
private List<Double> taskEstimationAccuracyHistogram;
private BigDecimal marginWithDeadLine;
private List<Double> lagInTaskCompletionHistogram;
@ -81,10 +83,11 @@ public class DashboardModel implements IDashboardModel {
TaskDeadlineViolationStatusEnum.class);
}
@Override
public void setCurrentOrder(Order order) {
this.currentOrder = order;
this.taskCount = null;
if(tasksAvailable()) {
if (tasksAvailable()) {
this.calculateTaskStatusStatistics();
this.calculateTaskViolationStatusStatistics();
this.calculateMarginWithDeadLine();
@ -94,38 +97,49 @@ public class DashboardModel implements IDashboardModel {
}
/* Progress KPI: "Number of tasks by status" */
@Override
public BigDecimal getPercentageOfFinishedTasks() {
return taskStatusStats.get(TaskStatusEnum.FINISHED);
}
@Override
public BigDecimal getPercentageOfInProgressTasks() {
return taskStatusStats.get(TaskStatusEnum.IN_PROGRESS);
}
@Override
public BigDecimal getPercentageOfReadyToStartTasks() {
return taskStatusStats.get(TaskStatusEnum.READY_TO_START);
}
@Override
public BigDecimal getPercentageOfBlockedTasks() {
return taskStatusStats.get(TaskStatusEnum.BLOCKED);
}
/* Progress KPI: "Deadline violation" */
@Override
public BigDecimal getPercentageOfOnScheduleTasks() {
return taskDeadlineViolationStatusStats.get(TaskDeadlineViolationStatusEnum.ON_SCHEDULE);
return taskDeadlineViolationStatusStats
.get(TaskDeadlineViolationStatusEnum.ON_SCHEDULE);
}
@Override
public BigDecimal getPercentageOfTasksWithViolatedDeadline() {
return taskDeadlineViolationStatusStats.get(TaskDeadlineViolationStatusEnum.DEADLINE_VIOLATED);
return taskDeadlineViolationStatusStats
.get(TaskDeadlineViolationStatusEnum.DEADLINE_VIOLATED);
}
@Override
public BigDecimal getPercentageOfTasksWithNoDeadline() {
return taskDeadlineViolationStatusStats.get(TaskDeadlineViolationStatusEnum.NO_DEADLINE);
return taskDeadlineViolationStatusStats
.get(TaskDeadlineViolationStatusEnum.NO_DEADLINE);
}
/* Progress KPI: "Global Progress of the Project" */
public BigDecimal getAdvancePercentageByHours(){
TaskGroup rootAsTaskGroup = (TaskGroup)getRootTask();
@Override
public BigDecimal getAdvancePercentageByHours() {
TaskGroup rootAsTaskGroup = (TaskGroup) getRootTask();
if (this.getRootTask() == null) {
throw new RuntimeException("Root task is null");
}
@ -133,17 +147,20 @@ public class DashboardModel implements IDashboardModel {
return ratio.multiply(BigDecimal.TEN).multiply(BigDecimal.TEN);
}
public BigDecimal getExpectedAdvancePercentageByHours(){
TaskGroup rootAsTaskGroup = (TaskGroup)getRootTask();
@Override
public BigDecimal getExpectedAdvancePercentageByHours() {
TaskGroup rootAsTaskGroup = (TaskGroup) getRootTask();
if (this.getRootTask() == null) {
throw new RuntimeException("Root task is null");
}
BigDecimal ratio = rootAsTaskGroup.getTheoreticalProgressByNumHoursForAllTasksUntilNow();
BigDecimal ratio = rootAsTaskGroup
.getTheoreticalProgressByNumHoursForAllTasksUntilNow();
return ratio.multiply(BigDecimal.TEN).multiply(BigDecimal.TEN);
}
@Override
public BigDecimal getCriticalPathProgressByNumHours() {
TaskGroup rootAsTaskGroup = (TaskGroup)getRootTask();
TaskGroup rootAsTaskGroup = (TaskGroup) getRootTask();
if (this.getRootTask() == null) {
throw new RuntimeException("Root task is null");
}
@ -151,17 +168,20 @@ public class DashboardModel implements IDashboardModel {
return ratio.multiply(BigDecimal.TEN).multiply(BigDecimal.TEN);
}
@Override
public BigDecimal getExpectedCriticalPathProgressByNumHours() {
TaskGroup rootAsTaskGroup = (TaskGroup)getRootTask();
TaskGroup rootAsTaskGroup = (TaskGroup) getRootTask();
if (this.getRootTask() == null) {
throw new RuntimeException("Root task is null");
}
BigDecimal ratio = rootAsTaskGroup.getTheoreticalProgressByNumHoursForCriticalPathUntilNow();
BigDecimal ratio = rootAsTaskGroup
.getTheoreticalProgressByNumHoursForCriticalPathUntilNow();
return ratio.multiply(BigDecimal.TEN).multiply(BigDecimal.TEN);
}
@Override
public BigDecimal getCriticalPathProgressByDuration() {
TaskGroup rootAsTaskGroup = (TaskGroup)getRootTask();
TaskGroup rootAsTaskGroup = (TaskGroup) getRootTask();
if (this.getRootTask() == null) {
throw new RuntimeException("Root task is null");
}
@ -169,16 +189,19 @@ public class DashboardModel implements IDashboardModel {
return ratio.multiply(BigDecimal.TEN).multiply(BigDecimal.TEN);
}
@Override
public BigDecimal getExpectedCriticalPathProgressByDuration() {
TaskGroup rootAsTaskGroup = (TaskGroup)getRootTask();
TaskGroup rootAsTaskGroup = (TaskGroup) getRootTask();
if (this.getRootTask() == null) {
throw new RuntimeException("Root task is null");
}
BigDecimal ratio = rootAsTaskGroup.getTheoreticalProgressByDurationForCriticalPathUntilNow();
BigDecimal ratio = rootAsTaskGroup
.getTheoreticalProgressByDurationForCriticalPathUntilNow();
return ratio.multiply(BigDecimal.TEN).multiply(BigDecimal.TEN);
}
/* Time KPI: Margin with deadline */
@Override
public BigDecimal getMarginWithDeadLine() {
return this.marginWithDeadLine;
}
@ -208,6 +231,7 @@ public class DashboardModel implements IDashboardModel {
}
/* Time KPI: Estimation accuracy */
@Override
public List<Double> getFinishedTasksEstimationAccuracyHistogram() {
return this.taskEstimationAccuracyHistogram;
}
@ -216,21 +240,19 @@ public class DashboardModel implements IDashboardModel {
if (this.getRootTask() == null) {
throw new RuntimeException("Root task is null");
}
CalculateFinishedTasksEstimationDeviationVisitor visitor =
new CalculateFinishedTasksEstimationDeviationVisitor();
CalculateFinishedTasksEstimationDeviationVisitor visitor = new CalculateFinishedTasksEstimationDeviationVisitor();
TaskElement rootTask = getRootTask();
rootTask.acceptVisitor(visitor);
List<Double> deviations = visitor.getDeviations();
// [-100, -90), [-90, -80), ..., [190, 200), [200, inf)
this.taskEstimationAccuracyHistogram = createHistogram(
EA_STRETCHES_MIN_VALUE,
EA_STRETCHES_MAX_VALUE,
EA_STRETCHES_PERCENTAGE_STEP,
deviations);
EA_STRETCHES_MIN_VALUE, EA_STRETCHES_MAX_VALUE,
EA_STRETCHES_PERCENTAGE_STEP, deviations);
}
/* Time KPI: Lead/Lag in task completion */
@Override
public List<Double> getLagInTaskCompletionHistogram() {
return this.lagInTaskCompletionHistogram;
}
@ -239,8 +261,7 @@ public class DashboardModel implements IDashboardModel {
if (this.getRootTask() == null) {
throw new RuntimeException("Root task is null");
}
CalculateFinishedTasksLagInCompletionVisitor visitor =
new CalculateFinishedTasksLagInCompletionVisitor();
CalculateFinishedTasksLagInCompletionVisitor visitor = new CalculateFinishedTasksLagInCompletionVisitor();
TaskElement rootTask = getRootTask();
rootTask.acceptVisitor(visitor);
List<Double> deviations = visitor.getDeviations();
@ -253,45 +274,137 @@ public class DashboardModel implements IDashboardModel {
LTC_STRETCHES_MAX_VALUE = Collections.max(deviations);
}
LTC_STRETCHES_STEP = (LTC_STRETCHES_MAX_VALUE - LTC_STRETCHES_MIN_VALUE)
/LTC_NUMBER_OF_INTERVALS;
/ LTC_NUMBER_OF_INTERVALS;
this.lagInTaskCompletionHistogram = createHistogram(
LTC_STRETCHES_MIN_VALUE,
LTC_STRETCHES_MAX_VALUE,
LTC_STRETCHES_STEP,
deviations);
LTC_STRETCHES_MIN_VALUE, LTC_STRETCHES_MAX_VALUE,
LTC_STRETCHES_STEP, deviations);
}
private List<Double> createHistogram(double lowBound, double highBound,
double intervalStep, List<Double> values) {
double variableRange = highBound - lowBound;
/* TODO: What if highBound == lowBound? */
int numberOfClasses = (int)(variableRange/intervalStep);
int[] classes = new int[numberOfClasses+1];
int numberOfClasses = (int) (variableRange / intervalStep);
int[] classes = new int[numberOfClasses + 1];
for(Double value: values) {
for (Double value : values) {
int index;
if (value >= highBound) {
index = numberOfClasses;
} else {
index = (int)(numberOfClasses *
(((value.doubleValue() - lowBound))/variableRange));
index = (int) (numberOfClasses * (((value.doubleValue() - lowBound)) / variableRange));
}
classes[index]++;
}
List<Double> histogram = new ArrayList<Double>();
int numberOfConsideredTasks = values.size();
for (int numberOfElementsInClass: classes) {
for (int numberOfElementsInClass : classes) {
Double relativeCount = new Double(0.0);
if (numberOfConsideredTasks > 0) {
relativeCount = new Double(1.0*numberOfElementsInClass/
numberOfConsideredTasks);
relativeCount = new Double(1.0 * numberOfElementsInClass
/ numberOfConsideredTasks);
}
histogram.add(relativeCount);
}
return histogram;
}
@Override
public Map<Interval, Integer> calculateTaskCompletation() {
Map<Interval, Integer> result = new LinkedHashMap<Interval, Integer>();
final Integer one = Integer.valueOf(1);
// Get deviations of finished tasks, calculate max, min and delta
List<Double> deviations = getDeviations();
if (deviations.isEmpty()) {
return result;
}
Double max = Collections.max(deviations);
Double min = Collections.min(deviations);
double delta = (max - min) / Interval.MAX_INTERVALS;
// Create MAX_INTERVALS
double from = min;
for (int i = 0; i < Interval.MAX_INTERVALS; i++) {
result.put(Interval.create(from, from + delta), Integer.valueOf(0));
from = from + delta;
}
// Construct map with number of tasks for each interval
final Set<Interval> intervals = result.keySet();
for (Double each : deviations) {
Interval interval = Interval.containingValue(intervals, each);
if (interval != null) {
Integer value = result.get(interval);
result.put(interval, value + one);
}
}
return result;
}
/**
*
* @author Diego Pino García<dpino@igalia.com>
*
*/
static class Interval {
public static final double MAX_INTERVALS = 6;
private double min;
private double max;
private Interval() {
}
public static Interval create(double min, double max) {
return new Interval(min, max);
}
private Interval(double min, double max) {
this.min = min;
this.max = max;
}
public static Interval copy(Interval interval) {
return new Interval(interval.min, interval.max);
}
public static Interval containingValue(Collection<Interval> intervals,
Double value) {
for (Interval each : intervals) {
if (each.includes(value)) {
return each;
}
}
return null;
}
private boolean includes(double value) {
return (value >= min) && (value <= max);
}
@Override
public String toString() {
return String.format("[%.2f, %.2f]", min, max);
}
}
private List<Double> getDeviations() {
if (this.getRootTask() == null) {
throw new RuntimeException("Root task is null");
}
CalculateFinishedTasksEstimationDeviationVisitor visitor = new CalculateFinishedTasksEstimationDeviationVisitor();
TaskElement rootTask = getRootTask();
rootTask.acceptVisitor(visitor);
return visitor.getDeviations();
}
@Override
public Map<TaskStatusEnum, Integer> calculateTaskStatus() {
AccumulateTasksStatusVisitor visitor = new AccumulateTasksStatusVisitor();
TaskElement rootTask = getRootTask();
@ -322,7 +435,8 @@ public class DashboardModel implements IDashboardModel {
throw new RuntimeException("Root task is null");
}
rootTask.acceptVisitor(visitor);
Map<TaskDeadlineViolationStatusEnum, Integer> count = visitor.getTaskDeadlineViolationStatusData();
Map<TaskDeadlineViolationStatusEnum, Integer> count = visitor
.getTaskDeadlineViolationStatusData();
mapAbsoluteValuesToPercentages(count, taskDeadlineViolationStatusStats);
}
@ -353,8 +467,9 @@ public class DashboardModel implements IDashboardModel {
}
private int countTasksInAResultMap(Map<? extends Object, Integer> map) {
/* It's only needed to count the number of tasks once
* each time setOrder is called.
/*
* It's only needed to count the number of tasks once each time setOrder
* is called.
*/
if (this.taskCount != null) {
return this.taskCount.intValue();
@ -367,6 +482,7 @@ public class DashboardModel implements IDashboardModel {
return sum;
}
@Override
public boolean tasksAvailable() {
return getRootTask() != null;
}

View file

@ -25,6 +25,7 @@ import java.util.Map;
import org.libreplan.business.orders.entities.Order;
import org.libreplan.business.planner.entities.TaskStatusEnum;
import org.libreplan.web.dashboard.DashboardModel.Interval;
interface IDashboardModel {
@ -69,7 +70,9 @@ interface IDashboardModel {
/* Time KPI: "Lead/Lag in task completion" */
List<Double> getLagInTaskCompletionHistogram();
Map<TaskStatusEnum, Integer> calculateTaskStatus();
Map<Interval, Integer> calculateTaskCompletation();
}

View file

@ -34,9 +34,11 @@
<div height="30px" sclass="toolbar-box" />
<window id="dashboardWindow" apply="${dsController}" vflex="1" style="margin:0;overflow:auto">
<window id="dashboardWindow" apply="${dsController}" vflex="1" contentStyle="margin: 0; overflow: auto">
<div id="projectDashboardChartsDiv" sclass="dashboards-container" height="100%">
<div id="projectDashboardChartsDiv" sclass="dashboards-container" height="100%" width="100%">
<!-- Progress -->
<groupbox closable="false">
<caption label="${i18n:_('Progress')}" />
<hbox>
@ -44,7 +46,7 @@
<n:div id="task-status" style="height:200px; width:400px; margin-left: 100px;"></n:div>
<!-- Tasks summary -->
<grid id="gridTasksSummary" style="margin-top: 50px" visible="false">
<grid id="gridTasksSummary" style="margin-top: 50px;" width="300px">
<auxhead>
<auxheader label="${i18n:_('Tasks summary')}" colspan="2"/>
</auxhead>
@ -72,7 +74,18 @@
</rows>
</grid>
</hbox>
</groupbox>
<!-- Time -->
<groupbox closable="false">
<caption label="${i18n:_('Time')}" />
<hbox>
<n:div id="task-completation-lag" style="height:200px; width:560px;"></n:div>
<n:div id="deadline-violation" style="height:200px; width:400px; margin-left: 100px;"></n:div>
<n:div id="margin-with-deadline" style="height:200px; width:400px; margin-left: 100px;"></n:div>
</hbox>
</groupbox>
</div>
<div id="projectDashboardNoTasksWarningDiv" visible="false">
@ -87,12 +100,24 @@
<!-- The variable for containing the 'global progress' has to be global and created before defer -->
<script type="text/javascript">
var global_progress = {
title: 'Titulo',
};
var global_progress = { };
var task_status = { };
var task_completation_lag = { };
</script>
<n:style type="text/css">
.tooltip {
display:none;
position:absolute;
border:1px solid #333;
background-color:#161616;
border-radius:5px;
padding:10px;
color:#fff;
font-size:12px Arial;
}
</n:style>
<!-- Configure the parameters for the 'global progress' chart. The object contains a method 'render' that
is called from the Java file once all objects in the view have been created -->
<script type="text/javascript" defer="true">
@ -161,6 +186,66 @@
this.plot = jQuery.jqplot('task-status', [jQuery.parseJSON(data)], this);
}
};
task_completation_lag = {
id: 'task-completation-lag',
title: 'Task completation lag',
data: [],
seriesDefaults:{
renderer:$.jqplot.BarRenderer,
rendererOptions: {
fillToZero: true
}
},
axesDefaults: {
tickRenderer: $.jqplot.CanvasAxisTickRenderer ,
tickOptions: {
angle: -30,
fontSize: '10pt'
}
},
axes: {
xaxis: {
label: 'Number of Days / Days Interval',
renderer: $.jqplot.CategoryAxisRenderer,
},
},
render: function(data, conf) {
if (conf.ticks !== undefined) {
this.axes.xaxis.ticks = jQuery.parseJSON(conf.ticks);
}
if (conf.titles !== undefined) {
this.axes.xaxis.ticks = jQuery.parseJSON(conf.ticks);
}
this.plot = $.jqplot(this.id, [jQuery.parseJSON(data)], this);
this.attachTooltip();
},
attachTooltip: function() {
var node = $('#' + this.id);
node.bind('jqplotDataHighlight',
function (ev, seriesIndex, pointIndex, data) {
var x = ev.pageX - node.offset().left;
var y = ev.pageY - node.offset().top;
var tooltip = $('<span class="tooltip"></span>');
tooltip.text(data[1]).appendTo(node);
tooltip.css({'top': y, 'left': x, 'position': 'absolute'});
tooltip.fadeIn('slow');
}
);
node.bind('jqplotDataUnhighlight',
function (ev) {
$('.tooltip').remove();
}
);
node.mousemove(function(ev) {
var x = ev.pageX - node.offset().left;
var y = ev.pageY - node.offset().top;
$('.tooltip').css({'top': y, 'left': x, 'position': 'absolute'})
});
}
};
]]>
</script>