Muhammad Sulaiman Yusuf
2 years ago
8 changed files with 430 additions and 1376 deletions
@ -0,0 +1,284 @@ |
|||||||
|
var overlayControl = gantt.ext.overlay; |
||||||
|
var today = new Date(); |
||||||
|
|
||||||
|
function toggleOverlay() { |
||||||
|
if(overlayControl.isOverlayVisible(lineOverlay)){ |
||||||
|
gantt.config.readonly = false; |
||||||
|
overlayControl.hideOverlay(lineOverlay); |
||||||
|
gantt.$root.classList.remove("overlay_visible"); |
||||||
|
}else{ |
||||||
|
gantt.config.readonly = true; |
||||||
|
overlayControl.showOverlay(lineOverlay); |
||||||
|
gantt.$root.classList.add("overlay_visible"); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function getChartScaleRange(){ |
||||||
|
var tasksRange = gantt.getSubtaskDates(); |
||||||
|
var cells = []; |
||||||
|
var scale = gantt.getScale(); |
||||||
|
if(!tasksRange.start_date){ |
||||||
|
return scale.trace_x; |
||||||
|
} |
||||||
|
|
||||||
|
scale.trace_x.forEach(function(date){ |
||||||
|
if(date >= tasksRange.start_date && date <= tasksRange.end_date){ |
||||||
|
cells.push(date); |
||||||
|
} |
||||||
|
}); |
||||||
|
return cells; |
||||||
|
} |
||||||
|
|
||||||
|
function getProgressLine(){ |
||||||
|
var tasks = gantt.getTaskByTime(); |
||||||
|
var scale = gantt.getScale(); |
||||||
|
var step = scale.unit; |
||||||
|
|
||||||
|
var timegrid = {}; |
||||||
|
|
||||||
|
var totalDuration = 0; |
||||||
|
|
||||||
|
gantt.eachTask(function(task){ |
||||||
|
if(gantt.isSummaryTask(task)){ |
||||||
|
return; |
||||||
|
} |
||||||
|
if(!task.duration){ |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
var currDate = gantt.date[scale.unit + "_start"](new Date(task.start_date)); |
||||||
|
|
||||||
|
while (currDate < task.end_date) { |
||||||
|
var date = currDate; |
||||||
|
currDate = gantt.date.add(currDate, 1, step); |
||||||
|
|
||||||
|
if (!gantt.isWorkTime({date: date, task: task, unit: step})) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
|
||||||
|
var timestamp = currDate.valueOf(); |
||||||
|
if (!timegrid[timestamp]){ |
||||||
|
timegrid[timestamp] = { |
||||||
|
planned: 0, |
||||||
|
real: 0 |
||||||
|
}; |
||||||
|
} |
||||||
|
timegrid[timestamp].planned += 1; |
||||||
|
if (date <= today){ |
||||||
|
timegrid[timestamp].real += 1 * (task.progress || 0); |
||||||
|
} |
||||||
|
totalDuration += 1; |
||||||
|
} |
||||||
|
|
||||||
|
}); |
||||||
|
|
||||||
|
var cumulativePlannedDurations = []; |
||||||
|
var cumulativeRealDurations = []; |
||||||
|
var cumulativePredictedDurations = [] |
||||||
|
var totalPlanned = 0; |
||||||
|
var totalReal = 0; |
||||||
|
|
||||||
|
var chartScale = getChartScaleRange(); |
||||||
|
var dailyRealProgress = -1; |
||||||
|
var totalPredictedProgress = 0; |
||||||
|
for(var i = 0; i < chartScale.length; i++){ |
||||||
|
start = new Date(chartScale[i]); |
||||||
|
end = gantt.date.add(start, 1, step); |
||||||
|
var cell = timegrid[start.valueOf()] || {planned:0, real:0}; |
||||||
|
totalPlanned = cell.planned + totalPlanned; |
||||||
|
cumulativePlannedDurations.push(totalPlanned); |
||||||
|
|
||||||
|
if(start <= today) { |
||||||
|
totalReal = (cell.real||0) + totalReal; |
||||||
|
cumulativeRealDurations.push(totalReal); |
||||||
|
cumulativePredictedDurations.push(null); |
||||||
|
} else { |
||||||
|
if(dailyRealProgress < 0){ |
||||||
|
dailyRealProgress = totalReal / cumulativeRealDurations.length; |
||||||
|
totalPredictedProgress = totalReal; |
||||||
|
cumulativePredictedDurations.pop(); |
||||||
|
cumulativePredictedDurations.push(totalPredictedProgress); |
||||||
|
} |
||||||
|
totalPredictedProgress += dailyRealProgress; |
||||||
|
cumulativePredictedDurations.push(totalPredictedProgress); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
for(var i = 0; i < cumulativePlannedDurations.length; i++){ |
||||||
|
cumulativePlannedDurations[i] = Math.round(cumulativePlannedDurations[i] / totalPlanned * 100); |
||||||
|
if(cumulativeRealDurations[i] !== undefined){ |
||||||
|
cumulativeRealDurations[i] = Math.round(cumulativeRealDurations[i] / totalPlanned * 100); |
||||||
|
} |
||||||
|
|
||||||
|
if(cumulativePredictedDurations[i] !== null){ |
||||||
|
cumulativePredictedDurations[i] = Math.round(cumulativePredictedDurations[i] / totalPlanned * 100); |
||||||
|
} |
||||||
|
} |
||||||
|
return {planned: cumulativePlannedDurations, real: cumulativeRealDurations, predicted: cumulativePredictedDurations}; |
||||||
|
} |
||||||
|
|
||||||
|
function getScalePaddings(){ |
||||||
|
var scale = gantt.getScale(); |
||||||
|
var dataRange = gantt.getSubtaskDates(); |
||||||
|
|
||||||
|
var chartScale = getChartScaleRange(); |
||||||
|
var newWidth = scale.col_width; |
||||||
|
var padding = { |
||||||
|
left:0, |
||||||
|
right:0 |
||||||
|
}; |
||||||
|
|
||||||
|
if(dataRange.start_date){ |
||||||
|
var yScaleLabelsWidth = 48; |
||||||
|
// fine tune values in order to align chart with the scale range
|
||||||
|
padding.left = gantt.posFromDate(dataRange.start_date) - yScaleLabelsWidth; |
||||||
|
padding.right = scale.full_width - gantt.posFromDate(dataRange.end_date) - yScaleLabelsWidth; |
||||||
|
padding.top = gantt.config.row_height - 12; |
||||||
|
padding.bottom = gantt.config.row_height - 12; |
||||||
|
} |
||||||
|
return padding; |
||||||
|
} |
||||||
|
|
||||||
|
var myChart; |
||||||
|
var lineOverlay = overlayControl.addOverlay(function(container) { |
||||||
|
|
||||||
|
var scaleLabels = []; |
||||||
|
|
||||||
|
var chartScale = getChartScaleRange(); |
||||||
|
|
||||||
|
chartScale.forEach(function(date){ |
||||||
|
scaleLabels.push(dateToStr(date)); |
||||||
|
}); |
||||||
|
|
||||||
|
var values = getProgressLine(); |
||||||
|
|
||||||
|
var canvas = document.createElement("canvas"); |
||||||
|
container.appendChild(canvas); |
||||||
|
canvas.style.height = container.offsetHeight + "px"; |
||||||
|
canvas.style.width = container.offsetWidth + "px"; |
||||||
|
|
||||||
|
var ctx = canvas.getContext("2d"); |
||||||
|
if(myChart){ |
||||||
|
myChart.destroy(); |
||||||
|
} |
||||||
|
myChart = new Chart(ctx, { |
||||||
|
type: "line", |
||||||
|
data: { |
||||||
|
datasets: [ |
||||||
|
{ |
||||||
|
label: "Planned progress", |
||||||
|
backgroundColor: "#001eff", |
||||||
|
borderColor: "#001eff", |
||||||
|
data: values.planned, |
||||||
|
fill: false, |
||||||
|
cubicInterpolationMode: 'monotone' |
||||||
|
}, |
||||||
|
{ |
||||||
|
label: "Real progress", |
||||||
|
backgroundColor: "#ff5454", |
||||||
|
borderColor: "#ff5454", |
||||||
|
data: values.real, |
||||||
|
fill: false, |
||||||
|
cubicInterpolationMode: 'monotone' |
||||||
|
} |
||||||
|
, |
||||||
|
{ |
||||||
|
label: "Real progress (predicted)", |
||||||
|
backgroundColor: "#ff5454", |
||||||
|
borderColor: "#ff5454", |
||||||
|
data: values.predicted, |
||||||
|
borderDash: [5, 10], |
||||||
|
fill: false, |
||||||
|
cubicInterpolationMode: 'monotone' |
||||||
|
} |
||||||
|
] |
||||||
|
}, |
||||||
|
options: { |
||||||
|
responsive: true, |
||||||
|
maintainAspectRatio: false, |
||||||
|
layout: { |
||||||
|
padding: getScalePaddings() |
||||||
|
}, |
||||||
|
onResize: function(chart, newSize) { |
||||||
|
var dataRange = gantt.getSubtaskDates(); |
||||||
|
if(dataRange.start_date){ |
||||||
|
// align chart with the scale range
|
||||||
|
chart.options.layout.padding = getScalePaddings(); |
||||||
|
} |
||||||
|
}, |
||||||
|
legend: { |
||||||
|
display: false |
||||||
|
}, |
||||||
|
tooltips: { |
||||||
|
mode: "index", |
||||||
|
intersect: false, |
||||||
|
callbacks: { |
||||||
|
label: function(tooltipItem, data) { |
||||||
|
var dataset = data.datasets[tooltipItem.datasetIndex]; |
||||||
|
return dataset.label + ": " + dataset.data[tooltipItem.index] + "%"; |
||||||
|
} |
||||||
|
} |
||||||
|
}, |
||||||
|
hover: { |
||||||
|
mode: "nearest", |
||||||
|
intersect: true |
||||||
|
}, |
||||||
|
scales: { |
||||||
|
xAxes: [{ |
||||||
|
labels: scaleLabels, |
||||||
|
gridLines:{ |
||||||
|
display: false |
||||||
|
}, |
||||||
|
ticks: { |
||||||
|
display: false |
||||||
|
} |
||||||
|
}, |
||||||
|
{ |
||||||
|
position:"top", |
||||||
|
labels: scaleLabels, |
||||||
|
gridLines:{ |
||||||
|
display: false |
||||||
|
}, |
||||||
|
ticks: { |
||||||
|
display: false |
||||||
|
} |
||||||
|
} |
||||||
|
], |
||||||
|
yAxes: [{ |
||||||
|
display: true, |
||||||
|
gridLines: { |
||||||
|
display:false |
||||||
|
}, |
||||||
|
ticks: { |
||||||
|
display: true, |
||||||
|
min: 0, |
||||||
|
max: 100, |
||||||
|
stepSize: 10, |
||||||
|
callback: function(current) { |
||||||
|
if (current > 100) {return "";} |
||||||
|
return current + "%"; |
||||||
|
} |
||||||
|
} |
||||||
|
}, |
||||||
|
{ |
||||||
|
display: true, |
||||||
|
position: "right", |
||||||
|
gridLines: { |
||||||
|
display:false |
||||||
|
}, |
||||||
|
ticks: { |
||||||
|
display: true, |
||||||
|
min: 0, |
||||||
|
max: 100, |
||||||
|
stepSize: 10, |
||||||
|
callback: function(current) { |
||||||
|
if (current > 100) {return "";} |
||||||
|
return current + "%"; |
||||||
|
} |
||||||
|
}} |
||||||
|
] |
||||||
|
} |
||||||
|
} |
||||||
|
}); |
||||||
|
return canvas; |
||||||
|
}); |
Loading…
Reference in new issue