budget-view-chart
Version:
A React chart component specialising in display budget for personal finance.
204 lines (203 loc) • 8 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.DataProcessor = exports.ChartData = exports.BudgetData = void 0;
/**
* Data structure representing a budget item
*/
class BudgetData {
name;
description;
monthlyBudget;
amount;
// chart info
xStart;
xLength;
yStart;
yLength;
static EChartsDataSetDimensions = [
{ name: 'name', type: 'ordinal' },
{ name: 'description', type: 'ordinal' },
{ name: 'monthlyBudget', type: 'float' },
{ name: 'amount', type: 'float' },
{ name: 'xStart', type: 'float' },
{ name: 'xLength', type: 'float' },
{ name: 'yStart', type: 'float' },
{ name: 'yLength', type: 'float' }
];
}
exports.BudgetData = BudgetData;
/**
* Data structure representing a block on the chart
*/
class ChartData {
type;
name;
description;
monthlyBudget;
month;
amount;
// chart
xStart;
xLength;
yStart;
yLength;
static EChartsDataSetDimensions = [
{ name: 'type', type: 'ordinal' },
{ name: 'name', type: 'ordinal' },
{ name: 'description', type: 'ordinal' },
{ name: 'monthlyBudget', type: 'float' },
{ name: 'xStart', type: 'float' },
{ name: 'xLength', type: 'float' },
{ name: 'month', type: 'ordinal' },
{ name: 'amount', type: 'float' },
{ name: 'yStart', type: 'float' },
{ name: 'yLength', type: 'float' }
];
}
exports.ChartData = ChartData;
class DataProcessor {
budgetRecords;
TOTAL_X;
TOTAL_Y;
totalBudget;
totalAmount;
budgetNames;
/**
* Construct a DataProcessor
* @param {BudgetRecord[]} budgetRecords
* @param {number} TOTAL_X
* @param {number} TOTAL_Y
*/
constructor(budgetRecords, TOTAL_X, TOTAL_Y) {
this.budgetRecords = budgetRecords;
// TODO: Validate BudgetRecord
this.TOTAL_X = TOTAL_X;
this.TOTAL_Y = TOTAL_Y;
this.totalBudget = this.budgetRecords.reduce((acc, cur) => {
return acc + cur.monthlyBudget * 12;
}, 0);
this.totalAmount = this.budgetRecords.reduce((acc, cur) => {
return acc + cur.monthlyAmount.reduce((acc, cur) => {
return acc + cur;
}, 0);
}, 0);
this.budgetNames = this.budgetRecords.reduce((acc, cur) => {
if (!acc.includes(cur.name)) {
acc.push(cur.name);
}
return acc;
}, []);
}
getHighestY = () => {
return this.budgetRecords.reduce((acc, cur) => {
const budgetAmount = cur.monthlyAmount.reduce((acc, cur) => {
return acc + cur;
}, 0);
const budgetWidth = cur.monthlyBudget / (this.totalBudget / 12) * this.TOTAL_X;
const height = (budgetAmount / this.totalBudget) * (this.TOTAL_X * this.TOTAL_Y) / budgetWidth;
if (height > acc) {
return height;
}
else {
return acc;
}
}, 0);
};
getLowestY = () => {
return this.budgetRecords.reduce((acc, cur) => {
const lowestAmount = cur.monthlyAmount.reduce((acc, cur) => {
// eslint-disable-next-line @typescript-eslint/restrict-plus-operands
const currentAccumulatedAmount = acc[0] + cur;
if (currentAccumulatedAmount < acc[1]) {
return [currentAccumulatedAmount, currentAccumulatedAmount];
}
else {
return [currentAccumulatedAmount, acc[1]];
}
}, [0, 0])[1];
const budgetWidth = cur.monthlyBudget / (this.totalBudget / 12) * this.TOTAL_X;
const height = (lowestAmount / this.totalBudget) * (this.TOTAL_X * this.TOTAL_Y) / budgetWidth;
if (height < acc) {
return height;
}
else {
return acc;
}
}, 0);
};
getBudgetData = () => {
const budgetData = [];
let xTrack = [0, 0]; // [xStart, xLength]
this.budgetRecords.forEach((element) => {
// assert(element.monthlyBudget.amount>=0);
const data = new BudgetData();
data.name = element.name;
data.description = element.description;
data.monthlyBudget = element.monthlyBudget;
data.xStart = xTrack[0] + xTrack[1];
data.xLength = element.monthlyBudget / (this.totalBudget / 12) * this.TOTAL_X;
data.amount = element.monthlyAmount.reduce((acc, cur) => acc + cur, 0);
data.yStart = 0;
data.yLength = this.TOTAL_Y;
budgetData.push(Object.assign({}, data));
xTrack = [data.xStart, data.xLength];
});
return budgetData;
};
/**
*
* @returns {ChartData[]}
*/
getChartData = () => {
const chartData = [];
let xTrack = [0, 0]; // [xStart, xLength]
this.budgetRecords.forEach((budgetRecord) => {
// assert(element.monthlyBudget.amount>=0);
const breakdownData = new ChartData();
breakdownData.type = 'breakdown';
breakdownData.name = budgetRecord.name;
breakdownData.description = budgetRecord.description;
breakdownData.monthlyBudget = budgetRecord.monthlyBudget;
breakdownData.xStart = xTrack[0] + xTrack[1];
breakdownData.xLength = budgetRecord.monthlyBudget / (this.totalBudget / 12) * this.TOTAL_X;
xTrack = [breakdownData.xStart, breakdownData.xLength];
let yTrack = [0, 0]; // [yStart, yLength]
for (let month = 0; month < budgetRecord.monthlyAmount.length; month++) {
const monthlyAmount = budgetRecord.monthlyAmount[month];
breakdownData.month = month;
breakdownData.amount = monthlyAmount;
breakdownData.yStart = yTrack[0] + yTrack[1];
breakdownData.yLength = (monthlyAmount / this.totalBudget) * (this.TOTAL_X * this.TOTAL_Y) / breakdownData.xLength;
yTrack = [breakdownData.yStart, breakdownData.yLength];
chartData.push(Object.assign({}, breakdownData));
}
});
return chartData;
};
getMonthlyAggregatedChartData = () => {
const groupByMonth = this.getChartData().reduce((aggPerMonth, breakdownData) => {
aggPerMonth[breakdownData.month].push(breakdownData);
return aggPerMonth;
}, Array.from(Array(12), () => new Array(0)));
let yTrack = [0, 0]; // [yStart, yLength]
return groupByMonth.map((monthlyBreakdownDataArray, index) => {
const month = index;
const monthTotalBudget = monthlyBreakdownDataArray.reduce((acc, cur) => acc + cur.monthlyBudget, 0);
const monthTotalAmount = monthlyBreakdownDataArray.reduce((acc, cur) => acc + cur.amount, 0);
const monthlyAggregateData = new ChartData();
monthlyAggregateData.name = '';
monthlyAggregateData.type = 'aggregate';
monthlyAggregateData.description = '';
monthlyAggregateData.monthlyBudget = monthTotalBudget;
monthlyAggregateData.xStart = 0;
monthlyAggregateData.xLength = this.TOTAL_X;
monthlyAggregateData.month = month;
monthlyAggregateData.amount = monthTotalAmount;
monthlyAggregateData.yStart = yTrack[0] + yTrack[1];
monthlyAggregateData.yLength = (monthTotalAmount / this.totalBudget) * (this.TOTAL_X * this.TOTAL_Y) / monthlyAggregateData.xLength;
yTrack = [monthlyAggregateData.yStart, monthlyAggregateData.yLength];
return monthlyBreakdownDataArray.concat([Object.assign({}, monthlyAggregateData)]);
});
};
}
exports.DataProcessor = DataProcessor;