Skip to content

Commit

Permalink
coin2html: add details popup
Browse files Browse the repository at this point in the history
  • Loading branch information
mkobetic committed Dec 21, 2024
1 parent da810a7 commit 248ecff
Show file tree
Hide file tree
Showing 7 changed files with 554 additions and 302 deletions.
1 change: 1 addition & 0 deletions cmd/coin2html/js/body.html
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ <h1>
<section id="view"></section>
</section>
</div>
<div hidden id="details" class="details"></div>

<script src="src/commodity.ts"></script>
<script src="src/account.ts"></script>
Expand Down
2 changes: 1 addition & 1 deletion cmd/coin2html/js/src/chart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { scaleLinear, scaleOrdinal, scaleTime } from "d3-scale";
import { schemeCategory10 } from "d3-scale-chromatic";
import { select } from "d3-selection";

export function viewChart(options?: {
export function viewChartTotals(options?: {
negated?: boolean; // is this negatively denominated account (e.g. Income/Liability)
}) {
const containerSelector = MainView;
Expand Down
76 changes: 43 additions & 33 deletions cmd/coin2html/js/src/register.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,16 @@ import {
MainView,
addShowLocationInput,
addAggregationStyleInput,
AggregationStyle,
showDetails,
} from "./views";
import { Account, Posting } from "./account";
import {
balanceOrSum,
dateToString,
groupBy,
groupByWithSubAccounts,
last,
PostingGroup,
shortenAccountName,
trimToDateRange,
} from "./utils";
Expand Down Expand Up @@ -99,21 +101,18 @@ function viewRegisterAggregated(
.classed("even", (_, i) => i % 2 == 0)
.selectAll("td")
.data((g) => {
const row = [
[dateToString(g.date), "date"],
[
State.View.AggregationStyle == AggregationStyle.Balances
? g.balance
: g.sum,
"amount",
],
const row: [PostingGroup, (g: PostingGroup) => string, string][] = [
[g, (g) => dateToString(g.date), "date"],
[g, (g) => balanceOrSum(g).toString(), "amount"],
];
if (options.aggregatedTotal) row.push([g.total, "amount"]);
if (options.aggregatedTotal)
row.push([g, (g) => g.total.toString(), "amount"]);
return row;
})
.join("td")
.classed("amount", ([v, c]) => c == "amount")
.text(([v, c]) => v.toString());
.classed("amount", ([g, v, c]) => c == "amount")
.text(([g, v, c]) => v(g))
.on("click", (e, [g, v, c]) => showDetails(g));
}

function viewRegisterAggregatedWithSubAccounts(
Expand Down Expand Up @@ -178,27 +177,19 @@ function viewRegisterAggregatedWithSubAccounts(
.selectAll("td")
.data((row) => {
const total = last(row)!;
const columns = row.map((g) => [
State.View.AggregationStyle == AggregationStyle.Flows
? g.sum
: g.balance,
"amount",
]);
const columns: [PostingGroup, (g: PostingGroup) => string, string][] =
row.map((g) => [g, (g) => balanceOrSum(g).toString(), "amount"]);
// prepend date
columns.unshift([dateToString(row[0].date), "date"]);
columns.unshift([row[0], (g) => dateToString(g.date), "date"]);
// append total correctly
if (options.aggregatedTotal)
columns.push([
State.View.AggregationStyle == AggregationStyle.Flows
? total.total
: total.balance,
"amount",
]);
columns.push([total, (g) => balanceOrSum(g).toString(), "amount"]);
return columns;
})
.join("td")
.classed("amount", ([v, c]) => c == "amount")
.text(([v, c]) => v.toString());
.classed("amount", ([g, v, c]) => c == "amount")
.text(([g, v, c]) => v(g))
.on("click", (e, [g, v, c]) => showDetails(g));
}

function viewRegisterFull(
Expand Down Expand Up @@ -273,6 +264,27 @@ function viewRegisterFullWithSubAccounts(
negated: boolean;
}
) {
const data = account.withAllChildPostings(State.StartDate, State.EndDate);
renderPostingsWithSubAccounts(account, data, containerSelector, {
showLocation: State.View.ShowLocation,
showNotes: State.View.ShowNotes,
});
}

export function renderPostingsWithSubAccounts(
account: Account,
data: Posting[],
containerSelector: string,
optionOverrides?: {
showLocation?: boolean;
showNotes?: boolean;
}
) {
const options = {
showLocation: false,
showNotes: false,
};
Object.assign(options, optionOverrides);
const labels = [
"Date",
"Description",
Expand All @@ -281,10 +293,9 @@ function viewRegisterFullWithSubAccounts(
"Amount",
"Cum.Total",
];
if (State.View.ShowLocation) labels.push("Location");
if (options.showLocation) labels.push("Location");
const table = addTableWithHeader(containerSelector, labels);
const total = new Amount(0, account.commodity);
const data = account.withAllChildPostings(State.StartDate, State.EndDate);
const rows = table.append("tbody").selectAll("tr").data(data).enter();
rows
.append("tr")
Expand All @@ -301,15 +312,14 @@ function viewRegisterFullWithSubAccounts(
[p.quantity, "amount"],
[Amount.clone(total), "amount"],
];
if (State.View.ShowLocation)
values.push([p.transaction.location, "text"]);
if (options.showLocation) values.push([p.transaction.location, "text"]);
return values;
})
.join("td")
.classed("amount", ([v, c]) => c == "amount")
.attr("rowspan", (_, i) => (i == 0 && State.View.ShowNotes ? 2 : null))
.attr("rowspan", (_, i) => (i == 0 && options.showNotes ? 2 : null))
.text(([v, c]) => v.toString());
if (State.View.ShowNotes) {
if (options.showNotes) {
rows
.append("tr")
.classed("even", (_, i) => i % 2 == 0)
Expand Down
22 changes: 21 additions & 1 deletion cmd/coin2html/js/src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Account, Posting } from "./account";
import { Amount, Commodity } from "./commodity";
import { State } from "./views";
import { AggregationStyle, State } from "./views";

export function dateToString(date: Date): string {
return date.toISOString().split("T")[0];
Expand All @@ -25,6 +25,12 @@ export type PostingGroup = {
width?: number; // used to cache width value (x) in layered stack chart
};

export function balanceOrSum(g: PostingGroup) {
return State.View.AggregationStyle == AggregationStyle.Flows
? g.sum
: g.balance;
}

export function groupBy(
postings: Posting[],
groupBy: d3.TimeInterval,
Expand Down Expand Up @@ -54,6 +60,20 @@ export function groupBy(
});
}

export function topN(
postings: Posting[],
n: number,
commodity: Commodity
): Posting[] {
const top = [...postings];
top.sort(
(a, b) =>
commodity.convert(a.quantity, a.transaction.posted).toNumber() -
commodity.convert(b.quantity, b.transaction.posted).toNumber()
);
return top.slice(0, n);
}

// list of groups for an account
export type AccountPostingGroups = {
account?: Account;
Expand Down
33 changes: 26 additions & 7 deletions cmd/coin2html/js/src/views.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { select } from "d3-selection";
import { timeMonth, timeWeek, timeYear } from "d3-time";
import { viewRegister } from "./register";
import { viewChart } from "./chart";
import { renderPostingsWithSubAccounts, viewRegister } from "./register";
import { viewChartTotals } from "./chart";
import { Account } from "./account";
import { shortenAccountName } from "./utils";
import { PostingGroup, shortenAccountName, topN } from "./utils";

export const Aggregation = {
None: null,
Expand Down Expand Up @@ -44,26 +44,26 @@ export const State = {
export const Views = {
Assets: {
Register: viewRegister,
Chart: viewChart,
Chart: viewChartTotals,
},
Liabilities: {
Register: () => viewRegister({ negated: true }),
Chart: () => viewChart({ negated: true }),
Chart: () => viewChartTotals({ negated: true }),
},
Income: {
Register: () =>
viewRegister({
negated: true,
aggregatedTotal: true,
}),
Chart: () => viewChart({ negated: true }),
Chart: () => viewChartTotals({ negated: true }),
},
Expenses: {
Register: () =>
viewRegister({
aggregatedTotal: true,
}),
Chart: viewChart,
Chart: viewChartTotals,
},
Equity: {
Register: viewRegister,
Expand Down Expand Up @@ -207,6 +207,7 @@ export const ShowClosedAccounts = "#main #controls input#closedAccounts";
export const AccountName = "#main output#account span#name";
export const AccountCommodity = "#main output#account span#commodity";
export const MainView = "#main section#view";
export const Details = "div#details";

export function emptyElement(selector: string) {
(select(selector).node() as Element).replaceChildren();
Expand Down Expand Up @@ -283,3 +284,21 @@ export function updateAccounts() {
addAccountList();
updateAccount();
}

export function showDetails(g: PostingGroup) {
emptyElement(Details);
const details = select(Details);
details
.insert("a")
.text("X")
.on("click", () => details.attr("hidden", true));
const account = State.SelectedAccount;
renderPostingsWithSubAccounts(
account,
topN(g.postings, 20, account.commodity),
Details,
{ showLocation: true }
);

details.attr("hidden", null);
}
10 changes: 10 additions & 0 deletions cmd/coin2html/js/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,16 @@ label {
margin: 0.3em;
}

/* Fixed details div hidden by default */
div#details {
position: fixed;
left: 20%;
top: 20%;
background-color: white;
border-style: solid;
padding: 10px;
}

/* VIEW REGISTER */
table#register {
width: 100%;
Expand Down
Loading

0 comments on commit 248ecff

Please sign in to comment.