update colors

This commit is contained in:
Anantha Kumaran 2022-09-10 08:41:38 +05:30
parent b92fbadcc3
commit bdac3ee430
11 changed files with 205 additions and 40 deletions

24
package-lock.json generated
View File

@ -11,6 +11,7 @@
"@types/lodash": "^4.14.181", "@types/lodash": "^4.14.181",
"@types/sprintf-js": "^1.1.2", "@types/sprintf-js": "^1.1.2",
"bulma": "^0.9.4", "bulma": "^0.9.4",
"chroma-js": "^2.4.2",
"clusterize.js": "^0.19.0", "clusterize.js": "^0.19.0",
"d3": "^7.4.0", "d3": "^7.4.0",
"d3-svg-legend": "^2.25.6", "d3-svg-legend": "^2.25.6",
@ -22,6 +23,7 @@
"tippy.js": "^6.3.7" "tippy.js": "^6.3.7"
}, },
"devDependencies": { "devDependencies": {
"@types/chroma-js": "^2.1.4",
"@typescript-eslint/eslint-plugin": "^5.22.0", "@typescript-eslint/eslint-plugin": "^5.22.0",
"@typescript-eslint/parser": "^5.22.0", "@typescript-eslint/parser": "^5.22.0",
"eslint": "^8.15.0", "eslint": "^8.15.0",
@ -173,6 +175,12 @@
"url": "https://opencollective.com/popperjs" "url": "https://opencollective.com/popperjs"
} }
}, },
"node_modules/@types/chroma-js": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/@types/chroma-js/-/chroma-js-2.1.4.tgz",
"integrity": "sha512-l9hWzP7cp7yleJUI7P2acmpllTJNYf5uU6wh50JzSIZt3fFHe+w2FM6w9oZGBTYzjjm2qHdnQvI+fF/JF/E5jQ==",
"dev": true
},
"node_modules/@types/clusterize.js": { "node_modules/@types/clusterize.js": {
"version": "0.18.1", "version": "0.18.1",
"resolved": "https://registry.npmjs.org/@types/clusterize.js/-/clusterize.js-0.18.1.tgz", "resolved": "https://registry.npmjs.org/@types/clusterize.js/-/clusterize.js-0.18.1.tgz",
@ -950,6 +958,11 @@
"fsevents": "~2.3.2" "fsevents": "~2.3.2"
} }
}, },
"node_modules/chroma-js": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chroma-js/-/chroma-js-2.4.2.tgz",
"integrity": "sha512-U9eDw6+wt7V8z5NncY2jJfZa+hUH8XEj8FQHgFJTrUFnJfXYf4Ml4adI2vXZOjqRDpFWtYVWypDfZwnJ+HIR4A=="
},
"node_modules/clusterize.js": { "node_modules/clusterize.js": {
"version": "0.19.0", "version": "0.19.0",
"resolved": "https://registry.npmjs.org/clusterize.js/-/clusterize.js-0.19.0.tgz", "resolved": "https://registry.npmjs.org/clusterize.js/-/clusterize.js-0.19.0.tgz",
@ -3188,6 +3201,12 @@
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.5.tgz", "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.5.tgz",
"integrity": "sha512-9X2obfABZuDVLCgPK9aX0a/x4jaOEweTTWE2+9sr0Qqqevj2Uv5XorvusThmc9XGYpS9yI+fhh8RTafBtGposw==" "integrity": "sha512-9X2obfABZuDVLCgPK9aX0a/x4jaOEweTTWE2+9sr0Qqqevj2Uv5XorvusThmc9XGYpS9yI+fhh8RTafBtGposw=="
}, },
"@types/chroma-js": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/@types/chroma-js/-/chroma-js-2.1.4.tgz",
"integrity": "sha512-l9hWzP7cp7yleJUI7P2acmpllTJNYf5uU6wh50JzSIZt3fFHe+w2FM6w9oZGBTYzjjm2qHdnQvI+fF/JF/E5jQ==",
"dev": true
},
"@types/clusterize.js": { "@types/clusterize.js": {
"version": "0.18.1", "version": "0.18.1",
"resolved": "https://registry.npmjs.org/@types/clusterize.js/-/clusterize.js-0.18.1.tgz", "resolved": "https://registry.npmjs.org/@types/clusterize.js/-/clusterize.js-0.18.1.tgz",
@ -3783,6 +3802,11 @@
"readdirp": "~3.6.0" "readdirp": "~3.6.0"
} }
}, },
"chroma-js": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chroma-js/-/chroma-js-2.4.2.tgz",
"integrity": "sha512-U9eDw6+wt7V8z5NncY2jJfZa+hUH8XEj8FQHgFJTrUFnJfXYf4Ml4adI2vXZOjqRDpFWtYVWypDfZwnJ+HIR4A=="
},
"clusterize.js": { "clusterize.js": {
"version": "0.19.0", "version": "0.19.0",
"resolved": "https://registry.npmjs.org/clusterize.js/-/clusterize.js-0.19.0.tgz", "resolved": "https://registry.npmjs.org/clusterize.js/-/clusterize.js-0.19.0.tgz",

View File

@ -5,6 +5,8 @@
"@types/jquery": "^3.5.14", "@types/jquery": "^3.5.14",
"@types/lodash": "^4.14.181", "@types/lodash": "^4.14.181",
"@types/sprintf-js": "^1.1.2", "@types/sprintf-js": "^1.1.2",
"bulma": "^0.9.4",
"chroma-js": "^2.4.2",
"clusterize.js": "^0.19.0", "clusterize.js": "^0.19.0",
"d3": "^7.4.0", "d3": "^7.4.0",
"d3-svg-legend": "^2.25.6", "d3-svg-legend": "^2.25.6",
@ -13,10 +15,10 @@
"jquery": "^3.6.0", "jquery": "^3.6.0",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"sprintf-js": "^1.1.2", "sprintf-js": "^1.1.2",
"tippy.js": "^6.3.7", "tippy.js": "^6.3.7"
"bulma": "^0.9.4"
}, },
"devDependencies": { "devDependencies": {
"@types/chroma-js": "^2.1.4",
"@typescript-eslint/eslint-plugin": "^5.22.0", "@typescript-eslint/eslint-plugin": "^5.22.0",
"@typescript-eslint/parser": "^5.22.0", "@typescript-eslint/parser": "^5.22.0",
"eslint": "^8.15.0", "eslint": "^8.15.0",

View File

@ -15,8 +15,10 @@ import {
secondName, secondName,
textColor, textColor,
tooltip, tooltip,
skipTicks skipTicks,
generateColorScheme
} from "./utils"; } from "./utils";
import COLORS from "./colors";
export default async function () { export default async function () {
const { const {
@ -55,7 +57,7 @@ function renderAllocationTarget(allocationTargets: AllocationTarget[]) {
const keys = ["target", "current"]; const keys = ["target", "current"];
const colorKeys = ["target", "current", "diff"]; const colorKeys = ["target", "current", "diff"];
const colors = ["#1f77b4", "#17becf", "#4a4a4a"]; const colors = [COLORS.primary, COLORS.secondary, COLORS.diff];
const y = d3.scaleBand().range([0, height]).paddingInner(0).paddingOuter(0); const y = d3.scaleBand().range([0, height]).paddingInner(0).paddingOuter(0);
y.domain(allocationTargets.map((t) => t.name)); y.domain(allocationTargets.map((t) => t.name));
@ -244,7 +246,7 @@ function renderPartition(element: HTMLElement, aggregates, hierarchy) {
return formatFloat((d.value / root.value) * 100) + "%"; return formatFloat((d.value / root.value) * 100) + "%";
}; };
const color = rainbowScale(_.keys(aggregates)); const color = generateColorScheme(_.keys(aggregates));
const stratify = d3 const stratify = d3
.stratify<Aggregate>() .stratify<Aggregate>()
@ -373,7 +375,7 @@ function renderAllocationTimeline(
0, 0,
d3.max(d3.map(points, (p) => d3.max(_.values(_.omit(p, "date"))))) d3.max(d3.map(points, (p) => d3.max(_.values(_.omit(p, "date")))))
]), ]),
z = d3.scaleOrdinal(d3.schemeCategory10).domain(assets); z = generateColorScheme(assets);
const line = (group) => const line = (group) =>
d3 d3

11
web/src/colors.ts Normal file
View File

@ -0,0 +1,11 @@
const COLORS = {
gain: "#b2df8a",
gainText: "#48c78e",
loss: "#fb9a99",
lossText: "#f14668",
primary: "#1f77b4",
secondary: "#17becf",
tertiary: "#ff7f0e",
diff: "#4a4a4a"
};
export default COLORS;

View File

@ -1,7 +1,7 @@
import * as d3 from "d3"; import * as d3 from "d3";
import legend from "d3-svg-legend"; import legend from "d3-svg-legend";
import { sprintf } from "sprintf-js";
import dayjs, { Dayjs } from "dayjs"; import dayjs, { Dayjs } from "dayjs";
import chroma from "chroma-js";
import _ from "lodash"; import _ from "lodash";
import { import {
ajax, ajax,
@ -14,8 +14,10 @@ import {
secondName, secondName,
setHtml, setHtml,
skipTicks, skipTicks,
tooltip tooltip,
generateColorScheme
} from "./utils"; } from "./utils";
import COLORS from "./colors";
export default async function () { export default async function () {
const { const {
@ -82,10 +84,10 @@ function renderSelectedMonth(
investments: Posting[] investments: Posting[]
) { ) {
renderer(expenses); renderer(expenses);
setHtml("current-month-income", sum(incomes, -1)); setHtml("current-month-income", sum(incomes, -1), COLORS.gainText);
setHtml("current-month-tax", sum(taxes)); setHtml("current-month-tax", sum(taxes), COLORS.lossText);
setHtml("current-month-expenses", sum(expenses)); setHtml("current-month-expenses", sum(expenses), COLORS.lossText);
setHtml("current-month-investment", sum(investments)); setHtml("current-month-investment", sum(investments), COLORS.secondary);
} }
function sum(postings: Posting[], sign = 1) { function sum(postings: Posting[], sign = 1) {
@ -159,7 +161,7 @@ function renderMonthlyExpensesTimeline(
x.domain(points.map((p) => p.month)); x.domain(points.map((p) => p.month));
y.domain([0, d3.max(points, sum)]); y.domain([0, d3.max(points, sum)]);
const z = d3.scaleOrdinal<string>().range(d3.schemeCategory10); const z = generateColorScheme(groups);
g.append("g") g.append("g")
.attr("class", "axis x") .attr("class", "axis x")
@ -401,8 +403,8 @@ function renderCurrentExpensesBreakdown(
.style("white-space", "pre") .style("white-space", "pre")
.style("font-size", "13px") .style("font-size", "13px")
.style("font-weight", "bold") .style("font-weight", "bold")
.attr("fill", function (d) { .style("fill", function (d) {
return z(d.category); return chroma(z(d.category)).darken(0.8).hex();
}) })
.attr("class", "is-family-monospace") .attr("class", "is-family-monospace")
.text( .text(

View File

@ -1,7 +1,9 @@
import chroma from "chroma-js";
import * as d3 from "d3"; import * as d3 from "d3";
import legend from "d3-svg-legend"; import legend from "d3-svg-legend";
import dayjs from "dayjs"; import dayjs from "dayjs";
import _ from "lodash"; import _ from "lodash";
import COLORS from "./colors";
import { import {
ajax, ajax,
formatCurrency, formatCurrency,
@ -26,13 +28,13 @@ export default async function () {
} }
const areaKeys = ["gain", "loss"]; const areaKeys = ["gain", "loss"];
const colors = ["#b2df8a", "#fb9a99"]; const colors = [COLORS.gain, COLORS.loss];
const areaScale = d3.scaleOrdinal<string>().domain(areaKeys).range(colors); const areaScale = d3.scaleOrdinal<string>().domain(areaKeys).range(colors);
const lineKeys = ["balance", "investment", "withdrawal"]; const lineKeys = ["balance", "investment", "withdrawal"];
const lineScale = d3 const lineScale = d3
.scaleOrdinal<string>() .scaleOrdinal<string>()
.domain(lineKeys) .domain(lineKeys)
.range(["#1f77b4", "#17becf", "#ff7f0e"]); .range([COLORS.primary, COLORS.secondary, COLORS.tertiary]);
function renderTable(gain: Gain) { function renderTable(gain: Gain) {
const tbody = d3.select(this); const tbody = d3.select(this);
@ -95,7 +97,13 @@ function renderOverview(gains: Gain[]) {
.paddingOuter(0.1); .paddingOuter(0.1);
const keys = ["balance", "investment", "withdrawal", "gain", "loss"]; const keys = ["balance", "investment", "withdrawal", "gain", "loss"];
const colors = ["#1f77b4", "#17becf", "#ff7f0e", "#b2df8a", "#fb9a99"]; const colors = [
COLORS.primary,
COLORS.secondary,
COLORS.tertiary,
COLORS.gain,
COLORS.loss
];
const z = d3.scaleOrdinal<string>(colors).domain(keys); const z = d3.scaleOrdinal<string>(colors).domain(keys);
const getInvestmentAmount = (g: Gain) => const getInvestmentAmount = (g: Gain) =>
@ -203,7 +211,9 @@ function renderOverview(gains: Gain[]) {
.text((g) => formatCurrency(getGainAmount(g))) .text((g) => formatCurrency(getGainAmount(g)))
.attr("alignment-baseline", "hanging") .attr("alignment-baseline", "hanging")
.attr("text-anchor", "end") .attr("text-anchor", "end")
.style("fill", (g) => (getGainAmount(g) > 0 ? z("gain") : "none")) .style("fill", (g) =>
getGainAmount(g) > 0 ? chroma(z("gain")).darken().hex() : "none"
)
.attr("dx", "-3") .attr("dx", "-3")
.attr("dy", "3") .attr("dy", "3")
.attr("x", textGroupZero + (textGroupWidth * 2) / 3) .attr("x", textGroupZero + (textGroupWidth * 2) / 3)
@ -223,7 +233,9 @@ function renderOverview(gains: Gain[]) {
.append("text") .append("text")
.text((g) => formatCurrency(getGainAmount(g))) .text((g) => formatCurrency(getGainAmount(g)))
.attr("text-anchor", "end") .attr("text-anchor", "end")
.style("fill", (g) => (getGainAmount(g) < 0 ? z("loss") : "none")) .style("fill", (g) =>
getGainAmount(g) < 0 ? chroma(z("loss")).darken().hex() : "none"
)
.attr("dx", "-3") .attr("dx", "-3")
.attr("dy", "-3") .attr("dy", "-3")
.attr("x", textGroupZero + (textGroupWidth * 2) / 3) .attr("x", textGroupZero + (textGroupWidth * 2) / 3)
@ -254,7 +266,11 @@ function renderOverview(gains: Gain[]) {
.text((g) => formatFloat(g.xirr)) .text((g) => formatFloat(g.xirr))
.attr("text-anchor", "end") .attr("text-anchor", "end")
.attr("alignment-baseline", "middle") .attr("alignment-baseline", "middle")
.style("fill", (g) => (g.xirr < 0 ? z("loss") : z("gain"))) .style("fill", (g) =>
g.xirr < 0
? chroma(z("loss")).darken().hex()
: chroma(z("gain")).darken().hex()
)
.attr("x", xirrWidth + xirrTextWidth) .attr("x", xirrWidth + xirrTextWidth)
.attr("y", (g) => y(restName(g.account)) + y.bandwidth() / 2); .attr("y", (g) => y(restName(g.account)) + y.bandwidth() / 2);

View File

@ -2,10 +2,12 @@ import * as d3 from "d3";
import legend from "d3-svg-legend"; import legend from "d3-svg-legend";
import dayjs from "dayjs"; import dayjs from "dayjs";
import _ from "lodash"; import _ from "lodash";
import COLORS from "./colors";
import { import {
ajax, ajax,
formatCurrency, formatCurrency,
formatCurrencyCrude, formatCurrencyCrude,
generateColorScheme,
Income, Income,
Posting, Posting,
restName, restName,
@ -27,8 +29,8 @@ export default async function () {
const netTax = _.sumBy(taxes, (t) => _.sumBy(t.postings, (p) => p.amount)); const netTax = _.sumBy(taxes, (t) => _.sumBy(t.postings, (p) => p.amount));
setHtml("gross-income", formatCurrency(grossIncome)); setHtml("gross-income", formatCurrency(grossIncome), COLORS.gainText);
setHtml("net-tax", formatCurrency(netTax)); setHtml("net-tax", formatCurrency(netTax), COLORS.lossText);
} }
function renderMonthlyInvestmentTimeline(incomes: Income[]) { function renderMonthlyInvestmentTimeline(incomes: Income[]) {
@ -119,7 +121,7 @@ function renderIncomeTimeline(
) )
]); ]);
const z = d3.scaleOrdinal<string>().range(d3.schemeCategory10); const z = generateColorScheme(groupKeys);
g.append("g") g.append("g")
.attr("class", "axis x") .attr("class", "axis x")

View File

@ -8,6 +8,7 @@ import {
formatCurrency, formatCurrency,
formatCurrencyCrude, formatCurrencyCrude,
formatFloat, formatFloat,
generateColorScheme,
Posting, Posting,
secondName, secondName,
skipTicks, skipTicks,
@ -132,7 +133,7 @@ function renderMonthlyInvestmentTimeline(postings: Posting[]) {
) )
]); ]);
const z = d3.scaleOrdinal<string>().range(d3.schemeCategory10); const z = generateColorScheme(groups);
g.append("g") g.append("g")
.attr("class", "axis x") .attr("class", "axis x")
@ -312,7 +313,7 @@ function renderYearlyInvestmentTimeline(yearlyCards: YearlyCard[]) {
) )
]); ]);
const z = d3.scaleOrdinal<string>().range(d3.schemeCategory10); const z = generateColorScheme(groups);
g.append("g") g.append("g")
.attr("class", "axis y") .attr("class", "axis y")

View File

@ -2,6 +2,7 @@ import * as d3 from "d3";
import legend from "d3-svg-legend"; import legend from "d3-svg-legend";
import dayjs from "dayjs"; import dayjs from "dayjs";
import _ from "lodash"; import _ from "lodash";
import COLORS from "./colors";
import { import {
ajax, ajax,
formatCurrency, formatCurrency,
@ -22,13 +23,19 @@ export default async function () {
current.investment_amount + current.investment_amount +
current.gain_amount - current.gain_amount -
current.withdrawal_amount current.withdrawal_amount
) ),
COLORS.primary
); );
setHtml( setHtml(
"investment", "investment",
formatCurrency(current.investment_amount - current.withdrawal_amount) formatCurrency(current.investment_amount - current.withdrawal_amount),
COLORS.secondary
);
setHtml(
"gains",
formatCurrency(current.gain_amount),
current.gain_amount >= 0 ? COLORS.gainText : COLORS.lossText
); );
setHtml("gains", formatCurrency(current.gain_amount));
setHtml("xirr", formatFloat(xirr)); setHtml("xirr", formatFloat(xirr));
renderOverview(points, document.getElementById("d3-overview-timeline")); renderOverview(points, document.getElementById("d3-overview-timeline"));
@ -47,7 +54,7 @@ function renderOverview(points: Overview[], element: Element) {
.attr("transform", "translate(" + margin.left + "," + margin.top + ")"); .attr("transform", "translate(" + margin.left + "," + margin.top + ")");
const areaKeys = ["gain", "loss"]; const areaKeys = ["gain", "loss"];
const colors = ["#b2df8a", "#fb9a99"]; const colors = [COLORS.gain, COLORS.loss];
const areaScale = d3.scaleOrdinal().domain(areaKeys).range(colors); const areaScale = d3.scaleOrdinal().domain(areaKeys).range(colors);
const lineKeys = ["networth", "investment"]; const lineKeys = ["networth", "investment"];
@ -55,7 +62,7 @@ function renderOverview(points: Overview[], element: Element) {
const lineScale = d3 const lineScale = d3
.scaleOrdinal<string>() .scaleOrdinal<string>()
.domain(lineKeys) .domain(lineKeys)
.range(["#1f77b4", "#17becf", "#ff7f0e"]); .range([COLORS.primary, COLORS.secondary]);
const positions = _.flatMap(points, (p) => [ const positions = _.flatMap(points, (p) => [
p.gain_amount + p.investment_amount - p.withdrawal_amount, p.gain_amount + p.investment_amount - p.withdrawal_amount,

View File

@ -1,3 +1,4 @@
import chroma from "chroma-js";
import dayjs from "dayjs"; import dayjs from "dayjs";
import { sprintf } from "sprintf-js"; import { sprintf } from "sprintf-js";
import _ from "lodash"; import _ from "lodash";
@ -243,8 +244,106 @@ export function skipTicks<Domain>(
}; };
} }
export function setHtml(selector: string, value: string) { export function generateColorScheme(domain: string[]) {
const node = document.querySelector(".d3-" + selector); let colors: string[];
const n = domain.length;
if (n <= 12) {
colors = {
1: ["#7570b3"],
2: ["#7fc97f", "#fdc086"],
3: ["#66c2a5", "#fc8d62", "#8da0cb"],
4: ["#66c2a5", "#fc8d62", "#8da0cb", "#e78ac3"],
5: ["#66c2a5", "#fc8d62", "#8da0cb", "#e78ac3", "#a6d854"],
6: ["#66c2a5", "#fc8d62", "#8da0cb", "#e78ac3", "#a6d854", "#ffd92f"],
7: [
"#8dd3c7",
"#ffed6f",
"#bebada",
"#fb8072",
"#80b1d3",
"#fdb462",
"#b3de69"
],
8: [
"#8dd3c7",
"#ffed6f",
"#bebada",
"#fb8072",
"#80b1d3",
"#fdb462",
"#b3de69",
"#fccde5"
],
9: [
"#8dd3c7",
"#ffed6f",
"#bebada",
"#fb8072",
"#80b1d3",
"#fdb462",
"#b3de69",
"#fccde5",
"#d9d9d9"
],
10: [
"#8dd3c7",
"#ffed6f",
"#bebada",
"#fb8072",
"#80b1d3",
"#fdb462",
"#b3de69",
"#fccde5",
"#d9d9d9",
"#bc80bd"
],
11: [
"#8dd3c7",
"#ffed6f",
"#bebada",
"#fb8072",
"#80b1d3",
"#fdb462",
"#b3de69",
"#fccde5",
"#d9d9d9",
"#bc80bd",
"#ccebc5"
],
12: [
"#8dd3c7",
"#ffed6f",
"#bebada",
"#fb8072",
"#80b1d3",
"#fdb462",
"#b3de69",
"#fccde5",
"#d9d9d9",
"#bc80bd",
"#ccebc5",
"#ffed6f"
]
}[n];
} else {
const z = d3
.scaleSequential()
.domain([0, n - 1])
.interpolator(d3.interpolateSinebow);
colors = _.map(_.range(0, n), (n) => chroma(z(n)).desaturate(1.5).hex());
}
return d3.scaleOrdinal<string>().domain(domain).range(colors);
}
export function setHtml(selector: string, value: string, color?: string) {
const node: HTMLElement = document.querySelector(".d3-" + selector);
if (color) {
node.style.backgroundColor = color;
node.style.padding = "5px";
node.style.color = "white";
}
node.innerHTML = value; node.innerHTML = value;
} }

View File

@ -58,7 +58,7 @@
</div> </div>
<div class="level-item has-text-centered"> <div class="level-item has-text-centered">
<div> <div>
<p class="heading">Gain</p> <p class="heading">Gain / Loss</p>
<p class="d3-gains title"></p> <p class="d3-gains title"></p>
</div> </div>
</div> </div>
@ -363,10 +363,9 @@
<section class="section tab-expense"> <section class="section tab-expense">
<div class="container is-fluid"> <div class="container is-fluid">
<div class="columns"> <div class="columns">
<div class="column is-full"> <div class="column is-full pb-0">
<div class="p-3 has-text-centered"> <div class="has-text-right">
<input style="width: 250px" class="input is-medium is-size-4" required type="month" id="d3-current-month"> <input style="width: 175px" class="input is-medium is-size-6" required type="month" id="d3-current-month">
<p class="heading d3-current-month is-size-4"></p>
</div> </div>
</div> </div>
</div> </div>
@ -389,13 +388,13 @@
<div class="level-item has-text-centered"> <div class="level-item has-text-centered">
<div> <div>
<p class="heading">Net Investment</p> <p class="heading">Net Investment</p>
<p class="d3-current-month-investment title has-text-success"></p> <p class="d3-current-month-investment title"></p>
</div> </div>
</div> </div>
<div class="level-item has-text-centered"> <div class="level-item has-text-centered">
<div> <div>
<p class="heading">Expenses</p> <p class="heading">Expenses</p>
<p class="d3-current-month-expenses title has-text-danger"></p> <p class="d3-current-month-expenses title"></p>
</div> </div>
</div> </div>
</nav> </nav>