For gauge chart creation ChartJS from http://chartjs.devexpress.com/ is used, which have to be downloaded and unpacked into MoneyManagerEX/res directory. Report works with instable version 6254.
The report package (see below) contains a description of this report, as well as what are the prerequesites for this report to work.
SQL:
Code: Select all
/**
 * Budget: One month is calculated by 365 / 12 = 30.41667, so that one month contains 30.41667 days.
 * 		This value will be used to calculate weekly, bi-weekly or daily budget for a month.
 */
select dic.TransCode, dic.CategoryId, dic.Category Category
	, ifnull(round(ActualAmount, 2), 0) ActualAmount, ifnull(round(BudgetAmount, 2), 0) BudgetAmount
	, ifnull(round((ifnull(ActualAmount, 0) / BudgetAmount) * 100, 2), 1000) Rate
from (
    select distinct TransCode, CategId CategoryId, CategName category 
    from category_v1 
    	join checkingaccount_v1 using (CategId) 
	where 1=1
		and TransCode <> 'Transfer'
) dic 
    left join (
		select CategoryId, Category, total(BasedTransAmount) ActualAmount
        from (
            select ifnull(st.CategId, t.CategId) CategoryId, c.CategName category
            	, ifnull(st.SplitTransAmount, t.TransAmount) * cf.BaseConvRate BasedTransAmount
            from (checkingaccount_v1 t
		left join SplitTransactions_v1 st on (t.TransId = st.TransId))
                join Category_v1 c on (c.CategId = ifnull(st.CategId, t.CategId))
                join AccountList_v1 a on (t.AccountId = a.AccountId)
                join CurrencyFormats_v1 cf on (a.CurrencyId = cf.CurrencyId)
            where 1=1
		and t.TransCode <> 'Transfer'
                and t.Status <> 'V'
                and TransDate between date('now', 'start of month') and date('now', 'start of month', '+1 month', '-1 day')
        )
        group by CategoryId, Category
	) d on (dic.CategoryId = d.CategoryId)
	left join (
		select c.CategId CategoryId, c.CategName Category
			, total(abs(
				case 
					when Period = 'Weekly' then Amount * 30.41667 / 7
					when Period = 'Bi-Weekly' then Amount * 30.41667 / 7 / 2
					when Period = 'Monthly' then Amount 
					when Period = 'Bi-Monthly' then Amount / 2
					when Period = 'Quarterly' then Amount / 3
					when Period = 'Half-Yearly' then Amount / 6
					when Period = 'Yearly' then Amount / 12
					when Period = 'Daily' then Amount * 30.41667
				end
			)) BudgetAmount
		from BudgetTable_v1 bt
		    join BudgetYear_v1 by on (bt.BudgetYearId = by.BudgetYearId)
		    join Category_v1 c on (bt.CategId = c.CategId)
		where 1=1
		    and by.BudgetYearName = strftime('%Y-%m', date())
		group by c.CategId, c.CategName
	) b on (dic.CategoryId = b.CategoryId)
where 1=0
	or ifnull(ActualAmount, 0) != 0
	or ifnull(BudgetAmount, 0) != 0
