No strategies have been added. Please go to the 'Fund & Strategy Inputs' tab to add strategies.
";
if (dashboardContentEl) dashboardContentEl.innerHTML = message;
if (reportOutputEl) reportOutputEl.innerHTML = message;
// Destroy existing charts if they exist and there's no data
if (mshfpa_aumChartInstance) mshfpa_aumChartInstance.destroy();
if (mshfpa_returnChartInstance) mshfpa_returnChartInstance.destroy();
if (mshfpa_sharpeChartInstance) mshfpa_sharpeChartInstance.destroy();
mshfpa_aumChartInstance = null;
mshfpa_returnChartInstance = null;
mshfpa_sharpeChartInstance = null;
return;
}
let totalAum = 0;
let weightedReturnSum = 0;
let weightedVolatilitySum = 0;
let weightedSharpeSum = 0;
mshfpa_strategies.forEach(s => {
totalAum += s.aum;
});
if (totalAum > 0) {
mshfpa_strategies.forEach(s => {
const weight = s.aum / totalAum;
weightedReturnSum += weight * s.returnPercent;
weightedVolatilitySum += weight * s.volatility; // Simple weighted average, not true portfolio volatility
weightedSharpeSum += weight * s.sharpeRatio; // Simple weighted average
});
}
const strategyNames = mshfpa_strategies.map(s => s.name);
const strategyAums = mshfpa_strategies.map(s => s.aum);
const strategyReturns = mshfpa_strategies.map(s => s.returnPercent);
const strategySharpes = mshfpa_strategies.map(s => s.sharpeRatio);
const strategyVolatilities = mshfpa_strategies.map(s => s.volatility);
// --- Build HTML Content for Dashboard and Report ---
let htmlContent = `
Overall Fund Summary
Total Assets Under Management (AUM):
$${totalAum.toLocaleString()}
AUM-Weighted Average Return:
${mshfpa_formatNumber(weightedReturnSum)}%
AUM-Weighted Average Volatility (Illustrative):
${mshfpa_formatNumber(weightedVolatilitySum)}%
AUM-Weighted Average Sharpe Ratio (Illustrative):
${mshfpa_formatNumber(weightedSharpeSum)}
Strategy Breakdown
| Strategy Name |
AUM ($) |
AUM (%) |
Return (%) |
Volatility (%) |
Sharpe Ratio |
`;
mshfpa_strategies.forEach(s => {
const weightPercent = totalAum > 0 ? (s.aum / totalAum * 100) : 0;
htmlContent += `
| ${s.name} |
$${s.aum.toLocaleString()} |
${mshfpa_formatNumber(weightPercent)}% |
${mshfpa_formatNumber(s.returnPercent)}% |
${mshfpa_formatNumber(s.volatility)}% |
${mshfpa_formatNumber(s.sharpeRatio)} |
`;
});
htmlContent += `
AUM Allocation by Strategy
Annualized Return by Strategy
Sharpe Ratio by Strategy
`;
if(dashboardContentEl) dashboardContentEl.innerHTML = htmlContent;
// For PDF, we might want to slightly alter or ensure chart images are captured
if(reportOutputEl) reportOutputEl.innerHTML = htmlContent; // Initially same content
// --- Initialize/Update Charts ---
// Destroy existing charts before redrawing
if (mshfpa_aumChartInstance) mshfpa_aumChartInstance.destroy();
if (mshfpa_returnChartInstance) mshfpa_returnChartInstance.destroy();
if (mshfpa_sharpeChartInstance) mshfpa_sharpeChartInstance.destroy();
const chartBackgroundColor = [
'rgba(54, 162, 235, 0.7)', 'rgba(255, 99, 132, 0.7)', 'rgba(75, 192, 192, 0.7)',
'rgba(255, 206, 86, 0.7)', 'rgba(153, 102, 255, 0.7)', 'rgba(255, 159, 64, 0.7)',
'rgba(199, 199, 199, 0.7)', 'rgba(83, 102, 255, 0.7)', 'rgba(102,255,83,0.7)'
];
const chartBorderColor = chartBackgroundColor.map(color => color.replace('0.7', '1'));
// AUM Pie Chart
const aumCtx = document.getElementById('mshfpa-aumChart')?.getContext('2d');
if (aumCtx) {
mshfpa_aumChartInstance = new Chart(aumCtx, {
type: 'pie',
data: {
labels: strategyNames,
datasets: [{
label: 'AUM ($)',
data: strategyAums,
backgroundColor: chartBackgroundColor,
borderColor: chartBorderColor,
borderWidth: 1
}]
},
options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: 'bottom' } } }
});
}
// Return Bar Chart
const returnCtx = document.getElementById('mshfpa-returnChart')?.getContext('2d');
if (returnCtx) {
mshfpa_returnChartInstance = new Chart(returnCtx, {
type: 'bar',
data: {
labels: strategyNames,
datasets: [{
label: 'Annualized Return (%)',
data: strategyReturns,
backgroundColor: chartBackgroundColor,
borderColor: chartBorderColor,
borderWidth: 1
}]
},
options: { responsive: true, maintainAspectRatio: false, scales: { y: { beginAtZero: true, ticks: { callback: function(value) { return value + '%';} } } } }
});
}
// Sharpe Ratio Bar Chart
const sharpeCtx = document.getElementById('mshfpa-sharpeChartCanvas')?.getContext('2d');
if (sharpeCtx) {
mshfpa_sharpeChartInstance = new Chart(sharpeCtx, {
type: 'bar',
data: {
labels: strategyNames,
datasets: [{
label: 'Sharpe Ratio',
data: strategySharpes,
backgroundColor: chartBackgroundColor,
borderColor: chartBorderColor,
borderWidth: 1
}]
},
options: { responsive: true, maintainAspectRatio: false, scales: { y: { beginAtZero: true } } }
});
}
}
async function mshfpa_downloadPDF() {
const downloadButton = document.getElementById('mshfpa-downloadPdfButton');
if (!downloadButton) return;
const originalButtonText = downloadButton.textContent;
downloadButton.textContent = 'Generating PDF...';
downloadButton.disabled = true;
const { jsPDF } = window.jspdf;
const reportContentElement = document.getElementById('mshfpa-reportOutputContainerForPdf');
if (!reportContentElement) {
alert("Error: Report content element not found for PDF generation.");
downloadButton.textContent = originalButtonText;
downloadButton.disabled = false;
return;
}
if (mshfpa_strategies.length === 0) {
alert("No data to export. Please add strategies first.");
downloadButton.textContent = originalButtonText;
downloadButton.disabled = false;
return;
}
// Ensure charts are rendered before capturing for PDF
// In some cases, a small timeout helps ensure canvas elements are fully rendered
// For this version, we assume charts are already rendered by mshfpa_updateDashboardAndReport
// If charts are complex or load async, more robust handling might be needed.
try {
// Temporarily make sure the content is fully visible for html2canvas
const originalDisplay = reportContentElement.style.display;
reportContentElement.style.display = 'block'; // Ensure it's visible if it wasn't
// For better PDF quality, especially with charts:
// 1. Clone the report content.
// 2. Explicitly set chart dimensions on the cloned canvases if needed.
// 3. Convert canvas charts to images and embed them in the cloned content.
// This step makes the PDF generation more robust for charts.
const clonedReportElement = reportContentElement.cloneNode(true);
document.body.appendChild(clonedReportElement); // Append to body to ensure rendering for html2canvas
// Replace canvas elements with images in the cloned node
const canvasElements = clonedReportElement.querySelectorAll('canvas');
for (const canvas of canvasElements) {
// Find original chart instance by canvas ID to get its image
let originalChartInstance = null;
if (canvas.id === 'mshfpa-aumChart') originalChartInstance = mshfpa_aumChartInstance;
else if (canvas.id === 'mshfpa-returnChart') originalChartInstance = mshfpa_returnChartInstance;
else if (canvas.id === 'mshfpa-sharpeChartCanvas') originalChartInstance = mshfpa_sharpeChartInstance;
if (originalChartInstance) {
const imgData = originalChartInstance.toBase64Image();
const img = document.createElement('img');
img.src = imgData;
img.style.maxWidth = '100%'; // Ensure image fits within PDF
img.style.height = 'auto';
canvas.parentNode.replaceChild(img, canvas);
}
}
// Apply some basic styling to the cloned element for PDF consistency
clonedReportElement.style.padding = '20px';
clonedReportElement.style.backgroundColor = 'white';
clonedReportElement.style.fontFamily = getComputedStyle(document.documentElement).getPropertyValue('--mshfpa-font-family').trim();
clonedReportElement.style.color = getComputedStyle(document.documentElement).getPropertyValue('--mshfpa-text-color').trim();
// Ensure table borders are visible in PDF
const tablesInClone = clonedReportElement.querySelectorAll('.mshfpa-strategy-table');
tablesInClone.forEach(table => {
table.style.borderCollapse = 'collapse'; // Important for html2canvas
const ths = table.querySelectorAll('th');
const tds = table.querySelectorAll('td');
[...ths, ...tds].forEach(cell => {
cell.style.border = '1px solid #cccccc'; // Light gray border
});
ths.forEach(th => {
th.style.backgroundColor = '#f0f0f0'; // Light gray header
});
});
const canvas = await html2canvas(clonedReportElement, {
scale: 2, // Improves quality
useCORS: true, // If any external images were used (not in this case)
logging: false,
onclone: (documentClone) => { // This is useful if you need to modify the cloned document before rendering
// Example: Remove scripts or interactive elements that shouldn't be in the PDF
}
});
const imgData = canvas.toDataURL('image/png');
const pdf = new jsPDF({
orientation: 'p', // portrait
unit: 'pt', // points
format: 'a4' // page format
});
const pdfWidth = pdf.internal.pageSize.getWidth();
const pdfHeight = pdf.internal.pageSize.getHeight();
const imgWidth = canvas.width;
const imgHeight = canvas.height;
const ratio = Math.min(pdfWidth / imgWidth, pdfHeight / imgHeight);
const imgX = (pdfWidth - imgWidth * ratio) / 2;
const imgY = 15; // Small top margin
pdf.addImage(imgData, 'PNG', imgX, imgY, imgWidth * ratio, imgHeight * ratio);
const fundNameForFile = (document.getElementById('mshfpa-fundName')?.value || "FundAnalysis").replace(/[^a-z0-9]/gi, '_').toLowerCase();
pdf.save(`${fundNameForFile}_performance_report.pdf`);
// Cleanup
document.body.removeChild(clonedReportElement);
reportContentElement.style.display = originalDisplay; // Restore original display
} catch (error) {
console.error("Error generating PDF:", error);
alert("An error occurred while generating the PDF. Please check the console for details.");
} finally {
downloadButton.textContent = originalButtonText;
downloadButton.disabled = false;
}
}