494 lines
21 KiB
JavaScript
494 lines
21 KiB
JavaScript
(function ($) {
|
|
var settings = {
|
|
title: 'Resource Utilization',
|
|
titleClass: 'pv-title',
|
|
magnifyBlockCss: {
|
|
'height': '110px',
|
|
'padding-left': '5px',
|
|
'width': '245px'
|
|
},
|
|
magnifyClass: 'optimuse-bar',
|
|
actualMagnifyClass: 'pv-mock-opt',
|
|
actualMagnifyTitle: 'Actual',
|
|
actualMagnifyTitleCss: {
|
|
'margin-left': '-20px',
|
|
'margin-top': '-5px'
|
|
|
|
},
|
|
plannedMagnifyClass: 'pv-mock-opt',
|
|
plannedMagnifyTitle: 'Planned',
|
|
plannedMagnifyTitleCss: {
|
|
'margin-left': '-20px',
|
|
'margin-top': '-5px'
|
|
},
|
|
stackedChartClass: 'pv-chart optimuse-magnify',
|
|
stackedChartCss: {
|
|
'height': '175px',
|
|
'padding-left': '5px',
|
|
'padding-right': '5px',
|
|
'width': '225px'
|
|
},
|
|
chartModalTitle: 'Resource Usage',
|
|
chartModalActualTitle: 'Actual Resource Allocation',
|
|
chartModalPlannedTitle: 'Planned Resource Allocation',
|
|
isUOMHours: false,
|
|
data: null
|
|
};
|
|
|
|
var createSeparatorBlock = function () {
|
|
return $('<div class="magnify-seperator"></div>');
|
|
};
|
|
var createTitleBlock = function () {
|
|
return $('<div>' + settings.title + '</div>').addClass(settings.titleClass);
|
|
};
|
|
var createMagnifyBlock = function () {
|
|
var chartUniquePostfix = Math.uuid();
|
|
var magnifyActualTitle = $('<div>' + settings.actualMagnifyTitle + '</div>').css(settings.actualMagnifyTitleCss);
|
|
var magnifyActualBlock = $('<div data-magnify="actual" id="magnify-actual-' + chartUniquePostfix + '"></div>')
|
|
.addClass(settings.actualMagnifyClass)
|
|
.append(magnifyActualTitle);
|
|
|
|
var magnifyPlannedTitle = $('<div>' + settings.plannedMagnifyTitle + '</div>').css(settings.plannedMagnifyTitleCss);
|
|
var magnifyPlannedBlock = $('<div data-magnify="planned" id="magnify-planned-' + chartUniquePostfix + '"></div>')
|
|
.addClass(settings.plannedMagnifyClass)
|
|
.append(magnifyPlannedTitle);
|
|
|
|
var magnifyBlock = $('<div></div>').css(settings.magnifyBlockCss)
|
|
.addClass(settings.magnifyClass)
|
|
.append(magnifyActualBlock)
|
|
.append(magnifyPlannedBlock);
|
|
|
|
return magnifyBlock;
|
|
};
|
|
var createStackedChartBlock = function () {
|
|
var blockId = 'magnify-stacked-' + Math.uuid();
|
|
var block = $('<div data-magnify="stacked"></div>').addClass(settings.stackedChartClass)
|
|
.css(settings.stackedChartCss)
|
|
.attr('id', blockId);
|
|
|
|
return block;
|
|
};
|
|
var createResourceUsageDialog = function () {
|
|
var chartUniquePostfix = Math.uuid();
|
|
var dialog =
|
|
"<div class='modal fade' data-width='1000' tabindex='-1' role='dialog' style='display: none;'>\
|
|
<div class='modal-content'>\
|
|
<div class='modal-header'>\
|
|
<button type='button' class='close' data-dismiss='modal' aria-hidden='true'>x</button>\
|
|
<h4 class='modal-title'>"+ settings.chartModalTitle + "</h4>\
|
|
</div>\
|
|
<div class='modal-body'>\
|
|
<div data-magnify='actual' class='pv-mock-opt' id='magnify-actual-" + chartUniquePostfix + "' style='margin-bottom: -55px;'><div style='margin-left: -23px;; margin-top: -5px;'>" + settings.actualMagnifyTitle + "</div></div>\
|
|
<div style='height: 300px;' class='" + settings.stackedChartClass + "' data-chart='actual' id='chart-actual-" + chartUniquePostfix + "'></div>\
|
|
<br />\
|
|
<div data-magnify='planned' class='pv-mock-opt' id='magnify-planned-" + chartUniquePostfix + "' style='margin-bottom: -55px;'><div style='margin-left: -23px;; margin-top: -5px;'>" + settings.plannedMagnifyTitle + "</div></div>\
|
|
<div style='height: 300px;' class='" + settings.stackedChartClass + "' data-chart='planned' id='chart-planned-" + chartUniquePostfix + "'></div>\
|
|
</div>\
|
|
<div class='modal-footer'>\
|
|
<button type='button' class='btn btn-default' data-dismiss='modal'>Close</button>\
|
|
</div>\
|
|
</div>\
|
|
</div>";
|
|
|
|
return dialog;
|
|
};
|
|
var createPreloader = function () {
|
|
var preloader =
|
|
"<div class='loadRotator hidden'>\
|
|
<span>\
|
|
<img class='valign-middle' src='/Content/images/loadFA.gif' /> loading...\
|
|
</span>\
|
|
</div>";
|
|
|
|
return preloader;
|
|
};
|
|
var createStackedChart = function ($this) {
|
|
var chartId = getStackedChartBlockId($this);
|
|
AmCharts.makeChart(chartId, {
|
|
"autoMargins": false,
|
|
"categoryField": "planvsact",
|
|
"categoryAxis": {
|
|
"gridPosition": "start",
|
|
"axisAlpha": 0,
|
|
"gridAlpha": 0
|
|
},
|
|
"export": {
|
|
"enabled": true
|
|
},
|
|
"fontFamily": "Open Sans",
|
|
"marginTop": 40,
|
|
"marginRight": 16,
|
|
"marginLeft": -20,
|
|
"marginBottom": 0,
|
|
"rotate": false,
|
|
"type": "serial",
|
|
"theme": "light",
|
|
"valueAxes": [{
|
|
"stackType": "100%",
|
|
"axisAlpha": 0,
|
|
"gridAlpha": 0,
|
|
"labelsEnabled": false,
|
|
"position": "left"
|
|
}],
|
|
"balloon": {
|
|
"textAlign": "left"
|
|
},
|
|
"graphs": [{
|
|
"fillAlphas": 0.9,
|
|
"fixedColumnWidth": 54,
|
|
"fontSize": 10,
|
|
"lineAlpha": 0.5,
|
|
"lineColor": "#ffe445",
|
|
"type": "column",
|
|
"labelText": "[[percents]]%",
|
|
"labelFunction": createStackedChartLabelText,
|
|
"valueField": "underallocatedWeeks",
|
|
"summaryField": "underallocatedSummary", // custom field, contact EN if you need some clarification
|
|
"balloonFunction": createStackedChartBalloonText
|
|
}, {
|
|
"fillAlphas": 0.9,
|
|
"fixedColumnWidth": 54,
|
|
"fontSize": 10,
|
|
"lineAlpha": 0.5,
|
|
"lineColor": "#b2d235",
|
|
"type": "column",
|
|
"labelText": "[[percents]]%",
|
|
"labelFunction": createStackedChartLabelText,
|
|
"valueField": "idealallocatedWeeks",
|
|
"summaryField": "idealallocatedSummary", // custom field, contact EN if you need some clarification
|
|
"balloonFunction": createStackedChartBalloonText
|
|
}, {
|
|
"fillAlphas": 0.9,
|
|
"fixedColumnWidth": 54,
|
|
"fontSize": 10,
|
|
"lineAlpha": 0.5,
|
|
"lineColor": "#ff2424",
|
|
"type": "column",
|
|
"labelText": "[[percents]]%",
|
|
"labelFunction": createStackedChartLabelText,
|
|
"valueField": "overallocatedWeeks",
|
|
"summaryField": "overallocatedSummary", // custom field, contact EN if you need some clarification
|
|
"balloonFunction": createStackedChartBalloonText
|
|
}]
|
|
});
|
|
};
|
|
var createStackedChartBalloonText = function (item, graph) {
|
|
var summary = item.dataContext[item.graph.summaryField];
|
|
var uomValueText = '';
|
|
if (settings.isUOMHours) {
|
|
uomValueText = '<td>Hours:</td><td> ' + summary.MinHoursValue + ' - ' + summary.MaxHoursValue + '</td>';
|
|
} else {
|
|
uomValueText = '<td>Resources:</td><td> ' + summary.MinResourcesValue + ' - ' + summary.MaxResourcesValue + '</td>';
|
|
}
|
|
var balloneText = '<table><tr>' + uomValueText + '</tr><tr><td>Weeks:</td><td> ' + summary.Weeks + '</td></tr></table>';
|
|
|
|
return balloneText;
|
|
};
|
|
var createStackedChartLabelText = function (item, labelText) {
|
|
return item.values.percents > 5 ? labelText : '';
|
|
};
|
|
var createResourceUsageCharts = function ($this) {
|
|
var actualId = getResourceUsageChartBlockId($this, false);
|
|
var plannedId = getResourceUsageChartBlockId($this, true);
|
|
|
|
createResourceUsageChart(actualId, settings.chartModalActualTitle);
|
|
createResourceUsageChart(plannedId, settings.chartModalPlannedTitle);
|
|
};
|
|
var createResourceUsageChart = function (chartId, title) {
|
|
var chart = findChart(chartId);
|
|
if (chart) {
|
|
return;
|
|
}
|
|
|
|
AmCharts.makeChart(chartId, {
|
|
"categoryAxis": {
|
|
"gridPosition": "start",
|
|
"axisAlpha": 0,
|
|
"gridAlpha": 0,
|
|
"parseDates": true
|
|
},
|
|
"categoryField": "weekEnding",
|
|
"depth3D": 2,
|
|
"fontFamily": "Open Sans",
|
|
"legend": {
|
|
"enabled": true,
|
|
},
|
|
"theme": "light",
|
|
"type": "serial",
|
|
"titles": [{
|
|
"size": 15,
|
|
"text": title
|
|
}],
|
|
"graphs": [{
|
|
"title": "Over Allocation",
|
|
"balloonFunction": createResourceUsageChartBalloonText,
|
|
"valueField": "overAllocated",
|
|
"bullet": "round",
|
|
"bulletBorderColor": "#FFFFFF",
|
|
"bulletBorderThickness": 1,
|
|
"bulletSize": 2,
|
|
"lineThickness": 1,
|
|
"lineAlpha": 1,
|
|
"fillAlphas": 0.2,
|
|
"stackable": false,
|
|
"lineColor": "red"
|
|
}, {
|
|
"title": "Optimal Allocation",
|
|
"balloonFunction": createResourceUsageChartBalloonText,
|
|
"valueField": "idealAllocated",
|
|
"bullet": "round",
|
|
"bulletBorderColor": "#FFFFFF",
|
|
"bulletBorderThickness": 1,
|
|
"bulletSize": 2,
|
|
"lineThickness": 1,
|
|
"lineAlpha": 1,
|
|
"fillAlphas": 0.2,
|
|
"stackable": false,
|
|
"lineColor": "green"
|
|
}, {
|
|
"title": "Under Allocation",
|
|
"balloonFunction": createResourceUsageChartBalloonText,
|
|
"valueField": "underAllocated",
|
|
"bullet": "round",
|
|
"bulletBorderColor": "#FFFFFF",
|
|
"bulletBorderThickness": 1,
|
|
"bulletSize": 2,
|
|
"lineThickness": 1,
|
|
"lineAlpha": 1,
|
|
"fillAlphas": 0.2,
|
|
"stackable": false,
|
|
"lineColor": "yellow"
|
|
}]
|
|
});
|
|
};
|
|
var createResourceUsageChartBalloonText = function (item, graph) {
|
|
var balloneText = Math.abs(item.values.value).formatNumber(false) + " units at " + (new Date(item.category)).toDateString();
|
|
|
|
return balloneText;
|
|
};
|
|
var createMarkup = function ($this) {
|
|
if (!$this) {
|
|
throw '$this does not exist';
|
|
}
|
|
|
|
// if markup is already created
|
|
if ($this.data('optimuse')) {
|
|
return;
|
|
}
|
|
|
|
var preloader = createPreloader();
|
|
var container = $('<div></div>').addClass("container hidden")
|
|
.append(createSeparatorBlock())
|
|
.append(createTitleBlock())
|
|
.append(createMagnifyBlock())
|
|
.append(createStackedChartBlock())
|
|
.append(createResourceUsageDialog());
|
|
|
|
$this.data('optimuse', true)
|
|
.append(preloader)
|
|
.append(container);
|
|
|
|
createStackedChart($this);
|
|
createResourceUsageCharts($this);
|
|
|
|
return $this;
|
|
};
|
|
var getStackedChartBlockId = function ($this) {
|
|
return $this.find('[data-magnify="stacked"]').attr('id');
|
|
};
|
|
var getResourceUsageChartBlockId = function ($this, planned) {
|
|
return $this.find('[data-chart="' + (planned ? "planned" : "actual") + '"]').attr('id');
|
|
};
|
|
var getStackedChartDataSource = function (data) {
|
|
var data = settings.data || {};
|
|
var dataSource = [getStackedChartItem(data.ActualCapacity), getStackedChartItem(data.PlannedCapacity)];
|
|
|
|
return dataSource;
|
|
};
|
|
var getStackedChartItem = function (capacity) {
|
|
var item = {
|
|
planvsact: "",
|
|
underallocatedSummary: capacity ? capacity.UnderAllocatedSummary : null,
|
|
idealallocatedSummary: capacity ? capacity.IdealAllocatedSummary : null,
|
|
overallocatedSummary: capacity ? capacity.OverAllocatedSummary : null,
|
|
underallocatedWeeks: (capacity && capacity.UnderAllocatedSummary) ? capacity.UnderAllocatedSummary.Weeks || 0 : 0,
|
|
idealallocatedWeeks: (capacity && capacity.IdealAllocatedSummary) ? capacity.IdealAllocatedSummary.Weeks || 0 : 0,
|
|
overallocatedWeeks: (capacity && capacity.OverAllocatedSummary) ? capacity.OverAllocatedSummary.Weeks || 0 : 0,
|
|
};
|
|
|
|
return item;
|
|
};
|
|
var getIdealResourceUsagePercentage = function (capacity) {
|
|
if (!capacity)
|
|
return 0;
|
|
|
|
var totalCapacity = capacity.TotalHoursCapacity || 0;
|
|
var overAllocated = capacity.OverAllocatedSummary ? capacity.OverAllocatedSummary.TotalHoursValue || 0 : 0;
|
|
var underAllocated = capacity.UnderAllocatedSummary ? capacity.UnderAllocatedSummary.TotalHoursValue || 0 : 0;
|
|
var notIdealAllocated = overAllocated + underAllocated;
|
|
|
|
if (totalCapacity == 0) {
|
|
return notIdealAllocated == 0 ? 100 : 0;
|
|
} else {
|
|
var notIdealPercentage = Math.min((notIdealAllocated / totalCapacity), 1);
|
|
return Math.round(100 * (1 - notIdealPercentage));
|
|
}
|
|
};
|
|
var getSummaryAllocationPercent = function (capacity, summary) {
|
|
var totalCapacity = (capacity || {}).TotalHoursCapacity || 0,
|
|
summaryAllocated = (summary || {}).TotalHoursValue || 0;
|
|
|
|
if (totalCapacity == 0) {
|
|
return summaryAllocated == 0 ? 0 : 100;
|
|
}
|
|
else {
|
|
return Math.round((summaryAllocated / totalCapacity) * 100);
|
|
}
|
|
};
|
|
var getResourceUsageChartDataSource = function (planned) {
|
|
var dataSource = [];
|
|
var weekEndings = settings.data.WeekEndings || [];
|
|
if (weekEndings.length <= 0) {
|
|
return dataSource;
|
|
}
|
|
var capacity = (planned ? settings.data.PlannedCapacity : settings.data.ActualCapacity);
|
|
if (capacity) {
|
|
var overAllocatedSummary = capacity.OverAllocatedSummary || {},
|
|
underAllocatedSummary = capacity.UnderAllocatedSummary || {},
|
|
idealAllocatedSummary = capacity.IdealAllocatedSummary || {};
|
|
|
|
var overAllocatedValues = (settings.isUOMHours ? overAllocatedSummary.HoursUsageData : overAllocatedSummary.ResourceUsageData) || {},
|
|
underAllocatedValues = (settings.isUOMHours ? underAllocatedSummary.HoursUsageData : underAllocatedSummary.ResourceUsageData) || {},
|
|
idealAllocatedValues = (settings.isUOMHours ? idealAllocatedSummary.HoursUsageData : idealAllocatedSummary.ResourceUsageData) || {};
|
|
|
|
for (var weekIndex = 0; weekIndex < weekEndings.length; weekIndex++) {
|
|
var weekEnding = weekEndings[weekIndex];
|
|
var dataItem = {
|
|
weekEnding: weekEnding,
|
|
overAllocated: overAllocatedValues[weekEnding] || 0,
|
|
underAllocated: underAllocatedValues[weekEnding] || 0,
|
|
idealAllocated: idealAllocatedValues[weekEnding] || 0
|
|
};
|
|
dataSource.push(dataItem);
|
|
}
|
|
}
|
|
|
|
return dataSource;
|
|
};
|
|
var refreshResourceUsageCharts = function ($this) {
|
|
var resourceUsageActualChartId = getResourceUsageChartBlockId($this, false);
|
|
var resourceUsagePlannedChartId = getResourceUsageChartBlockId($this, true);
|
|
|
|
updateChart(resourceUsageActualChartId, getResourceUsageChartDataSource(false));
|
|
updateChart(resourceUsagePlannedChartId, getResourceUsageChartDataSource(true));
|
|
};
|
|
var showResourceUsageDialog = function ($this) {
|
|
$this.find('.modal').modal('show');
|
|
};
|
|
|
|
var methods = {
|
|
init: function (options) {
|
|
settings = $.extend(true, settings, options);
|
|
this.each(function () {
|
|
var $this = $(this);
|
|
createMarkup($this);
|
|
});
|
|
|
|
if (settings.data) {
|
|
methods.setData.apply(this, [settings.data]);
|
|
}
|
|
},
|
|
showPreloader: function () {
|
|
return this.each(function () {
|
|
var $this = $(this);
|
|
$this.find('.container').addClass('hidden');
|
|
$this.find('.loadRotator').removeClass('hidden');
|
|
});
|
|
},
|
|
hidePreloader: function () {
|
|
return this.each(function () {
|
|
var $this = $(this);
|
|
$this.find('.container').removeClass('hidden');
|
|
$this.find('.loadRotator').addClass('hidden');
|
|
});
|
|
},
|
|
mouveBarTitles: function () {
|
|
var $this = $(this);
|
|
$this.find('.container').find("tspan[x='0']").attr("x", "49").css("font-weight", "bold");;
|
|
return null;
|
|
},
|
|
setData: function (data) {
|
|
settings.data = data || settings.data;
|
|
return this.each(function () {
|
|
var $this = $(this);
|
|
if (settings.data) {
|
|
var magnifyGlassActualId = $this.find('[data-magnify="actual"]').map(function (index, element) {
|
|
return $(element).attr('id');
|
|
});
|
|
var magnifyGlassPlannedId = $this.find('[data-magnify="planned"]').map(function (index, element) {
|
|
return $(element).attr('id');
|
|
});
|
|
|
|
methods.drawMagnifyGlass.apply(this, [magnifyGlassActualId, false]);
|
|
methods.drawMagnifyGlass.apply(this, [magnifyGlassPlannedId, true]);
|
|
|
|
$this.find('div.magnifyContainer').on('click', function () {
|
|
showResourceUsageDialog($this);
|
|
});
|
|
|
|
var stackedChartId = getStackedChartBlockId($this);
|
|
updateChart(stackedChartId, getStackedChartDataSource());
|
|
|
|
refreshResourceUsageCharts($this);
|
|
}
|
|
});
|
|
},
|
|
changeUOMMode: function (isUOMHours) {
|
|
settings.isUOMHours = isUOMHours;
|
|
return this.each(function () {
|
|
var $this = $(this);
|
|
|
|
refreshResourceUsageCharts($this);
|
|
});
|
|
},
|
|
drawMagnifyGlass: function (magnifyGlassId, planned) {
|
|
var data = settings.data || {};
|
|
var capacity = (planned ? data.PlannedCapacity : data.ActualCapacity) || {};
|
|
var canvasUniquePostfix = Math.uuid();
|
|
var gradient = 'linear-gradient(-135deg, #7030A0 20%, #475FBE 30%, #04ABED 40%, #3CBDAF 70%, #8ED054 80%)';
|
|
|
|
for (var i = 0; i < magnifyGlassId.length; i++) {
|
|
$('#' + magnifyGlassId[i]).find('div.magnifyContainer').remove();
|
|
$('#' + magnifyGlassId[i]).bar(getIdealResourceUsagePercentage(capacity) || 0, (planned ? 'planned' : 'actual') + '-canvas-' + canvasUniquePostfix, true, gradient);
|
|
}
|
|
},
|
|
drawResourceUsageGraph: function (chartDivId, planned) {
|
|
return $(this).each(function () {
|
|
var title = planned ? settings.chartModalPlannedTitle : settings.chartModalActualTitle;
|
|
|
|
createResourceUsageChart(chartDivId, title);
|
|
updateChart(chartDivId, getResourceUsageChartDataSource(planned));
|
|
});
|
|
},
|
|
getOverAllocationPercent: function () {
|
|
var data = settings.data || 0,
|
|
capacity = data.ActualCapacity || {};
|
|
|
|
return getSummaryAllocationPercent(capacity, capacity.OverAllocatedSummary);
|
|
},
|
|
getUnderAllocationPercent: function () {
|
|
var data = settings.data || 0,
|
|
capacity = data.ActualCapacity || {};
|
|
|
|
return getSummaryAllocationPercent(capacity, capacity.UnderAllocatedSummary);
|
|
},
|
|
};
|
|
|
|
$.fn.optimuse = function (options) {
|
|
if (typeof options === 'string' && typeof methods[options] === 'function') {
|
|
return methods[options].apply(this, Array.prototype.slice.call(arguments, 1));
|
|
} else if (typeof options === 'object' || !options) {
|
|
return methods.init.apply(this, arguments);
|
|
}
|
|
};
|
|
}(jQuery)); |