Skip to content

Commit

Permalink
add cash flow diagram
Browse files Browse the repository at this point in the history
  • Loading branch information
ananthakumaran committed Apr 22, 2023
1 parent 7262592 commit c7657c5
Show file tree
Hide file tree
Showing 12 changed files with 445 additions and 9 deletions.
80 changes: 79 additions & 1 deletion internal/server/expense.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,47 @@
package server

import (
"math"

"github.com/ananthakumaran/paisa/internal/model/posting"
"github.com/ananthakumaran/paisa/internal/query"
"github.com/gin-gonic/gin"
"github.com/samber/lo"
"gorm.io/gorm"
)

type Node struct {
ID uint `json:"id"`
Name string `json:"name"`
}

type Link struct {
Source uint `json:"source"`
Target uint `json:"target"`
Value float64 `json:"value"`
}

type Pair struct {
Source uint `json:"source"`
Target uint `json:"target"`
}

type Graph struct {
Nodes []Node `json:"nodes"`
Links []Link `json:"links"`
}

func GetExpense(db *gorm.DB) gin.H {
expenses := query.Init(db).Like("Expenses:%").NotLike("Expenses:Tax").All()
incomes := query.Init(db).Like("Income:%").All()
investments := query.Init(db).Like("Assets:%").NotLike("Assets:Checking").All()
taxes := query.Init(db).Like("Expenses:Tax").All()
postings := query.Init(db).All()

graph := make(map[string]Graph)
for fy, ps := range posting.GroupByFY(postings) {
graph[fy] = computeGraph(ps)
}

return gin.H{
"expenses": expenses,
Expand All @@ -24,5 +54,53 @@ func GetExpense(db *gorm.DB) gin.H {
"expenses": posting.GroupByFY(expenses),
"incomes": posting.GroupByFY(incomes),
"investments": posting.GroupByFY(investments),
"taxes": posting.GroupByFY(taxes)}}
"taxes": posting.GroupByFY(taxes)},
"graph": graph}
}

func computeGraph(postings []posting.Posting) Graph {
nodes := make(map[string]Node)
links := make(map[Pair]float64)

var nodeID uint = 0

grouped := lo.GroupBy(postings, func(p posting.Posting) string { return p.TransactionID })
transactions := lo.Map(lo.Values(grouped), func(ps []posting.Posting, _ int) Transaction {
sample := ps[0]
return Transaction{ID: sample.TransactionID, Date: sample.Date, Payee: sample.Payee, Postings: ps}
})

for _, p := range postings {
_, ok := nodes[p.Account]
if !ok {
nodeID++
nodes[p.Account] = Node{ID: nodeID, Name: p.Account}
}

}

for _, t := range transactions {
from := lo.Filter(t.Postings, func(p posting.Posting, _ int) bool { return p.Amount < 0 })
to := lo.Filter(t.Postings, func(p posting.Posting, _ int) bool { return p.Amount > 0 })

for _, f := range from {
for math.Abs(f.Amount) > 0.1 && len(to) > 0 {
top := to[0]
if top.Amount > -f.Amount {
links[Pair{Source: nodes[f.Account].ID, Target: nodes[top.Account].ID}] += -f.Amount
top.Amount -= f.Amount
f.Amount = 0
} else {
links[Pair{Source: nodes[f.Account].ID, Target: nodes[top.Account].ID}] += top.Amount
f.Amount += top.Amount
to = to[1:]
}
}
}
}

return Graph{Nodes: lo.Values(nodes), Links: lo.Map(lo.Keys(links), func(k Pair, _ int) Link {
return Link{Source: k.Source, Target: k.Target, Value: links[k]}
})}

}
126 changes: 126 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
"codemirror": "^6.0.1",
"d3": "^7.4.0",
"d3-delaunay": "^6.0.2",
"d3-path-arrows": "github:tomshanley/d3-path-arrows",
"d3-sankey-circular": "github:tomshanley/d3-sankey-circular",
"d3-svg-legend": "^2.25.6",
"dayjs": "^1.11.0",
"esbuild": "^0.14.29",
Expand Down
9 changes: 9 additions & 0 deletions src/app.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,12 @@ declare module "arima/async" {
const P: Promise<typeof Arima>;
export default P;
}

declare module "d3-sankey-circular" {
export function sankeyCircular(): any;
export function sankeyJustify(): any;
}

declare module "d3-path-arrows" {
export function pathArrows(): any;
}
8 changes: 8 additions & 0 deletions src/app.scss
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ body {
border-left: 1px solid hsl(0deg, 0%, 86%);
}

svg text {
font-family: $family-sans-serif;
}

.axis {
font-family: $family-sans-serif;

Expand Down Expand Up @@ -93,6 +97,10 @@ body {
}
}

.g-arrow path {
opacity: 0.5;
}

.legendOrdinal .label,
.legendLine .label {
display: block;
Expand Down
3 changes: 2 additions & 1 deletion src/lib/components/Navbar.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@
href: "/expense",
children: [
{ label: "Monthly", href: "/monthly", monthPicker: true },
{ label: "Yearly", href: "/yearly", financialYearPicker: true }
{ label: "Yearly", href: "/yearly", financialYearPicker: true },
{ label: "Cash Flow", href: "/flow", tag: "alpha", financialYearPicker: true }
]
},
{
Expand Down
Loading

0 comments on commit c7657c5

Please sign in to comment.