order by dic.TransCode, dic.Category
;
Code: Select all
local dashboardChartsJsOutput = '';
---
-- The color definition of withdrawal colors. The "start" marks the left including value of
-- the range, the "end" marks the right excluding value.
-- 
-- The first 100% of withdrawal colors are green, as it's better be under budget.
-- 
local withdrawalColors = {};
withdrawalColors[1] = { ["start"] = -1, ["end"] = 25, ["color"] = "#009900"};
withdrawalColors[2] = { ["start"] = 25, ["end"] = 50, ["color"] = "#00CC00"};
withdrawalColors[3] = { ["start"] = 50, ["end"] = 75, ["color"] = "#00EE00"};
withdrawalColors[4] = { ["start"] = 75, ["end"] = 100, ["color"] = "#44FF00"};
withdrawalColors[5] = { ["start"] = 100, ["end"] = 105, ["color"] = "#88FF00"};
withdrawalColors[6] = { ["start"] = 105, ["end"] = 110, ["color"] = "#BBFF00"};
withdrawalColors[7] = { ["start"] = 110, ["end"] = 115, ["color"] = "#FFDD00"};
withdrawalColors[8] = { ["start"] = 115, ["end"] = 120, ["color"] = "#FFBB00"};
withdrawalColors[9] = { ["start"] = 120, ["end"] = 130, ["color"] = "#FF8800"};
withdrawalColors[10] = { ["start"] = 130, ["end"] = 150, ["color"] = "#FF4400"};
withdrawalColors[11] = { ["start"] = 150, ["end"] = 170, ["color"] = "#EE0000"};
withdrawalColors[12] = { ["start"] = 170, ["end"] = 190, ["color"] = "#CC0000"};
withdrawalColors[13] = { ["start"] = 190, ["end"] = 9999, ["color"] = "#990000"};
---
-- The color definition of desposit colors. 
-- 
-- The first 90% of deposit colors are red, as it's better to be over budget.
-- 
local depositColors = {};
depositColors[1] = { ["start"] = -1, ["end"] = 10, ["color"] = "#990000"};
depositColors[2] = { ["start"] = 10, ["end"] = 30, ["color"] = "#CC0000"};
depositColors[3] = { ["start"] = 30, ["end"] = 50, ["color"] = "#EE0000"};
depositColors[4] = { ["start"] = 50, ["end"] = 70, ["color"] = "#FF4400"};
depositColors[5] = { ["start"] = 70, ["end"] = 80, ["color"] = "#FF8800"};
depositColors[6] = { ["start"] = 80, ["end"] = 85, ["color"] = "#FFBB00"};
depositColors[7] = { ["start"] = 85, ["end"] = 90, ["color"] = "#FFDD00"};
depositColors[8] = { ["start"] = 90, ["end"] = 95, ["color"] = "#BBFF00"};
depositColors[9] = { ["start"] = 95, ["end"] = 100, ["color"] = "#88FF00"};
depositColors[10] = { ["start"] = 100, ["end"] = 125, ["color"] = "#44FF00"};
depositColors[11] = { ["start"] = 125, ["end"] = 150, ["color"] = "#00EE00"};
depositColors[12] = { ["start"] = 150, ["end"] = 175, ["color"] = "#00CC00"};
depositColors[13] = { ["start"] = 175, ["end"] = 9999, ["color"] = "#009900"};
---
-- The sum of the expenses budget over all category budgets
-- 
local budgetExpenseSum = 0;
---
-- The sum of the income budget over all category budgets
-- 
local budgetIncomeSum = 0;
---
-- The sum of all actual expenses over all categories
-- 
local actualExpenseSum = 0;
---
-- The sum of all actual incomes over all categories
-- 
local actualIncomeSum = 0;
---
-- Create the javascript chart code for the given category record, which contains the Code of the
-- transaction (Withdrawal or Deposit), the c ategory id, the category name, the budget amount
-- and the actual amount. The rate determines the quotient of actual amount and budget amount.
-- 
-- @param #table record a record of the database query
-- 
function createChartCodeForCategoryBudget(record) 
    local template = [==[
        var c%d = $('<div style="width: 125px; float: left; margin: 0 auto;" />').appendTo('#chartContainer%s');
        c%d.dxLinearGauge($.extend(true, {}, linearGaugeOptions, {
            title: { text: '%s', },
            scale: {
                startValue: 0, 
                endValue: %d, 
                majorTick: { customTickValues: [%d], },
            },
            value: %f,
            valueIndicator: { color: '%s', },
        }));
    ]==];
    local colorDefinition = withdrawalColors;
    if record:get("TransCode") == "Deposit" then
        colorDefinition = depositColors;
    end
    
    local color = determineRateColor(record:get("Rate"), colorDefinition);
    local js = string.format(template
        , record:get("CategoryId")
        , record:get("TransCode")
        , record:get("CategoryId")
        , record:get("Category")
        , 2 * record:get("BudgetAmount")
        , record:get("BudgetAmount"), record:get("ActualAmount")
        , color);
    
    return js;
