|
|
|
var overlayControl = gantt.ext.overlay;
|
|
|
|
var today = new Date();
|
|
|
|
var return_first;
|
|
|
|
var parts = [];
|
|
|
|
|
|
|
|
var sCurvePayload = {
|
|
|
|
period: 'week',
|
|
|
|
project_id: proyekId,
|
|
|
|
gantt_id: ganttId
|
|
|
|
};
|
|
|
|
|
|
|
|
function callback(response) {
|
|
|
|
return_first = response;
|
|
|
|
}
|
|
|
|
|
|
|
|
function messageListener(event) {
|
|
|
|
if (event.data && event.data.action === 'sendUrl') {
|
|
|
|
const parentUrl = event.data.url;
|
|
|
|
const isHierarchy = event.data.isHierarchy;
|
|
|
|
|
|
|
|
// Split the remaining path by "/"
|
|
|
|
parts = parentUrl.split("/");
|
|
|
|
if (parts[7] && parts[7] == "1" && isHierarchy) {
|
|
|
|
$.ajax({
|
|
|
|
headers: {
|
|
|
|
"Content-Type": "application/json",
|
|
|
|
"Authorization": `Bearer ${token}`
|
|
|
|
},
|
|
|
|
url: `${base_url}project/calculate-s-curve`,
|
|
|
|
type: "POST",
|
|
|
|
data: JSON.stringify(sCurvePayload),
|
|
|
|
success: function (data) {
|
|
|
|
callback(data)
|
|
|
|
window.removeEventListener('message', messageListener);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
$.ajax({
|
|
|
|
headers: {
|
|
|
|
"Content-Type": "application/json",
|
|
|
|
"Authorization": `Bearer ${token}`
|
|
|
|
},
|
|
|
|
url: `${base_url}project/s-curve-command`,
|
|
|
|
type: "POST",
|
|
|
|
data: JSON.stringify(sCurvePayload),
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
$.ajax({
|
|
|
|
headers: {
|
|
|
|
"Content-Type": "application/json",
|
|
|
|
"Authorization": `Bearer ${token}`
|
|
|
|
},
|
|
|
|
url: `${base_url}project/get-s-curve`,
|
|
|
|
type: "POST",
|
|
|
|
data: JSON.stringify(sCurvePayload),
|
|
|
|
success: function (data) {
|
|
|
|
callback(data)
|
|
|
|
window.removeEventListener('message', messageListener);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function getSCurveData() {
|
|
|
|
window.parent.postMessage({ action: 'getUrl' }, '*');
|
|
|
|
window.addEventListener('message', messageListener);
|
|
|
|
};
|
|
|
|
|
|
|
|
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);
|
|
|
|
// }
|
|
|
|
cells.push(date);
|
|
|
|
});
|
|
|
|
return cells;
|
|
|
|
}
|
|
|
|
|
|
|
|
function getProgressLine() {
|
|
|
|
// As long as the progress data length is same with chart scale range (period) then it's fine.
|
|
|
|
getSCurveData();
|
|
|
|
var cumulativePlannedDurations = return_first.data[0].data.percentagePlan;
|
|
|
|
var cumulativeRealDurations = return_first.data[0].data.percentageReal;
|
|
|
|
cumulativeRealDurations[cumulativeRealDurations.length - 1] = Math.ceil(cumulativeRealDurations[cumulativeRealDurations.length - 1]);
|
|
|
|
var maxPlan = cumulativePlannedDurations[cumulativePlannedDurations.length - 1]
|
|
|
|
var maxReal = cumulativeRealDurations[cumulativeRealDurations.length - 1]
|
|
|
|
var dates = return_first.data[0].data.date;
|
|
|
|
// Determine the appropriate data points based on the Gantt chart zoom level
|
|
|
|
var chartScaleRange = getChartScaleRange();
|
|
|
|
var zoomLevel = gantt.ext.zoom.getCurrentLevel();
|
|
|
|
|
|
|
|
// Adjust data points based on the zoom level
|
|
|
|
var plannedDurations, realDurations;
|
|
|
|
|
|
|
|
if (zoomLevel === 2) {
|
|
|
|
// Adjust data to monthly points
|
|
|
|
plannedDurations = adjustDataToZoom(dates, cumulativePlannedDurations, chartScaleRange, 2).data;
|
|
|
|
realDurations = adjustDataToZoom(dates, cumulativeRealDurations, chartScaleRange, 2).data;
|
|
|
|
dates = adjustDataToZoom(dates, cumulativeRealDurations, chartScaleRange, 2).dates;
|
|
|
|
} else if (zoomLevel === 1) {
|
|
|
|
// Adjust data to weekly points
|
|
|
|
plannedDurations = adjustDataToZoom(dates, cumulativePlannedDurations, chartScaleRange, 1).data;
|
|
|
|
realDurations = adjustDataToZoom(dates, cumulativeRealDurations, chartScaleRange, 1).data;
|
|
|
|
dates = adjustDataToZoom(dates, cumulativeRealDurations, chartScaleRange, 1).dates;
|
|
|
|
} else {
|
|
|
|
// Default: Use daily data points
|
|
|
|
plannedDurations = cumulativePlannedDurations;
|
|
|
|
realDurations = cumulativeRealDurations;
|
|
|
|
|
|
|
|
}
|
|
|
|
if (Math.floor(maxReal) > 110 || Math.floor(maxPlan) > 110) {
|
|
|
|
plannedDurations = plannedDurations.map((item) => {
|
|
|
|
return item / maxPlan * 100;
|
|
|
|
})
|
|
|
|
realDurations = realDurations.map((item) => {
|
|
|
|
if (maxPlan >= 200) {
|
|
|
|
return item / maxPlan * 100;
|
|
|
|
} else {
|
|
|
|
return item / maxReal * 100;
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
return { planned: plannedDurations, real: realDurations, dates: dates };
|
|
|
|
}
|
|
|
|
function adjustDataToZoom(dates, data, chartScaleRange, zoomLevel) {
|
|
|
|
// Implement logic to adjust data points based on the Gantt chart zoom level
|
|
|
|
// For example, if zoomLevel is 'month', aggregate daily data to monthly data
|
|
|
|
|
|
|
|
// Placeholder logic: This example assumes that the chartScaleRange is in days
|
|
|
|
var newData = [];
|
|
|
|
var newDates = [];
|
|
|
|
var aggregateValue = 0;
|
|
|
|
|
|
|
|
for (var i = 0; i < data.length; i++) {
|
|
|
|
aggregateValue = data[i];
|
|
|
|
|
|
|
|
if (zoomLevel === 2 && i % 30 === 0) {
|
|
|
|
// Aggregate data for each month (assuming 30 days in a month)
|
|
|
|
newData.push(aggregateValue);
|
|
|
|
newDates.push(dates[i]);
|
|
|
|
aggregateValue = 0;
|
|
|
|
} else if (zoomLevel === 1 && (i + 1) % 7 === 1) {
|
|
|
|
// Aggregate data for each week
|
|
|
|
newData.push(aggregateValue);
|
|
|
|
newDates.push(dates[i]);
|
|
|
|
aggregateValue = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (aggregateValue > 0) {
|
|
|
|
newData.push(+(Math.round(aggregateValue+ "e+2") + "e-2"));
|
|
|
|
newDates.push(dates[data.length - 1]);
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
data: newData,
|
|
|
|
dates: newDates
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
function calculateMonthDifference(startDate, endDate) {
|
|
|
|
let startYear = startDate.getFullYear();
|
|
|
|
let startMonth = startDate.getMonth();
|
|
|
|
let endYear = endDate.getFullYear();
|
|
|
|
let endMonth = endDate.getMonth();
|
|
|
|
|
|
|
|
let totalMonths = (endYear - startYear) * 12 + (endMonth - startMonth);
|
|
|
|
return totalMonths;
|
|
|
|
}
|
|
|
|
|
|
|
|
function getScalePaddings(values) {
|
|
|
|
let zoom = gantt.ext.zoom.getCurrentLevel();
|
|
|
|
var scale = gantt.getScale();
|
|
|
|
var dataRange = gantt.getSubtaskDates();
|
|
|
|
let minValueDate = new Date(values.dates[0]);
|
|
|
|
let maxValueDate = new Date(values.dates[values.dates.length - 1]);
|
|
|
|
|
|
|
|
var padding = {
|
|
|
|
left: 0,
|
|
|
|
right: 0
|
|
|
|
};
|
|
|
|
|
|
|
|
if(dataRange.start_date){
|
|
|
|
var yScaleLabelsWidth = 48;
|
|
|
|
let monthDifference = calculateMonthDifference(minValueDate, maxValueDate);
|
|
|
|
if(monthDifference >= 0 && monthDifference <= 3) {
|
|
|
|
padding.left = gantt.posFromDate(minValueDate) +5;
|
|
|
|
padding.right = scale.full_width - gantt.posFromDate(maxValueDate) - yScaleLabelsWidth;
|
|
|
|
}
|
|
|
|
if(monthDifference >= 4 && monthDifference <= 7) {
|
|
|
|
padding.left = gantt.posFromDate(minValueDate) - 2;
|
|
|
|
padding.right = scale.full_width - gantt.posFromDate(maxValueDate) - yScaleLabelsWidth;
|
|
|
|
}
|
|
|
|
if(monthDifference >= 8) {
|
|
|
|
padding.left = gantt.posFromDate(minValueDate) - 2;
|
|
|
|
padding.right = scale.full_width - gantt.posFromDate(maxValueDate) - 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 values = getProgressLine();
|
|
|
|
var scaleLabels = [];
|
|
|
|
|
|
|
|
values.planned = values.planned.map(value => +(Math.round(value + "e+2") + "e-2"));
|
|
|
|
values.real = values.real.map(value => +(Math.round(value + "e+2") + "e-2"));
|
|
|
|
values.dates.forEach(function(date){
|
|
|
|
var dateScale = new Date(date);
|
|
|
|
scaleLabels.push(dateToStr(dateScale));
|
|
|
|
})
|
|
|
|
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: "#0000cc",
|
|
|
|
borderColor: "#0000cc",
|
|
|
|
data: values.planned,
|
|
|
|
fill: false,
|
|
|
|
cubicInterpolationMode: 'monotone'
|
|
|
|
},
|
|
|
|
{
|
|
|
|
label: "Real progress",
|
|
|
|
backgroundColor: "#006600",
|
|
|
|
borderColor: "#006600",
|
|
|
|
data: values.real,
|
|
|
|
fill: false,
|
|
|
|
cubicInterpolationMode: 'monotone'
|
|
|
|
}
|
|
|
|
]
|
|
|
|
},
|
|
|
|
options: {
|
|
|
|
responsive: true,
|
|
|
|
maintainAspectRatio: false,
|
|
|
|
layout: {
|
|
|
|
padding: getScalePaddings(values)
|
|
|
|
},
|
|
|
|
onResize: function(chart, newSize) {
|
|
|
|
var dataRange = gantt.getSubtaskDates();
|
|
|
|
if(dataRange.start_date){
|
|
|
|
// align chart with the scale range
|
|
|
|
chart.options.layout.padding = getScalePaddings(values);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
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,
|
|
|
|
stepSize: 10,
|
|
|
|
}
|
|
|
|
},
|
|
|
|
]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
return canvas;
|
|
|
|
});
|