From d6818629fb499bbf725bf496042d5bf0f584991b Mon Sep 17 00:00:00 2001 From: Anantha Kumaran Date: Sat, 23 Apr 2022 09:18:31 +0530 Subject: [PATCH] split withdrawal, investment, and networth in overview page --- Makefile | 2 +- cmd/root.go | 11 ++--- internal/server/overview.go | 21 ++++++--- web/src/overview.ts | 90 ++++++++++++++++++++++++++++--------- web/src/utils.ts | 5 ++- web/static/index.html | 8 +++- 6 files changed, 102 insertions(+), 35 deletions(-) diff --git a/Makefile b/Makefile index 01cc877..a46e3e3 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ .PHONY: docs serve: - ./node_modules/.bin/nodemon --signal SIGTERM --watch '.' --ext go --exec 'go run . serve || exit 1' + ./node_modules/.bin/nodemon --signal SIGTERM --watch '.' --ext go,js,css,html --exec 'go run . serve || exit 1' watch: ./node_modules/.bin/esbuild web/src/index.ts --bundle --watch --sourcemap --outfile=web/static/dist.js docs: diff --git a/cmd/root.go b/cmd/root.go index c3bcb0f..99faee6 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -8,7 +8,7 @@ import ( "github.com/spf13/viper" ) -var cfgFile string +var configFile string var rootCmd = &cobra.Command{ Use: "paisa", @@ -24,7 +24,7 @@ func Execute() { func init() { cobra.OnInitialize(initConfig) - rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is ./paisa.yaml)") + rootCmd.PersistentFlags().StringVar(&configFile, "config", "", "config file (default is ./paisa.yaml)") } func initConfig() { @@ -35,10 +35,11 @@ func initConfig() { return } - if cfgFile != "" { - viper.SetConfigFile(cfgFile) + if envConfigFile := os.Getenv("PAISA_CONFIG"); envConfigFile != "" { + viper.SetConfigFile(envConfigFile) + } else if configFile != "" { + viper.SetConfigFile(configFile) } else { - viper.SetConfigName("paisa.yaml") viper.SetConfigType("yaml") viper.AddConfigPath(".") diff --git a/internal/server/overview.go b/internal/server/overview.go index 2b1cee4..c0263d0 100644 --- a/internal/server/overview.go +++ b/internal/server/overview.go @@ -13,9 +13,10 @@ import ( ) type Networth struct { - Date time.Time `json:"date"` - Actual float64 `json:"actual"` - Gain float64 `json:"gain"` + Date time.Time `json:"date"` + InvestmentAmount float64 `json:"investment_amount"` + WithdrawalAmount float64 `json:"withdrawal_amount"` + GainAmount float64 `json:"gain_amount"` } func GetOverview(db *gorm.DB) gin.H { @@ -41,14 +42,22 @@ func ComputeTimeline(db *gorm.DB, postings []posting.Posting) []Networth { pastPostings = append(pastPostings, p) } - actual := lo.Reduce(pastPostings, func(agg float64, p posting.Posting, _ int) float64 { - if service.IsInterest(db, p) { + investment := lo.Reduce(pastPostings, func(agg float64, p posting.Posting, _ int) float64 { + if p.Amount < 0 || service.IsInterest(db, p) { return agg } else { return p.Amount + agg } }, 0) + withdrawal := lo.Reduce(pastPostings, func(agg float64, p posting.Posting, _ int) float64 { + if p.Amount > 0 || service.IsInterest(db, p) { + return agg + } else { + return -p.Amount + agg + } + }, 0) + gain := lo.Reduce(pastPostings, func(agg float64, p posting.Posting, _ int) float64 { if service.IsInterest(db, p) { return p.Amount + agg @@ -56,7 +65,7 @@ func ComputeTimeline(db *gorm.DB, postings []posting.Posting) []Networth { return service.GetMarketPrice(db, p, start) - p.Amount + agg } }, 0) - networths = append(networths, Networth{Date: start, Actual: actual, Gain: gain}) + networths = append(networths, Networth{Date: start, InvestmentAmount: investment, WithdrawalAmount: withdrawal, GainAmount: gain}) } return networths } diff --git a/web/src/overview.ts b/web/src/overview.ts index 6c09aa8..caa488f 100644 --- a/web/src/overview.ts +++ b/web/src/overview.ts @@ -1,4 +1,5 @@ import * as d3 from "d3"; +window.d3 = d3; import legend from "d3-svg-legend"; import dayjs from "dayjs"; import _ from "lodash"; @@ -15,9 +16,17 @@ export default async function () { _.each(points, (n) => (n.timestamp = dayjs(n.date))); const current = _.last(points); - setHtml("networth", formatCurrency(current.actual + current.gain)); - setHtml("investment", formatCurrency(current.actual)); - setHtml("gains", formatCurrency(current.gain)); + setHtml( + "networth", + formatCurrency( + current.investment_amount + + current.gain_amount - + current.withdrawal_amount + ) + ); + setHtml("investment", formatCurrency(current.investment_amount)); + setHtml("withdrawal", formatCurrency(current.withdrawal_amount)); + setHtml("gains", formatCurrency(current.gain_amount)); const start = _.min(_.map(points, (p) => p.timestamp)), end = dayjs(); @@ -34,22 +43,28 @@ export default async function () { .append("g") .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); - const keys = ["gain", "loss"]; - const colors = d3.schemeSet2; - const ordinal = d3.scaleOrdinal().domain(keys).range(colors); + const areaKeys = ["gain", "loss"]; + const colors = ["#b2df8a", "#fb9a99"]; + const areaScale = d3.scaleOrdinal().domain(areaKeys).range(colors); + + const lineKeys = ["networth", "investment", "withdrawal"]; + const lineScale = d3 + .scaleOrdinal() + .domain(lineKeys) + .range(["#1f77b4", "#17becf", "#ff7f0e"]); svg .append("g") .attr("class", "legendOrdinal") - .attr("transform", "translate(165,3)"); + .attr("transform", "translate(365,3)"); const legendOrdinal = legend .legendColor() .shape("rect") .orient("horizontal") .shapePadding(50) - .labels(keys) - .scale(ordinal); + .labels(areaKeys) + .scale(areaScale); svg.select(".legendOrdinal").call(legendOrdinal as any); @@ -62,12 +77,12 @@ export default async function () { .legendColor() .shape("rect") .orient("horizontal") - .shapePadding(0) + .shapePadding(70) .labelOffset(22) .shapeHeight(3) .shapeWidth(25) - .labels(["investment"]) - .scale(d3.scaleOrdinal().domain(["investment"]).range(["#333"])); + .labels(lineKeys) + .scale(lineScale); svg.select(".legendLine").call(legendLine as any); @@ -75,8 +90,14 @@ export default async function () { y = d3 .scaleLinear() .range([height, 0]) - .domain([0, d3.max(points, (d) => d.gain + d.actual)]), - z = d3.scaleOrdinal(colors).domain(keys); + .domain([ + 0, + d3.max( + points, + (d) => d.gain_amount + d.investment_amount + ) + ]), + z = d3.scaleOrdinal(colors).domain(areaKeys); let area = (y0, y1) => d3 @@ -120,7 +141,7 @@ export default async function () { .attr( "d", area(height, (d) => { - return y(d.gain + d.actual); + return y(d.gain_amount + d.investment_amount); }) ); @@ -131,7 +152,7 @@ export default async function () { .attr( "d", area(0, (d) => { - return y(d.gain + d.actual); + return y(d.gain_amount + d.investment_amount); }) ); @@ -142,10 +163,11 @@ export default async function () { `url(${new URL("#clip-above", window.location.toString())})` ) .style("fill", z("gain")) + .style("opacity", "0.8") .attr( "d", area(0, (d) => { - return y(d.actual); + return y(d.investment_amount); }) ); @@ -156,16 +178,17 @@ export default async function () { `url(${new URL("#clip-below", window.location.toString())})` ) .style("fill", z("loss")) + .style("opacity", "0.8") .attr( "d", area(height, (d) => { - return y(d.actual); + return y(d.investment_amount); }) ); layer .append("path") - .style("stroke", "#333") + .style("stroke", lineScale("investment")) .style("fill", "none") .attr( "d", @@ -173,6 +196,33 @@ export default async function () { .line() .curve(d3.curveBasis) .x((d) => x(d.timestamp)) - .y((d) => y(d.actual)) + .y((d) => y(d.investment_amount)) + ); + + layer + .append("path") + .style("stroke", lineScale("withdrawal")) + .style("fill", "none") + .attr( + "d", + d3 + .line() + .curve(d3.curveBasis) + .defined((d) => d.withdrawal_amount > 0) + .x((d) => x(d.timestamp)) + .y((d) => y(d.withdrawal_amount)) + ); + + layer + .append("path") + .style("stroke", lineScale("networth")) + .style("fill", "none") + .attr( + "d", + d3 + .line() + .curve(d3.curveBasis) + .x((d) => x(d.timestamp)) + .y((d) => y(d.investment_amount + d.gain_amount - d.withdrawal_amount)) ); } diff --git a/web/src/utils.ts b/web/src/utils.ts index 9ff79fd..70193af 100644 --- a/web/src/utils.ts +++ b/web/src/utils.ts @@ -19,8 +19,9 @@ export interface Posting { export interface Networth { date: string; - actual: number; - gain: number; + investment_amount: number; + withdrawal_amount: number; + gain_amount: number; timestamp: dayjs.Dayjs; } diff --git a/web/static/index.html b/web/static/index.html index 161c32a..4d0bb90 100644 --- a/web/static/index.html +++ b/web/static/index.html @@ -43,6 +43,12 @@

+
+
+

Withdrawal

+

+
+

Gain

@@ -56,7 +62,7 @@
- +