end
---
-- Create the javascript chart code for comparison of two values. The given title is used as chart
-- title, the type specifies if its a expense or income chart (regarding color definition).
-- 
-- @param #string title the title of the chart
-- @param #float compareValue the comparison value which is displayed as value of the gauge
-- @param #float referenceValue the reference value which is displayed as subvalue at "12 o'clock"
-- @param #string type type of color definition expenses or income
-- 
function createChartCodeForComparison(title, compareValue, referenceValue, type)
    local normTitle = string.gsub(title, "%A", "");
    local template = [==[
        var d%s = $('<div style="width: 380px; float: left; margin: 0 auto;" />').appendTo('#chartContainerOverall');
        d%s.dxCircularGauge($.extend(true, {}, circularGaugeOptions, {
                title: { text: '%s', },
                scale: {
                    startValue: 0, 
                    endValue: %d, 
                },
                rangeContainer: {
                    ranges: [
                        %s
                    ],
                },
                value: %f,
                subvalues: [ %f ],
            }));
    ]==];
    
    local colorDefinition = withdrawalColors;
    if type == "Income" then
        colorDefinition = depositColors;
    end
    
    local jsRangeContainerTemplate = determineRangeColorsAsJs(referenceValue, colorDefinition);
    
    local js = string.format(template
        , normTitle
        , normTitle
        , title
        , 2 * referenceValue
        , jsRangeContainerTemplate
        , compareValue
        , referenceValue
    );
    
    return js;
end
---
-- Determine the colored ranges of the chart by the color definition. The maxValue is used to
-- calculate real values by the percentage values of the color definition.
-- 
-- The ranges will be returned as javascript code, which will be inserted into the chart options.
-- 
-- @param #float maxValue the maximum value of the ranges
-- @param #table colorDefinition defines the ranges and their colors
-- 
function determineRangeColorsAsJs(maxValue, colorDefinition)
    local js = "";
    local rangeTemplate = [==[
        { startValue: %d, endValue: %d, color: '%s' },
    ]==];
    for i = 1, #colorDefinition do
        js = js .. string.format(rangeTemplate
            , maxValue * (colorDefinition[i]["start"] / 100)
            , maxValue * (colorDefinition[i]["end"] / 100)
            , colorDefinition[i]["color"]
        );
    end
    
    return js;
end
---
-- Determine the color for the given rateString from the specified colorDefinition. If the range
-- has been found for the given rate, the according color will be returned.
-- 
-- @param #string rateString the rate which should be colored
-- @param #table colorDefinition defines the ranges and their colors
-- 
function determineRateColor(rateString, colorDefinition)
    local rate = tonumber(rateString); 
    for i = 1, #colorDefinition do
        if colorDefinition[i]["start"] < rate and rate <= colorDefinition[i]["end"] then
            color = colorDefinition[i]["color"];
            break;
        end
    end
        
    return color;
end
---
-- Update the sum values by the actual and budget amount of the record, dependent on the transaction
-- code
-- 
-- @param #table record the record of the database query
-- 
function updateAmountSumsBy(record) 
    if record:get("TransCode") == "Withdrawal" then
        budgetExpenseSum = budgetExpenseSum + record:get("BudgetAmount");
        actualExpenseSum = actualExpenseSum + record:get("ActualAmount");
    elseif record:get("TransCode") == "Deposit" then
        budgetIncomeSum = budgetIncomeSum + record:get("BudgetAmount");
        actualIncomeSum = actualIncomeSum + record:get("ActualAmount");
    end
end
---
-- Handle each record which will be returned by the database query
-- 
-- @param #table record the record of the database query
-- 
function handle_record(record) 
    local jsOutputPerRecord = createChartCodeForCategoryBudget(record);
    
    updateAmountSumsBy(record);
    
    dashboardChartsJsOutput = dashboardChartsJsOutput .. jsOutputPerRecord;
