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/sprintf-js": "^1.1.2",
"bulma": "^0.9.4",
"chroma-js": "^2.4.2",
"clusterize.js": "^0.19.0",
"d3": "^7.4.0",
"d3-svg-legend": "^2.25.6",
@ -22,6 +23,7 @@
"tippy.js": "^6.3.7"
},
"devDependencies": {
"@types/chroma-js": "^2.1.4",
"@typescript-eslint/eslint-plugin": "^5.22.0",
"@typescript-eslint/parser": "^5.22.0",
"eslint": "^8.15.0",
@ -173,6 +175,12 @@
"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": {
"version": "0.18.1",
"resolved": "https://registry.npmjs.org/@types/clusterize.js/-/clusterize.js-0.18.1.tgz",
@ -950,6 +958,11 @@
"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": {
"version": "0.19.0",
"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",
"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": {
"version": "0.18.1",
"resolved": "https://registry.npmjs.org/@types/clusterize.js/-/clusterize.js-0.18.1.tgz",
@ -3783,6 +3802,11 @@
"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": {
"version": "0.19.0",
"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/lodash": "^4.14.181",
"@types/sprintf-js": "^1.1.2",
"bulma": "^0.9.4",
"chroma-js": "^2.4.2",
"clusterize.js": "^0.19.0",
"d3": "^7.4.0",
"d3-svg-legend": "^2.25.6",
@ -13,10 +15,10 @@
"jquery": "^3.6.0",
"lodash": "^4.17.21",
"sprintf-js": "^1.1.2",
"tippy.js": "^6.3.7",
"bulma": "^0.9.4"
"tippy.js": "^6.3.7"
},
"devDependencies": {
"@types/chroma-js": "^2.1.4",
"@typescript-eslint/eslint-plugin": "^5.22.0",
"@typescript-eslint/parser": "^5.22.0",
"eslint": "^8.15.0",

View File

@ -15,8 +15,10 @@ import {
secondName,
textColor,
tooltip,
skipTicks
skipTicks,
generateColorScheme
} from "./utils";
import COLORS from "./colors";
export default async function () {
const {
@ -55,7 +57,7 @@ function renderAllocationTarget(allocationTargets: AllocationTarget[]) {
const keys = ["target", "current"];
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);
y.domain(allocationTargets.map((t) => t.name));
@ -244,7 +246,7 @@ function renderPartition(element: HTMLElement, aggregates, hierarchy) {
return formatFloat((d.value / root.value) * 100) + "%";
};
const color = rainbowScale(_.keys(aggregates));
const color = generateColorScheme(_.keys(aggregates));
const stratify = d3
.stratify<Aggregate>()
@ -373,7 +375,7 @@ function renderAllocationTimeline(
0,
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) =>
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 legend from "d3-svg-legend";
import { sprintf } from "sprintf-js";
import dayjs, { Dayjs } from "dayjs";
import chroma from "chroma-js";
import _ from "lodash";
import {
ajax,
@ -14,8 +14,10 @@ import {
secondName,
setHtml,
skipTicks,
tooltip
tooltip,
generateColorScheme
} from "./utils";
import COLORS from "./colors";
export default async function () {
const {
@ -82,10 +84,10 @@ function renderSelectedMonth(
investments: Posting[]
) {
renderer(expenses);
setHtml("current-month-income", sum(incomes, -1));
setHtml("current-month-tax", sum(taxes));
setHtml("current-month-expenses", sum(expenses));
setHtml("current-month-investment", sum(investments));
setHtml("current-month-income", sum(incomes, -1), COLORS.gainText);
setHtml("current-month-tax", sum(taxes), COLORS.lossText);
setHtml("current-month-expenses", sum(expenses), COLORS.lossText);
setHtml("current-month-investment", sum(investments), COLORS.secondary);
}
function sum(postings: Posting[], sign = 1) {
@ -159,7 +161,7 @@ function renderMonthlyExpensesTimeline(
x.domain(points.map((p) => p.month));
y.domain([0, d3.max(points, sum)]);
const z = d3.scaleOrdinal<string>().range(d3.schemeCategory10);
const z = generateColorScheme(groups);
g.append("g")
.attr("class", "axis x")
@ -401,8 +403,8 @@ function renderCurrentExpensesBreakdown(
.style("white-space", "pre")
.style("font-size", "13px")
.style("font-weight", "bold")
.attr("fill", function (d) {
return z(d.category);
.style("fill", function (d) {
return chroma(z(d.category)).darken(0.8).hex();
})
.attr("class", "is-family-monospace")
.text(

View File

@ -1,7 +1,9 @@
import chroma from "chroma-js";
import * as d3 from "d3";
import legend from "d3-svg-legend";
import dayjs from "dayjs";
import _ from "lodash";
import COLORS from "./colors";
import {
ajax,
formatCurrency,
@ -26,13 +28,13 @@ export default async function () {
}
const areaKeys = ["gain", "loss"];
const colors = ["#b2df8a", "#fb9a99"];
const colors = [COLORS.gain, COLORS.loss];
const areaScale = d3.scaleOrdinal<string>().domain(areaKeys).range(colors);
const lineKeys = ["balance", "investment", "withdrawal"];
const lineScale = d3
.scaleOrdinal<string>()
.domain(lineKeys)
.range(["#1f77b4", "#17becf", "#ff7f0e"]);
.range([COLORS.primary, COLORS.secondary, COLORS.tertiary]);
function renderTable(gain: Gain) {
const tbody = d3.select(this);
@ -95,7 +97,13 @@ function renderOverview(gains: Gain[]) {
.paddingOuter(0.1);
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 getInvestmentAmount = (g: Gain) =>
@ -203,7 +211,9 @@ function renderOverview(gains: Gain[]) {
.text((g) => formatCurrency(getGainAmount(g)))
.attr("alignment-baseline", "hanging")
.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("dy", "3")
.attr("x", textGroupZero + (textGroupWidth * 2) / 3)
@ -223,7 +233,9 @@ function renderOverview(gains: Gain[]) {
.append("text")
.text((g) => formatCurrency(getGainAmount(g)))
.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("dy", "-3")
.attr("x", textGroupZero + (textGroupWidth * 2) / 3)
@ -254,7 +266,11 @@ function renderOverview(gains: Gain[]) {
.text((g) => formatFloat(g.xirr))
.attr("text-anchor", "end")
.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("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 dayjs from "dayjs";
import _ from "lodash";
import COLORS from "./colors";
import {
ajax,
formatCurrency,
formatCurrencyCrude,
generateColorScheme,
Income,
Posting,
restName,
@ -27,8 +29,8 @@ export default async function () {
const netTax = _.sumBy(taxes, (t) => _.sumBy(t.postings, (p) => p.amount));
setHtml("gross-income", formatCurrency(grossIncome));
setHtml("net-tax", formatCurrency(netTax));
setHtml("gross-income", formatCurrency(grossIncome), COLORS.gainText);
setHtml("net-tax", formatCurrency(netTax), COLORS.lossText);
}
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")
.attr("class", "axis x")

View File

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

View File

@ -2,6 +2,7 @@ import * as d3 from "d3";
import legend from "d3-svg-legend";
import dayjs from "dayjs";
import _ from "lodash";
import COLORS from "./colors";
import {
ajax,
formatCurrency,
@ -22,13 +23,19 @@ export default async function () {
current.investment_amount +
current.gain_amount -
current.withdrawal_amount
)
),
COLORS.primary
);
setHtml(
"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));
renderOverview(points, document.getElementById("d3-overview-timeline"));
@ -47,7 +54,7 @@ function renderOverview(points: Overview[], element: Element) {
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
const areaKeys = ["gain", "loss"];
const colors = ["#b2df8a", "#fb9a99"];
const colors = [COLORS.gain, COLORS.loss];
const areaScale = d3.scaleOrdinal().domain(areaKeys).range(colors);
const lineKeys = ["networth", "investment"];
@ -55,7 +62,7 @@ function renderOverview(points: Overview[], element: Element) {
const lineScale = d3
.scaleOrdinal<string>()
.domain(lineKeys)
.range(["#1f77b4", "#17becf", "#ff7f0e"]);
.range([COLORS.primary, COLORS.secondary]);
const positions = _.flatMap(points, (p) => [
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 { sprintf } from "sprintf-js";
import _ from "lodash";
@ -243,8 +244,106 @@ export function skipTicks<Domain>(
};
}
export function setHtml(selector: string, value: string) {
const node = document.querySelector(".d3-" + selector);
export function generateColorScheme(domain: string[]) {
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;
}

View File

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