mirror of
https://github.com/jcreek/BankStatementAnalyser.git
synced 2026-07-12 18:43:49 +00:00
feat(*): Add basic graphing
This commit is contained in:
@@ -0,0 +1 @@
|
||||
export const transactions: Array<BankTransaction> = [];
|
||||
@@ -0,0 +1,129 @@
|
||||
import { transactions } from "./bankData";
|
||||
import Chart from 'chart.js/auto';
|
||||
|
||||
export default function generateCharts(): void {
|
||||
// const filter: Filter = { year: 2022, category: 'Eating out', excludeIncome: true};
|
||||
const filter: Filter = { year: 2022, excludeIncome: true};
|
||||
// const filter: Filter = { year: 2022, month: 1, excludeIncome: true};
|
||||
const chartData = getChartData(transactions, filter);
|
||||
console.log('chartData', chartData);
|
||||
|
||||
const ctx = document.getElementById('myChart') as HTMLCanvasElement;
|
||||
const myChart = new Chart(ctx, {
|
||||
type: 'bar',
|
||||
data: chartData,
|
||||
options: {
|
||||
responsive: true,
|
||||
scales: {
|
||||
x: {
|
||||
stacked: true,
|
||||
},
|
||||
y: {
|
||||
stacked: true
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function getRandomColor(): string {
|
||||
// Generate a random color in hexadecimal format
|
||||
const color = Math.floor(Math.random() * 0x1000000).toString(16);
|
||||
return '#' + ('000000' + color).slice(-6);
|
||||
}
|
||||
|
||||
function getChartData(transactions: BankTransaction[], filters: { year?: number, month?: number, category?: string, excludeIncome?: boolean } | null | undefined): ChartData {
|
||||
// Return an empty chart data object if no filters are provided
|
||||
if (!filters) {
|
||||
return {
|
||||
labels: [],
|
||||
datasets: []
|
||||
};
|
||||
}
|
||||
|
||||
// Filter the transactions based on the specified filters
|
||||
const filteredTransactions = transactions.filter(transaction => {
|
||||
if (filters.year && transaction.dateYear !== filters.year) {
|
||||
return false;
|
||||
}
|
||||
if (filters.month && transaction.dateMonth !== filters.month) {
|
||||
return false;
|
||||
}
|
||||
if (filters.category && transaction.category !== filters.category) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
// Group the transactions by year or month, depending on the filters
|
||||
const groupedTransactions: { [key: string]: BankTransaction[] } = {};
|
||||
if (filters.year && !filters.month) {
|
||||
// Group the transactions by month
|
||||
for (const transaction of filteredTransactions) {
|
||||
if (filters.excludeIncome && transaction.amount < 0) {
|
||||
continue; // Skip transactions with a positive amount if excludeIncome is true
|
||||
}
|
||||
const month = `${transaction.dateYear}-${transaction.dateMonth}`;
|
||||
if (!groupedTransactions[month]) {
|
||||
groupedTransactions[month] = [];
|
||||
}
|
||||
groupedTransactions[month].push(transaction);
|
||||
}
|
||||
} else {
|
||||
// Group the transactions by year
|
||||
for (const transaction of filteredTransactions) {
|
||||
if (filters.excludeIncome && transaction.amount < 0) {
|
||||
continue; // Skip transactions with a positive amount if excludeIncome is true
|
||||
}
|
||||
const year = transaction.dateYear.toString();
|
||||
if (!groupedTransactions[year]) {
|
||||
groupedTransactions[year] = [];
|
||||
}
|
||||
groupedTransactions[year].push(transaction);
|
||||
}
|
||||
}
|
||||
|
||||
// Create the chart data object
|
||||
const chartData: ChartData = {
|
||||
labels: [],
|
||||
datasets: []
|
||||
};
|
||||
|
||||
// Add a dataset for each category
|
||||
const categories: { [key: string]: string } = {};
|
||||
for (const year in groupedTransactions) {
|
||||
for (let index = 0; index < groupedTransactions[year].length; index++) {
|
||||
const transaction = groupedTransactions[year][index];
|
||||
if (!categories[transaction.category]) {
|
||||
categories[transaction.category] = getRandomColor();
|
||||
chartData.datasets.push({
|
||||
label: transaction.category,
|
||||
data: [],
|
||||
backgroundColor: categories[transaction.category]
|
||||
});
|
||||
}
|
||||
}
|
||||
console.log('here');
|
||||
|
||||
// Populate the chart data with the transaction amounts
|
||||
for (const year in groupedTransactions) {
|
||||
chartData.labels.push(year);
|
||||
for (let i = 0; i < chartData.datasets.length; i++) {
|
||||
chartData.datasets[i].data.push(0);
|
||||
}
|
||||
for (const transaction of groupedTransactions[year]) {
|
||||
for (let i = 0; i < chartData.datasets.length; i++) {
|
||||
if (chartData.datasets[i].label === transaction.category) {
|
||||
chartData.datasets[i].data[chartData.labels.length - 1] += transaction.amount;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
return chartData;
|
||||
}
|
||||
}
|
||||
|
||||
+7
-20
@@ -1,5 +1,6 @@
|
||||
import loadServiceWorker from './loadServiceWorker';
|
||||
import Chart from 'chart.js/auto';
|
||||
import loadBankData from './loadBankData';
|
||||
import generateCharts from './generateCharts';
|
||||
|
||||
require('./assets/favicon.ico');
|
||||
require('./assets/android-chrome-192x192.png');
|
||||
@@ -12,26 +13,12 @@ require('./styles/buttons.scss');
|
||||
require('./styles/modal.scss');
|
||||
require('./styles/spinner.scss');
|
||||
|
||||
const ctx: HTMLCanvasElement = <HTMLCanvasElement>document.getElementById('myChart');
|
||||
if (localStorage.hasOwnProperty('bankData')) {
|
||||
loadBankData(JSON.parse(localStorage.getItem('bankData')).values);
|
||||
generateCharts();
|
||||
}
|
||||
|
||||
|
||||
new Chart(ctx, {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: ['Red', 'Blue', 'Yellow', 'Green', 'Purple', 'Orange'],
|
||||
datasets: [{
|
||||
label: '# of Votes',
|
||||
data: [12, 19, 3, 5, 2, 3],
|
||||
borderWidth: 1
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
loadServiceWorker();
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import { transactions } from "./bankData";
|
||||
|
||||
export default function loadBankData(data: Array<any>):void {
|
||||
// Set bankData array to be whatever this new data is
|
||||
for (let index = 0; index < data.length; index++) {
|
||||
const dateParts: Array<string> = data[index][0].split("/");
|
||||
const date: Date = new Date(Number(dateParts[2]), Number(dateParts[1])-1, Number(dateParts[0])); // JS months are zero-indexed
|
||||
const year: number = date.getFullYear();
|
||||
const month: number = date.getMonth();
|
||||
const day: number = date.getDay();
|
||||
|
||||
const transaction: BankTransaction = {
|
||||
date: data[index][0],
|
||||
dateYear: year,
|
||||
dateMonth: month + 1, // JS months are zero-indexed
|
||||
dateDay: day,
|
||||
time: data[index][1],
|
||||
type: data[index][2],
|
||||
name: data[index][3],
|
||||
category: data[index][5],
|
||||
amount: Number(data[index][6]) * -1, // Invert money out being negative and money in being positive
|
||||
currency: data[index][7],
|
||||
localAmount: Number(data[index][8]) * -1, // Invert money out being negative and money in being positive
|
||||
localCurrency: data[index][9],
|
||||
};
|
||||
|
||||
transactions.push(transaction);
|
||||
}
|
||||
}
|
||||
+3
-12
@@ -45,11 +45,6 @@
|
||||
<canvas id="myChart"></canvas>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<footer class="footer-container">
|
||||
<hr>
|
||||
<span class="footer-items">Copyright © 2022 Josh Creek</span>
|
||||
@@ -118,7 +113,7 @@
|
||||
* Enables user interaction after all libraries are loaded.
|
||||
*/
|
||||
function maybeEnableButtons() {
|
||||
if (gapiInited && gisInited) {
|
||||
if (gapiInited && gisInited && !localStorage.hasOwnProperty('bankData')) {
|
||||
document.getElementById('authorize_button').style.visibility = 'visible';
|
||||
}
|
||||
}
|
||||
@@ -132,7 +127,7 @@
|
||||
throw (resp);
|
||||
}
|
||||
document.getElementById('signout_button').style.visibility = 'visible';
|
||||
document.getElementById('authorize_button').innerText = 'Refresh';
|
||||
document.getElementById('authorize_button').innerText = 'Refresh Google Sheets data';
|
||||
await listMajors();
|
||||
};
|
||||
|
||||
@@ -177,15 +172,11 @@
|
||||
return;
|
||||
}
|
||||
const range = response.result;
|
||||
localStorage.setItem('bankData', JSON.stringify(range));
|
||||
if (!range || !range.values || range.values.length == 0) {
|
||||
document.getElementById('content').innerText = 'No values found.';
|
||||
return;
|
||||
}
|
||||
// Flatten to string to display
|
||||
const output = range.values.reduce(
|
||||
(str, row) => `${str}${row[0]}, ${row[4]}\n`,
|
||||
'Name, Major:\n');
|
||||
document.getElementById('content').innerText = output;
|
||||
}
|
||||
</script>
|
||||
<script async defer src="https://apis.google.com/js/api.js" onload="gapiLoaded()"></script>
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
type BankTransaction = {
|
||||
date: string;
|
||||
dateYear: number;
|
||||
dateMonth: number;
|
||||
dateDay: number;
|
||||
time: string;
|
||||
type: string;
|
||||
name: string;
|
||||
// emoji here
|
||||
category: string;
|
||||
amount: number;
|
||||
currency: string;
|
||||
localAmount: number;
|
||||
localCurrency: string; // Actually the foreign currency
|
||||
};
|
||||
@@ -0,0 +1,8 @@
|
||||
type ChartData = {
|
||||
labels: string[];
|
||||
datasets: {
|
||||
label: string;
|
||||
backgroundColor: string;
|
||||
data: number[];
|
||||
}[];
|
||||
};
|
||||
@@ -0,0 +1,6 @@
|
||||
type Filter = {
|
||||
year?: number;
|
||||
month?: number;
|
||||
category?: string;
|
||||
excludeIncome?: boolean;
|
||||
};
|
||||
Reference in New Issue
Block a user