end
---
-- Function will be called after all records have been handled. The chart code for the sum values
-- will be created and put into a result table, which can be inserted within the template
-- 
-- @param #table result placeholder for javascript code to be inserted into the template
-- 
function complete(result)
    if actualIncomeSum == 0 then
        actualIncomeSum = actualExpenseSum / 2;
    end
    local jsExpenseIncome = createChartCodeForComparison("Expenses vs Income"
        , actualExpenseSum, actualIncomeSum, "Expense");
    local jsIncomeBudget = createChartCodeForComparison("Income vs Budget"
        , actualIncomeSum, budgetIncomeSum, "Income");
    local jsExpenseBudget = createChartCodeForComparison("Expense vs Budget"
        , actualExpenseSum, budgetExpenseSum, "Expense");
    
    result:set("EXPENSE_INCOME_DATA", jsExpenseIncome);
    result:set("INCOME_BUDGET_DATA", jsIncomeBudget);
    result:set("EXPENSE_BUDGET_DATA", jsExpenseBudget);
    result:set("DASHBOARD_DATA", dashboardChartsJsOutput);
end
Code: Select all
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title><TMPL_VAR name="REPORTNAME" /></title>
    <link href="master.css" rel="stylesheet"></link>
    <script src="jquery-2.0.3.min.js" type="text/javascript"></script>
    <script src="globalize.min.js" type="text/javascript"></script>
    <script src="dx.chartjs.js" type="text/javascript"></script>
    <script type="text/javascript">
        var circularGaugeOptions = {
            scale: {
                majorTick: {
                    tickInterval: 500,
                    color: '#000000',
                }, 
                minorTick: {
                    visible: true,
                    tickInterval: 100,
                    color: '#000000',
                }, 
                label: {
                    customizeText: function(label) {
                        return label.valueText + ' €';
                    },
                    indentFromTick: 15,
                },
            },
            rangeContainer: {
                palette: 'pastel',
                offset: -10,
            },
            title: {
                font: { size: 18 },
                position: 'top-center',
            },
            valueIndicator: {
                offset: 10,
                color: '#CC0000',
                text: {
                    format: 'fixedPoint',
                    precision: 2,
                    indent: 10,
                },
            },
            subvalueIndicator :{
                type: 'triangle',
                offset: -5,
                color: '#0033CC',
            },
        };
        var linearGaugeOptions = {
            geometry: {
                orientation: 'vertical',
            },
            scale: {
                majorTick: {
                    color: 'green',
                    showCalculatedTicks: false,
                },
                label: {
                    indentFromTick: -5,
                    font: {
                        color: 'green',
                    },
                    customizeText: function(label) {
                        return label.valueText + ' €';
                    },
                } ,
            },
            rangeContainer: {
                offset: 5,
            }, 
            valueIndicator: {
                type: 'rangebar',
                offset: 5,
                text: {
                    indent: 3,
                    format: 'fixedPoint',
                    precision: 2,
                    customizeText: function(label) {
                        return label.valueText + ' €';
                    },
                },
            },
            title: {
                font: { size: 12 },
                position: 'top-center',
            },
        };
        $(function () {
            <TMPL_VAR name="EXPENSE_INCOME_DATA" />
            <TMPL_VAR name="INCOME_BUDGET_DATA" />
            <TMPL_VAR name="EXPENSE_BUDGET_DATA" />
            <TMPL_VAR name="DASHBOARD_DATA" />
        });
    </script>
</head>
<body>
    <div class="page-header">
        <h2 class="text-center">Budget Monitoring for this month</h2>
        <p class="text-center">Compare Expenses, Income and Budget at <TMPL_VAR name="today" /></p>
    </div>
    <div id="chartContainerOverall" class="container" style="clear:both;"></div>
    <div id="chartContainerWithdrawal" class="container" style="height:350px;clear:both;">
        <h3>Budget monitoring of Withdrawal Categories</h3>
    </div>
    <div id="chartContainerDeposit" class="container" style="height:350px;clear:both;">
        <h3>Budget monitoring of Deposit Categories</h3>
    </div>
</body>
</html>
nuvito
