No data to display for this chart.
'; return; } const boxSize = Math.min(container.offsetWidth || 280, 280); // Use parent width or default svg.setAttribute('viewBox', `0 0 ${boxSize} ${boxSize}`); const radius = boxSize / 2 * 0.8; const cx = boxSize / 2; const cy = boxSize / 2; let cumulativePercent = 0; const totalValue = filteredData.reduce((sum, d) => sum + d.value, 0); filteredData.forEach((d, index) => { const percent = d.value / totalValue; const startAngle = cumulativePercent * 2 * Math.PI - Math.PI / 2; const endAngle = (cumulativePercent + percent) * 2 * Math.PI - Math.PI / 2; const x1 = cx + radius * Math.cos(startAngle); const y1 = cy + radius * Math.sin(startAngle); const x2 = cx + radius * Math.cos(endAngle); const y2 = cy + radius * Math.sin(endAngle); const largeArcFlag = percent > 0.5 ? 1 : 0; const pathData = `M ${cx},${cy} L ${x1},${y1} A ${radius},${radius} 0 ${largeArcFlag},1 ${x2},${y2} Z`; const path = document.createElementNS("http://www.w3.org/2000/svg", "path"); path.setAttribute("d", pathData); path.setAttribute("fill", bti_defaultColors[index % bti_defaultColors.length]); path.setAttribute("stroke", "#F9FAFB"); path.setAttribute("stroke-width", "1"); const title = document.createElementNS("http://www.w3.org/2000/svg", "title"); title.textContent = `${d.label}: ${bti_formatCurrency(d.value)} (${(percent*100).toFixed(1)}%)`; path.appendChild(title); svg.appendChild(path); cumulativePercent += percent; }); container.appendChild(svg); } // --- Tab 5: Manage Data --- function bti_exportData() { /* Same as hfit_exportData, adapted for bti_data */ const jsonData = JSON.stringify(bti_data, null, 2); const blob = new Blob([jsonData], {type: 'application/json'}); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'bti_investor_budget_data.json'; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); alert("Budget data exported successfully!"); } function bti_importData(event) { /* Same as hfit_importData, adapted for bti_data */ const file = event.target.files[0]; if (file) { const reader = new FileReader(); reader.onload = function(e) { try { const importedData = JSON.parse(e.target.result); if (importedData && importedData.incomes && importedData.expenses && importedData.investments) { if (confirm("Importing will overwrite current budget data. Are you sure?")) { bti_data = importedData; bti_saveData(); alert("Data imported successfully! Refreshing lists and summary..."); bti_renderAllLists(); bti_calculateAndDisplaySummary(); } } else { alert("Invalid file format for budget data."); } } catch (err) { alert("Error importing file: " + err.message); } }; reader.readAsText(file); event.target.value = ''; } } function bti_clearAllData() { /* Same as hfit_clearAllData, adapted for bti_data */ if (confirm("Are you sure you want to clear ALL locally stored budget data? This action cannot be undone.")) { bti_data = { incomes: [], expenses: [], investments: [] }; bti_saveData(); alert("All budget data cleared."); bti_renderAllLists(); bti_calculateAndDisplaySummary(); } } // --- PDF Generation --- function bti_downloadPDF() { if (!bti_data.summaryResults) { alert("Please calculate summary first."); return; } if (typeof window.jspdf === 'undefined' || typeof window.jspdf.jsPDF === 'undefined') { alert('Core PDF library (jsPDF) is not loaded.'); return; } const { jsPDF: JSPDF } = window.jspdf; const doc = new JSPDF(); if (typeof doc.autoTable !== 'function') { alert('PDF Table plugin (jsPDF-AutoTable) not loaded. PDF formatting may be basic.'); } let y = 15; const m = 15; const s = bti_data.summaryResults; doc.setFontSize(16); doc.text("Investor Budget Report", m, y); y += 10; doc.setFontSize(10); doc.text(`Report Date: ${new Date().toLocaleDateString()}`, m, y); y += 7; doc.setFontSize(12); doc.text("Monthly Budget Summary:", m, y); y += 6; let summaryData = [ ["Total Monthly Income:", bti_formatCurrency(s.totalMonthlyIncome)], ["Total Monthly Expenses:", bti_formatCurrency(s.totalMonthlyExpenses)], ["Total Monthly Investments:", bti_formatCurrency(s.totalMonthlyInvestments)], [{content: "Net Monthly Cash Flow:", styles:{fontStyle:'bold'}}, {content: bti_formatCurrency(s.netCashFlowMonthly), styles:{fontStyle:'bold', textColor: s.netCashFlowMonthly >=0 ? [22,163,74] : [220,38,38]}}], ["Investment Savings Rate:", bti_formatPercent(s.savingsRate)], ["Disposable Savings Rate:", bti_formatPercent(s.disposableSavingsRate)], ]; if (typeof doc.autoTable === 'function') { doc.autoTable({startY: y, body: summaryData, theme: 'plain', styles:{fontSize:9, cellPadding:1.2}, columnStyles:{0:{fontStyle:'bold'}}}); y = doc.lastAutoTable.finalY + 5; } else { summaryData.forEach(row => {doc.text(`${row[0]} ${row[1]}`,m,y); y+=4;}); y+=3;} const makeTable = (title, items) => { if (items.length === 0) return; if (y > 240 && items.length > 3) { doc.addPage(); y = m;} doc.setFontSize(11); doc.text(title, m, y); y += 5; const head = [['Source/Item', 'Monthly Amount ($)', 'Frequency']]; const body = items.map(item => [ item.name || item.itemName || 'Unnamed', bti_formatCurrency(bti_calculateMonthlyValue(item.amount || item.contributionAmount, item.frequency)), item.frequency.charAt(0).toUpperCase() + item.frequency.slice(1) ]); if (typeof doc.autoTable === 'function') { doc.autoTable({startY: y, head: head, body: body, theme: 'striped', headStyles:{fillColor:[30,64,175]}, styles:{fontSize:8}}); y = doc.lastAutoTable.finalY + 6; } else { body.forEach(row => {doc.text(row.join(' | '), m+5, y); y+=3.5;}); y+=3;} }; makeTable("Income Sources (Monthly Equivalent):", bti_data.incomes); makeTable("Expense Breakdown (Monthly Equivalent):", bti_data.expenses.map(e => ({...e, name: `${e.category}: ${e.itemName}`}))); // Combine cat & item makeTable("Planned Investments (Monthly Equivalent):", bti_data.investments); if (y > 275) { doc.addPage(); y = m; } doc.setFontSize(8); doc.text("Note: This is a conceptual budgeting tool based on user-defined inputs. For informational purposes only.", m, y); doc.save('Investor_Budget_Report.pdf'); }