From 8b861476428cb2c7029195acd1783155e986196b Mon Sep 17 00:00:00 2001 From: Diego Pino Date: Thu, 26 Apr 2012 16:45:36 +0200 Subject: [PATCH] Recode 'Task Completation Lead/Lag' using jqPlot --- .../web/dashboard/DashboardController.java | 471 ++++++++++-------- .../web/dashboard/DashboardModel.java | 204 ++++++-- .../web/dashboard/IDashboardModel.java | 7 +- .../webapp/dashboard/_dashboardfororder.zul | 97 +++- 4 files changed, 529 insertions(+), 250 deletions(-) diff --git a/libreplan-webapp/src/main/java/org/libreplan/web/dashboard/DashboardController.java b/libreplan-webapp/src/main/java/org/libreplan/web/dashboard/DashboardController.java index 393599732..352dfadd0 100644 --- a/libreplan-webapp/src/main/java/org/libreplan/web/dashboard/DashboardController.java +++ b/libreplan-webapp/src/main/java/org/libreplan/web/dashboard/DashboardController.java @@ -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 * @author Diego Pino García @@ -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 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 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 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 - * + * */ - 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 current = new LinkedHashMap(); + private final Map current = new LinkedHashMap(); - private Map expected = new LinkedHashMap(); + private final Map expected = new LinkedHashMap(); - private static List series = new ArrayList() { - { - add(Series.create(_("Current"), "blue")); - add(Series.create(_("Expected"), "red")); - } - }; + private static List series = new ArrayList() { + { + 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 array) { - List result = new ArrayList(); + private String jsonifyPercentages(Collection array) { + List result = new ArrayList(); - 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 result = new ArrayList(); - 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 result = new ArrayList(); + 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 - * + * */ 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 - * + * */ static class TaskStatus { - - private Map data = new LinkedHashMap(); - - private TaskStatus() { - - } - - public static TaskStatus create() { - return new TaskStatus(); - } - - private String getData() { - List result = new ArrayList(); - - TreeSet keys = new TreeSet(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 data = new LinkedHashMap(); + + private TaskStatus() { + + } + + public static TaskStatus create() { + return new TaskStatus(); + } + + private String getData() { + List result = new ArrayList(); + + TreeSet keys = new TreeSet(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 data = new LinkedHashMap(); + + 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 data) { + List result = new ArrayList(); + 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 map) { + List result = new ArrayList(); + for (String each : map.keySet()) { + result.add(String.format("\"%s\"", each)); + } + return String.format("'[%s]'", StringUtils.join(result, ',')); + } + + public static String values(Map map) { + List result = new ArrayList(); + for (Integer each : map.values()) { + result.add(each.toString()); + } + return String.format("'[%s]'", StringUtils.join(result, ',')); + } + + } + } \ No newline at end of file diff --git a/libreplan-webapp/src/main/java/org/libreplan/web/dashboard/DashboardModel.java b/libreplan-webapp/src/main/java/org/libreplan/web/dashboard/DashboardModel.java index 552bcf180..603c88f87 100644 --- a/libreplan-webapp/src/main/java/org/libreplan/web/dashboard/DashboardModel.java +++ b/libreplan-webapp/src/main/java/org/libreplan/web/dashboard/DashboardModel.java @@ -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 * @author Lorenzo Tilve Álvaro */ @@ -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 taskStatusStats; - private Map taskDeadlineViolationStatusStats; + private final Map taskStatusStats; + private final Map taskDeadlineViolationStatusStats; private List taskEstimationAccuracyHistogram; private BigDecimal marginWithDeadLine; private List 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 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 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 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 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 createHistogram(double lowBound, double highBound, double intervalStep, List 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 histogram = new ArrayList(); 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 calculateTaskCompletation() { + Map result = new LinkedHashMap(); + final Integer one = Integer.valueOf(1); + + // Get deviations of finished tasks, calculate max, min and delta + List 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 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 + * + */ + 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 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 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 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 count = visitor.getTaskDeadlineViolationStatusData(); + Map count = visitor + .getTaskDeadlineViolationStatusData(); mapAbsoluteValuesToPercentages(count, taskDeadlineViolationStatusStats); } @@ -353,8 +467,9 @@ public class DashboardModel implements IDashboardModel { } private int countTasksInAResultMap(Map 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; } diff --git a/libreplan-webapp/src/main/java/org/libreplan/web/dashboard/IDashboardModel.java b/libreplan-webapp/src/main/java/org/libreplan/web/dashboard/IDashboardModel.java index d37899688..f6649f881 100644 --- a/libreplan-webapp/src/main/java/org/libreplan/web/dashboard/IDashboardModel.java +++ b/libreplan-webapp/src/main/java/org/libreplan/web/dashboard/IDashboardModel.java @@ -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 getLagInTaskCompletionHistogram(); - + Map calculateTaskStatus(); - + + Map calculateTaskCompletation(); + } diff --git a/libreplan-webapp/src/main/webapp/dashboard/_dashboardfororder.zul b/libreplan-webapp/src/main/webapp/dashboard/_dashboardfororder.zul index 8fb93554a..b28165bfc 100644 --- a/libreplan-webapp/src/main/webapp/dashboard/_dashboardfororder.zul +++ b/libreplan-webapp/src/main/webapp/dashboard/_dashboardfororder.zul @@ -34,9 +34,11 @@
- + -
+
+ + @@ -44,7 +46,7 @@ - + @@ -72,7 +74,18 @@ + + + + + + + + + + +
@@ -87,12 +100,24 @@ + + .tooltip { + display:none; + position:absolute; + border:1px solid #333; + background-color:#161616; + border-radius:5px; + padding:10px; + color:#fff; + font-size:12px Arial; + } + +