UNPKG

budget-view-chart

Version:

A React chart component specialising in display budget for personal finance.

204 lines (203 loc) 8 kB
"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;