(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 $('
'); }; var createTitleBlock = function () { return $('
' + settings.title + '
').addClass(settings.titleClass); }; var createMagnifyBlock = function () { var chartUniquePostfix = Math.uuid(); var magnifyActualTitle = $('
' + settings.actualMagnifyTitle + '
').css(settings.actualMagnifyTitleCss); var magnifyActualBlock = $('
') .addClass(settings.actualMagnifyClass) .append(magnifyActualTitle); var magnifyPlannedTitle = $('
' + settings.plannedMagnifyTitle + '
').css(settings.plannedMagnifyTitleCss); var magnifyPlannedBlock = $('
') .addClass(settings.plannedMagnifyClass) .append(magnifyPlannedTitle); var magnifyBlock = $('
').css(settings.magnifyBlockCss) .addClass(settings.magnifyClass) .append(magnifyActualBlock) .append(magnifyPlannedBlock); return magnifyBlock; }; var createStackedChartBlock = function () { var blockId = 'magnify-stacked-' + Math.uuid(); var block = $('
').addClass(settings.stackedChartClass) .css(settings.stackedChartCss) .attr('id', blockId); return block; }; var createResourceUsageDialog = function () { var chartUniquePostfix = Math.uuid(); var dialog = ""; return dialog; }; var createPreloader = function () { var preloader = ""; 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 = 'Hours: ' + summary.MinHoursValue + ' - ' + summary.MaxHoursValue + ''; } else { uomValueText = 'Resources: ' + summary.MinResourcesValue + ' - ' + summary.MaxResourcesValue + ''; } var balloneText = '' + uomValueText + '
Weeks: ' + summary.Weeks + '
'; 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 = $('
').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));