`;
};
const renderCharts = () => {
destroyCharts();
// Line Chart - Margin Trends
const lineCtx = document.getElementById('line-chart')?.getContext('2d');
if (lineCtx) {
const labels = dashboardData.financialsOverTime.map(p => p.period);
const grossMarginData = dashboardData.financialsOverTime.map(p => ((p.revenue - p.cogs) / p.revenue) * 100);
const netMarginData = dashboardData.financialsOverTime.map(p => ((p.revenue - p.cogs - p.operatingExpenses) / p.revenue) * 100);
chartInstances.line = new Chart(lineCtx, {
type: 'line',
data: {
labels: labels,
datasets: [
{
label: 'Gross Margin (%)',
data: grossMarginData,
borderColor: 'rgba(5, 150, 105, 1)',
backgroundColor: 'rgba(16, 185, 129, 0.2)',
fill: true,
tension: 0.3
},
{
label: 'Net Margin (%)',
data: netMarginData,
borderColor: 'rgba(2, 132, 199, 1)',
backgroundColor: 'rgba(14, 165, 233, 0.2)',
fill: true,
tension: 0.3
}
]
},
options: {
responsive: true,
maintainAspectRatio: true,
scales: { y: { beginAtZero: true, ticks: { callback: value => `${value.toFixed(1)}%` } } }
}
});
}
// Bar Chart - Revenue vs COGS by Product
const barCtx = document.getElementById('bar-chart')?.getContext('2d');
if (barCtx) {
const labels = dashboardData.byProduct.map(p => p.product);
const revenueData = dashboardData.byProduct.map(p => p.revenue);
const cogsData = dashboardData.byProduct.map(p => p.cogs);
chartInstances.bar = new Chart(barCtx, {
type: 'bar',
data: {
labels: labels,
datasets: [
{
label: 'Revenue',
data: revenueData,
backgroundColor: 'rgba(16, 185, 129, 0.6)',
borderColor: 'rgba(5, 150, 105, 1)',
borderWidth: 1
},
{
label: 'COGS',
data: cogsData,
backgroundColor: 'rgba(245, 158, 11, 0.6)',
borderColor: 'rgba(217, 119, 6, 1)',
borderWidth: 1
}
]
},
options: {
responsive: true,
maintainAspectRatio: true,
scales: { y: { beginAtZero: true, ticks: { callback: value => `$${(value/1000)}K` } } },
}
});
}
// Doughnut Chart - Gross Profit by Region
const doughnutCtx = document.getElementById('doughnut-chart')?.getContext('2d');
if (doughnutCtx) {
const labels = dashboardData.byRegion.map(r => r.region);
const data = dashboardData.byRegion.map(r => r.revenue - r.cogs);
chartInstances.doughnut = new Chart(doughnutCtx, {
type: 'doughnut',
data: {
labels: labels,
datasets: [{
data: data,
backgroundColor: ['#059669', '#0284c7', '#d97706', '#7c3aed'],
hoverOffset: 4,
borderWidth: 0,
}]
},
options: {
responsive: true,
maintainAspectRatio: true,
plugins: {
legend: { position: 'bottom' },
tooltip: {
callbacks: {
label: function(context) {
let label = context.label || '';
if (label) {
label += ': ';
}
if (context.parsed !== null) {
label += new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(context.parsed);
}
return label;
}
}
}
}
}
});
}
};
// --- DATA CONFIGURATION LOGIC ---
const populateConfigData = () => {
dataConfigInput.value = JSON.stringify(dashboardData, null, 2);
};
updateDashboardBtn.addEventListener('click', () => {
try {
const newData = JSON.parse(dataConfigInput.value);
dashboardData = newData; // Update the main data object
jsonErrorDiv.classList.add('hidden');
updateAllViews();
alert('Dashboard updated successfully!');
showTab(0);
} catch (error) {
jsonErrorMessage.textContent = 'Invalid JSON format. Please check your data. ' + error.message;
jsonErrorDiv.classList.remove('hidden');
}
});
// --- PDF GENERATION ---
downloadPdfBtn.addEventListener('click', () => {
const { jsPDF } = window.jspdf;
const pdfContainer = document.getElementById('pdf-content-container');
const dashboardElement = document.getElementById('dashboard-tab');
if (!pdfContainer || !dashboardElement) return;
const clone = dashboardElement.cloneNode(true);
const buttonInClone = clone.querySelector('#download-pdf-btn');
if (buttonInClone) buttonInClone.parentElement.remove();
const canvases = clone.querySelectorAll('canvas');
canvases.forEach(canvas => {
const chartId = canvas.id.split('-')[0];
if (chartInstances[chartId]) {
const image = new Image();
image.src = chartInstances[chartId].toBase64Image();
image.style.width = '100%';
image.style.height = 'auto';
canvas.parentNode.replaceChild(image, canvas);
}
});
pdfContainer.innerHTML = '';
pdfContainer.appendChild(clone);
html2canvas(pdfContainer, {
scale: 2,
useCORS: true,
logging: false,
width: pdfContainer.scrollWidth,
height: pdfContainer.scrollHeight,
}).then(canvas => {
const imgData = canvas.toDataURL('image/png');
const pdf = new jsPDF({
orientation: 'portrait',
unit: 'pt',
format: 'a4'
});
const pdfWidth = pdf.internal.pageSize.getWidth();
const pdfHeight = pdf.internal.pageSize.getHeight();
const imgWidth = canvas.width;
const imgHeight = canvas.height;
const ratio = imgWidth / imgHeight;
let finalImgHeight = pdfWidth / ratio;
let heightLeft = finalImgHeight;
let position = 0;
pdf.addImage(imgData, 'PNG', 0, position, pdfWidth, finalImgHeight);
heightLeft -= pdfHeight;
while (heightLeft > 0) {
position = heightLeft - finalImgHeight;
pdf.addPage();
pdf.addImage(imgData, 'PNG', 0, position, pdfWidth, finalImgHeight);
heightLeft -= pdfHeight;
}
pdf.save('Margin_Analysis_Report.pdf');
pdfContainer.innerHTML = '';
}).catch(err => {
console.error("PDF generation failed:", err);
alert("Sorry, there was an error creating the PDF.");
});
});
// --- INITIALIZATION ---
const updateAllViews = () => {
calculateMetrics();
renderKPIs();
renderCharts();
};
// Initial load
showTab(0);
updateAllViews();
populateConfigData();
});
