hover tooltips on Übersicht table with breakdown details
This commit is contained in:
16
calc.js
16
calc.js
@@ -44,8 +44,12 @@ function run_simulation(p) {
|
|||||||
const renter_nw_arr = [];
|
const renter_nw_arr = [];
|
||||||
const cum_buy_arr = [];
|
const cum_buy_arr = [];
|
||||||
const cum_rent_arr = [];
|
const cum_rent_arr = [];
|
||||||
|
const detail_arr = [];
|
||||||
const labels = [];
|
const labels = [];
|
||||||
|
|
||||||
|
// yearly accumulators (reset each year)
|
||||||
|
let yr_interest = 0, yr_principal = 0, yr_upkeep = 0;
|
||||||
|
|
||||||
let breakeven_month = null;
|
let breakeven_month = null;
|
||||||
|
|
||||||
const invest_r_m = p.invest_return / 100 / 12;
|
const invest_r_m = p.invest_return / 100 / 12;
|
||||||
@@ -65,6 +69,9 @@ function run_simulation(p) {
|
|||||||
// German context: no mortgage interest tax deduction for private buyers
|
// German context: no mortgage interest tax deduction for private buyers
|
||||||
const total_buy_m = monthly_payment + prop_tax_m + maint_m + insurance_m;
|
const total_buy_m = monthly_payment + prop_tax_m + maint_m + insurance_m;
|
||||||
|
|
||||||
|
yr_interest += interest_m;
|
||||||
|
yr_principal += principal_m;
|
||||||
|
yr_upkeep += prop_tax_m + maint_m + insurance_m;
|
||||||
cum_buy_cost += total_buy_m;
|
cum_buy_cost += total_buy_m;
|
||||||
|
|
||||||
buyer_portfolio *= (1 + invest_r_m);
|
buyer_portfolio *= (1 + invest_r_m);
|
||||||
@@ -103,6 +110,14 @@ function run_simulation(p) {
|
|||||||
renter_nw_arr.push(Math.round(portfolio - tax_on_gains));
|
renter_nw_arr.push(Math.round(portfolio - tax_on_gains));
|
||||||
cum_buy_arr.push(Math.round(cum_buy_cost));
|
cum_buy_arr.push(Math.round(cum_buy_cost));
|
||||||
cum_rent_arr.push(Math.round(cum_rent_cost));
|
cum_rent_arr.push(Math.round(cum_rent_cost));
|
||||||
|
detail_arr.push({
|
||||||
|
house_value: Math.round(home_value),
|
||||||
|
buyer_portfolio: Math.round(buyer_portfolio),
|
||||||
|
yr_interest: Math.round(yr_interest),
|
||||||
|
yr_principal: Math.round(yr_principal),
|
||||||
|
yr_upkeep: Math.round(yr_upkeep),
|
||||||
|
});
|
||||||
|
yr_interest = 0; yr_principal = 0; yr_upkeep = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,6 +131,7 @@ function run_simulation(p) {
|
|||||||
renter_nw_arr,
|
renter_nw_arr,
|
||||||
cum_buy_arr,
|
cum_buy_arr,
|
||||||
cum_rent_arr,
|
cum_rent_arr,
|
||||||
|
detail_arr,
|
||||||
monthly_payment: Math.round(monthly_payment),
|
monthly_payment: Math.round(monthly_payment),
|
||||||
final_buyer_nw,
|
final_buyer_nw,
|
||||||
final_renter_nw,
|
final_renter_nw,
|
||||||
|
|||||||
@@ -409,6 +409,8 @@ th {
|
|||||||
|
|
||||||
tbody tr:hover { background: rgba(255,255,255,0.03); }
|
tbody tr:hover { background: rgba(255,255,255,0.03); }
|
||||||
|
|
||||||
|
td.has-tip { cursor: default; border-bottom-style: dashed; }
|
||||||
|
|
||||||
tr.winner-row td { font-weight: 600; }
|
tr.winner-row td { font-weight: 600; }
|
||||||
|
|
||||||
/* ===== Footer ===== */
|
/* ===== Footer ===== */
|
||||||
|
|||||||
83
ui.js
83
ui.js
@@ -140,22 +140,91 @@ const update_chart = (chart, labels, data_buy, data_rent, breakeven_month, years
|
|||||||
chart.update('none');
|
chart.update('none');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ===== Tooltip =====
|
||||||
|
|
||||||
|
const tooltip_el = (() => {
|
||||||
|
const el = document.createElement('div');
|
||||||
|
el.id = 'tbl_tooltip';
|
||||||
|
el.style.cssText = `
|
||||||
|
position:fixed; z-index:1000; pointer-events:none; display:none;
|
||||||
|
background:rgba(13,17,23,0.96); border:1px solid rgba(255,255,255,0.12);
|
||||||
|
border-radius:10px; padding:0.7rem 1rem; font-size:0.8rem; line-height:1.8;
|
||||||
|
color:#e6edf3; white-space:nowrap; box-shadow:0 8px 32px rgba(0,0,0,0.5);
|
||||||
|
`;
|
||||||
|
document.body.appendChild(el);
|
||||||
|
return el;
|
||||||
|
})();
|
||||||
|
|
||||||
|
const show_tooltip = (e, html) => {
|
||||||
|
tooltip_el.innerHTML = html;
|
||||||
|
tooltip_el.style.display = 'block';
|
||||||
|
move_tooltip(e);
|
||||||
|
};
|
||||||
|
|
||||||
|
const move_tooltip = (e) => {
|
||||||
|
const pad = 14;
|
||||||
|
const tw = tooltip_el.offsetWidth;
|
||||||
|
const th = tooltip_el.offsetHeight;
|
||||||
|
let x = e.clientX + pad;
|
||||||
|
let y = e.clientY + pad;
|
||||||
|
if (x + tw > window.innerWidth - pad) x = e.clientX - tw - pad;
|
||||||
|
if (y + th > window.innerHeight - pad) y = e.clientY - th - pad;
|
||||||
|
tooltip_el.style.left = x + 'px';
|
||||||
|
tooltip_el.style.top = y + 'px';
|
||||||
|
};
|
||||||
|
|
||||||
|
const hide_tooltip = () => { tooltip_el.style.display = 'none'; };
|
||||||
|
|
||||||
|
const tip_row = (label, value, color) =>
|
||||||
|
`<span style="color:${color || '#8b949e'}">${label}</span> <b>${fmt_eur(value)}</b>`;
|
||||||
|
|
||||||
// ===== Table =====
|
// ===== Table =====
|
||||||
|
|
||||||
const build_table = (result) => {
|
const build_table = (result) => {
|
||||||
const tbody = document.getElementById('table_body');
|
const tbody = document.getElementById('table_body');
|
||||||
tbody.innerHTML = '';
|
tbody.innerHTML = '';
|
||||||
for (let i = 0; i < result.labels.length; i++) {
|
for (let i = 0; i < result.labels.length; i++) {
|
||||||
|
const d = result.detail_arr[i];
|
||||||
const tr = document.createElement('tr');
|
const tr = document.createElement('tr');
|
||||||
tr.innerHTML = `
|
|
||||||
<td>${result.labels[i]}</td>
|
const td_buy_nw = document.createElement('td');
|
||||||
<td>${fmt_eur(result.buyer_nw_arr[i])}</td>
|
td_buy_nw.textContent = fmt_eur(result.buyer_nw_arr[i]);
|
||||||
<td>${fmt_eur(result.renter_nw_arr[i])}</td>
|
td_buy_nw.classList.add('has-tip');
|
||||||
<td>${fmt_eur(result.cum_buy_arr[i])}</td>
|
td_buy_nw.addEventListener('mouseenter', (e) => show_tooltip(e, [
|
||||||
<td>${fmt_eur(result.cum_rent_arr[i])}</td>
|
tip_row('Immobilienwert', d.house_value, '#f59e0b'),
|
||||||
`;
|
tip_row('ETF-Portfolio', d.buyer_portfolio, '#f59e0b'),
|
||||||
|
].join('<br>')));
|
||||||
|
|
||||||
|
const td_rent_nw = document.createElement('td');
|
||||||
|
td_rent_nw.textContent = fmt_eur(result.renter_nw_arr[i]);
|
||||||
|
td_rent_nw.classList.add('has-tip');
|
||||||
|
td_rent_nw.addEventListener('mouseenter', (e) => show_tooltip(e, [
|
||||||
|
tip_row('ETF-Portfolio (nach Steuer)', result.renter_nw_arr[i], '#2dd4bf'),
|
||||||
|
].join('<br>')));
|
||||||
|
|
||||||
|
const td_buy_cost = document.createElement('td');
|
||||||
|
td_buy_cost.textContent = fmt_eur(result.cum_buy_arr[i]);
|
||||||
|
td_buy_cost.classList.add('has-tip');
|
||||||
|
td_buy_cost.addEventListener('mouseenter', (e) => show_tooltip(e, [
|
||||||
|
`<span style="color:#8b949e;font-size:0.72rem;text-transform:uppercase;letter-spacing:.05em">dieses Jahr</span>`,
|
||||||
|
tip_row('Zinsen', d.yr_interest, '#f59e0b'),
|
||||||
|
tip_row('Tilgung', d.yr_principal, '#f59e0b'),
|
||||||
|
tip_row('Nebenkosten', d.yr_upkeep, '#f59e0b'),
|
||||||
|
].join('<br>')));
|
||||||
|
|
||||||
|
const td_rent_cost = document.createElement('td');
|
||||||
|
td_rent_cost.textContent = fmt_eur(result.cum_rent_arr[i]);
|
||||||
|
|
||||||
|
tr.appendChild(document.createElement('td')).textContent = result.labels[i];
|
||||||
|
tr.appendChild(td_buy_nw);
|
||||||
|
tr.appendChild(td_rent_nw);
|
||||||
|
tr.appendChild(td_buy_cost);
|
||||||
|
tr.appendChild(td_rent_cost);
|
||||||
tbody.appendChild(tr);
|
tbody.appendChild(tr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tbody.addEventListener('mousemove', move_tooltip);
|
||||||
|
tbody.addEventListener('mouseleave', hide_tooltip);
|
||||||
};
|
};
|
||||||
|
|
||||||
// ===== Result cards =====
|
// ===== Result cards =====
|
||||||
|
|||||||
Reference in New Issue
Block a user