feat(wpcarro/ynabsql): Proof-of-concept demo

Hacked this together during my week-off while I was in Telluride, CO. The git
history is quite sloppy; so is some of the code. But it (mostly) works as a
demo, and that was the point.

Change-Id: Icfbc277090b69a802c00becdbd162652e4e8e156
Reviewed-on: https://cl.tvl.fyi/c/depot/+/7904
Reviewed-by: wpcarro <wpcarro@gmail.com>
Tested-by: BuildkiteCI
Autosubmit: wpcarro <wpcarro@gmail.com>
This commit is contained in:
William Carroll 2023-01-22 21:14:38 -08:00 committed by clbot
parent 9f75973e4a
commit b3a91ce57b
10 changed files with 3833 additions and 149 deletions

View file

@ -0,0 +1,5 @@
/dist
/node_modules
/.parcel-cache
/cdn
/data.js

View file

@ -0,0 +1,3 @@
{
"extends": "@parcel/config-default"
}

View file

@ -0,0 +1 @@
/tmp/cdn

View file

@ -0,0 +1,66 @@
const colors = {
red: 'rgb(255, 45, 70)',
green: 'rgb(75, 192, 35)',
};
////////////////////////////////////////////////////////////////////////////////
// Main
////////////////////////////////////////////////////////////////////////////////
const mount = document.getElementById('mount');
const chart = new Chart(mount, {
type: 'scatter',
data: {
datasets: [
{
label: 'Revenue',
data: data.data.transactions.filter(x => x.Inflow > 0).map(x => ({
x: x.Date,
y: x.Inflow,
metadata: x,
})),
backgroundColor: colors.green,
},
{
label: 'Expenses',
data: data.data.transactions.filter(x => x.Outflow).map(x => ({
x: x.Date,
y: x.Outflow,
metadata: x,
})),
backgroundColor: colors.red,
},
],
},
options: {
scales: {
x: {
type: 'time',
title: {
display: true,
text: 'Date',
},
},
y: {
title: {
display: true,
text: 'Amount ($USD)'
},
},
},
plugins: {
tooltip: {
callbacks: {
title: function(x) {
return `$${x[0].raw.y} (${x[0].raw.metadata.Date.toLocaleDateString()})`;
},
label: function(x) {
const { Category, Payee, Memo } = x.raw.metadata;
return `${Payee} - ${Category} (${Memo})`;
},
},
},
},
},
});

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -2,24 +2,28 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="stylesheet" href="https://unpkg.com/terminal.css@0.7.2/dist/terminal.min.css" />
<link rel="stylesheet" href="https://unpkg.com/terminal.css@0.7.1/dist/terminal.min.css" />
<link rel="stylesheet" href="https://unpkg.com/terminal.css@0.7.1/dist/terminal.min.css" />
<!-- TODO(wpcarro): Cache these locally -->
<link rel="stylesheet" href="./cdn/terminal.min.css" />
<style>
:root {
--page-width: 100em;
}
</style>
</head>
<body>
<div id="react-mount"></div>
<!-- <canvas id="mount"></canvas> -->
<body class="container">
<div id="mount"></div>
<!-- chart.js -->
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="./cdn/chart.js"></script>
<script src="./cdn/date_fns.js"></script>
<script src="./cdn/chartjs-adapter-date-fns.bundle.min.js"></script>
<!-- react.js -->
<script src="https://unpkg.com/react@18/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
<script src="./cdn/react.development.js" crossorigin></script>
<script src="./cdn/react-dom.development.js" crossorigin></script>
<script src="./cdn/babel.min.js"></script>
<!-- depot JS -->
<script src="http://localhost:8002/index.js"></script>
<script src="./cdn/slx.js"></script>
<script src="./data.js"></script>
<script src="./index.js"></script>
<script src="./components.js" type="text/babel"></script>
</body>
</html>

View file

@ -1,72 +0,0 @@
const colors = {
red: 'rgb(255, 99, 132)',
orange: 'rgb(255, 159, 64)',
yellow: 'rgb(255, 205, 86)',
green: 'rgb(75, 192, 192)',
blue: 'rgb(54, 162, 235)',
purple: 'rgb(153, 102, 255)',
grey: 'rgb(201, 203, 207)'
};
function randomExpense() {
// 10/1000 expenses are O(1,000)
// 100/2000 expenses are O(100)
// 1,000/2000 expenses are O(10)
// 10,000/2000 expenses are O(1)
const r = Math.random();
if (r <= 0.02) {
return Math.floor(Math.random() * 5000);
} else if (r <= 0.1) {
return Math.floor(Math.random() * 1000);
} else if (r <= 0.5) {
return Math.floor(Math.random() * 100);
} else {
return Math.floor(Math.random() * 10);
}
}
// Browser starts to choke around 10,000 data points.
function generateData() {
return Array(2000).fill(0).map(x => ({
// select a random day [0, 365]
x: Math.floor(Math.random() * 365),
// select a random USD amount in the range [1, 5,000]
y: randomExpense(),
// TODO(wpcarro): Attach transaction to `metadata` key.
metadata: { foo: 'bar' },
}));
}
////////////////////////////////////////////////////////////////////////////////
// Main
////////////////////////////////////////////////////////////////////////////////
const mount = document.getElementById('mount');
new Chart(mount, {
type: 'scatter',
data: {
datasets: [
{
label: 'Expenses',
data: generateData(),
backgroundColor: colors.red,
}
],
},
options: {
plugins: {
tooltip: {
callbacks: {
title: function(x) {
return `$${x[0].raw.y}`;
},
label: function(x) {
return JSON.stringify(x.raw.metadata);
},
},
},
},
},
})

View file

@ -0,0 +1,15 @@
{
"devDependencies": {
"@parcel/validator-typescript": "^2.8.3",
"parcel": "^2.8.3",
"process": "^0.11.10",
"typescript": ">=3.0.0"
},
"dependencies": {
"chart.js": "^4.2.0",
"chartjs-adapter-date-fns": "^3.0.0",
"date-fns": "^2.29.3",
"react": "^18.2.0",
"react-dom": "^18.2.0"
}
}

File diff suppressed because it is too large Load diff