/** * ui.js — DOM wiring, event listeners, chart rendering */ // ===== Helpers ===== const fmt_eur = (n) => new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR', maximumFractionDigits: 0 }).format(n); const get_params = () => ({ years: +document.getElementById('years').value, invest_return: +document.getElementById('invest_return').value, tax_rate: +document.getElementById('tax_rate').value, home_price: +document.getElementById('home_price').value, down_pct: +document.getElementById('down_pct').value, interest_rate: +document.getElementById('interest_rate').value, loan_years: +document.getElementById('loan_years').value, closing_buy_pct: +document.getElementById('closing_buy_pct').value, closing_sell_pct:+document.getElementById('closing_sell_pct').value, appreciation: +document.getElementById('appreciation').value, prop_tax_rate: +document.getElementById('prop_tax_rate').value, maint_rate: +document.getElementById('maint_rate').value, insurance: +document.getElementById('insurance').value, monthly_rent: +document.getElementById('monthly_rent').value, rent_increase: +document.getElementById('rent_increase').value, renters_ins: +document.getElementById('renters_ins').value, start_capital: +document.getElementById('start_capital').value, }); // ===== Charts ===== const CHART_DEFAULTS = { color: '#8b949e', borderColor: 'rgba(255,255,255,0.08)', }; const make_chart = (canvas_id, label_buy, label_rent) => { const ctx = document.getElementById(canvas_id).getContext('2d'); return new Chart(ctx, { type: 'line', data: { labels: [], datasets: [ { label: label_buy, data: [], borderColor: '#f59e0b', backgroundColor: 'rgba(245,158,11,0.06)', borderWidth: 2.5, pointRadius: 3, pointHoverRadius: 5, fill: false, tension: 0.35, }, { label: label_rent, data: [], borderColor: '#2dd4bf', backgroundColor: 'rgba(45,212,191,0.06)', borderWidth: 2.5, pointRadius: 3, pointHoverRadius: 5, fill: false, tension: 0.35, }, ], }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { display: false }, tooltip: { mode: 'index', intersect: false, backgroundColor: 'rgba(13,17,23,0.9)', borderColor: 'rgba(255,255,255,0.12)', borderWidth: 1, titleColor: '#8b949e', bodyColor: '#e6edf3', padding: 12, callbacks: { label: (ctx) => ` ${ctx.dataset.label}: ${fmt_eur(ctx.parsed.y)}`, }, }, annotation: { annotations: {} }, }, scales: { x: { grid: { color: 'rgba(255,255,255,0.05)' }, ticks: { color: '#6e7681', font: { size: 11 } }, }, y: { grid: { color: 'rgba(255,255,255,0.05)' }, ticks: { color: '#6e7681', font: { size: 11 }, callback: (v) => fmt_eur(v), }, }, }, interaction: { mode: 'nearest', axis: 'x', intersect: false }, }, }); }; const chart_nw = make_chart('chart_nw', 'Kaufen', 'Mieten (ETF)'); const chart_cost = make_chart('chart_cost', 'Kaufen', 'Mieten'); const update_chart = (chart, labels, data_buy, data_rent, breakeven_month, years) => { chart.data.labels = labels; chart.data.datasets[0].data = data_buy; chart.data.datasets[1].data = data_rent; // breakeven annotation if (breakeven_month !== null && breakeven_month <= years * 12) { const be_year = breakeven_month / 12; chart.options.plugins.annotation.annotations = { breakeven: { type: 'line', xMin: be_year - 1, // labels are "Jahr 1" ... "Jahr N", zero-indexed xMax: be_year - 1, borderColor: 'rgba(255,255,255,0.25)', borderWidth: 1, borderDash: [5, 4], label: { display: true, content: `Break-Even Jahr ${be_year.toFixed(1)}`, color: '#8b949e', backgroundColor: 'rgba(13,17,23,0.85)', font: { size: 11 }, position: 'start', }, }, }; } else { chart.options.plugins.annotation.annotations = {}; } chart.update('none'); }; // ===== Table ===== const build_table = (result) => { const tbody = document.getElementById('table_body'); tbody.innerHTML = ''; for (let i = 0; i < result.labels.length; i++) { const tr = document.createElement('tr'); tr.innerHTML = `