${formatCurrency(h.value)}
${h.ticker}
${percentage}%
`;
});
renderChart(sectorAllocation, totalValue);
}
function renderChart(allocationData, totalValue) {
const ctx = document.getElementById('sector-chart').getContext('2d');
const labels = [], data = [];
for(const sector in allocationData) {
if (allocationData[sector] > 0) {
labels.push(sector);
data.push((allocationData[sector] / totalValue * 100).toFixed(2));
}
}
const chartData = { labels, datasets: [{ data, backgroundColor: ['#1e3a8a', '#3b82f6', '#93c5fd', '#334155', '#64748b', '#047857', '#34d399', '#f59e0b', '#fbbf24', '#b91c1c', '#f87171'] }]};
if (sectorChart) {
sectorChart.data = chartData;
sectorChart.update();
} else {
sectorChart = new Chart(ctx, { type: 'doughnut', data: chartData, options: { responsive: true, plugins: { legend: { display: true, position: 'bottom', labels: { boxWidth: 12 } } } }});
}
}
const holdingModal = document.getElementById('holding-modal');
function openModal(holding = null) {
document.getElementById('holding-form').reset();
if (holding) {
document.getElementById('modal-title').textContent = 'Edit Holding';
document.getElementById('holding-id').value = holding.id;
document.getElementById('ticker').value = holding.ticker;
document.getElementById('sector').value = holding.sector;
document.getElementById('value').value = holding.value;
} else {
document.getElementById('modal-title').textContent = 'Add New Holding';
}
holdingModal.classList.remove('hidden');
}
function closeModal() { holdingModal.classList.add('hidden'); }
document.getElementById('holding-form').addEventListener('submit', (e) => {
e.preventDefault();
const id = document.getElementById('holding-id').value;
const newHolding = {
id: id ? parseInt(id) : Date.now(),
ticker: document.getElementById('ticker').value,
sector: document.getElementById('sector').value,
value: parseFloat(document.getElementById('value').value),
};
if (id) {
portfolio = portfolio.map(h => h.id === newHolding.id ? newHolding : h);
} else {
portfolio.push(newHolding);
}
closeModal();
analyzeAndRender();
});
document.getElementById('holdings-list').addEventListener('click', e => {
if (e.target.classList.contains('delete-btn')) {
portfolio = portfolio.filter(h => h.id !== parseInt(e.target.dataset.id));
analyzeAndRender();
}
});
document.getElementById('add-holding-btn').addEventListener('click', () => openModal());
document.getElementById('cancel-btn').addEventListener('click', closeModal);
function downloadPdf() {
if (portfolio.length === 0) return;
const { jsPDF } = window.jspdf;
const pdf = new jsPDF();
pdf.setFont('helvetica', 'bold').setFontSize(22).text('Portfolio Diversification Report', 105, 20, { align: 'center' });
const totalValue = portfolio.reduce((sum, h) => sum + h.value, 0);
const chartImg = sectorChart.toBase64Image('image/png', 1.0);
pdf.autoTable({
startY: 30, theme: 'grid', headStyles: { fillColor: '#1e293b' },
head: [['Metric', 'Value']],
body: [
['Total Portfolio Value', formatCurrency(totalValue)],
['Number of Holdings', portfolio.length],
['Diversification Score', document.getElementById('diversification-score').textContent]
]
});
pdf.addImage(chartImg, 'PNG', 15, pdf.autoTable.previous.finalY + 10, 180, 90);
const body = portfolio.map(h => [h.ticker, h.sector, formatCurrency(h.value), `${(h.value / totalValue * 100).toFixed(2)}%`]);
pdf.autoTable({ startY: pdf.autoTable.previous.finalY + 105, head: [['Ticker', 'Sector', 'Value', 'Weight']], body, theme: 'striped', headStyles: { fillColor: '#1e293b' }});
pdf.save('Portfolio_Diversification_Report.pdf');
}
document.getElementById('download-pdf-btn').addEventListener('click', downloadPdf);
function init() {
const sectorSelect = document.getElementById('sector');
sectors.forEach(s => sectorSelect.innerHTML += ``);
portfolio = [
{id: 1, ticker: 'AAPL', sector: 'Technology', value: 25000},
{id: 2, ticker: 'JNJ', sector: 'Healthcare', value: 15000},
{id: 3, ticker: 'JPM', sector: 'Financials', value: 18000},
{id: 4, ticker: 'MSFT', sector: 'Technology', value: 22000},
{id: 5, ticker: 'XOM', sector: 'Energy', value: 10000},
];
analyzeAndRender();
}
init();
});
