${state2Name}
Population: ${this.formatNumber(data2.population)}
Med. Income: $${this.formatNumber(data2.medianHouseholdIncome)}
Age (65+): ${data2.ageDistribution['65+'] || 'N/A'}%
Net Migration: ${this.formatNumber(migStats2.netMigration)}
`;
// Comparison Chart (e.g., Population)
const compCtx = document.getElementById('dm-comparison-population-chart').getContext('2d');
if (this.charts['comparisonPopulation']) {
this.charts['comparisonPopulation'].destroy();
}
this.charts['comparisonPopulation'] = new Chart(compCtx, {
type: 'bar',
data: {
labels: ['Population', 'Median Income'],
datasets: [
{
label: state1Name,
data: [data1.population, data1.medianHouseholdIncome],
backgroundColor: 'var(--dm-primary-color)',
},
{
label: state2Name,
data: [data2.population, data2.medianHouseholdIncome],
backgroundColor: 'var(--dm-secondary-color)',
}
]
},
options: {
responsive: true, maintainAspectRatio: false,
plugins: { legend: { position: 'top' } },
scales: { y: { beginAtZero: true } }
}
});
},
downloadActiveTabPDF() {
const { jsPDF } = window.jspdf;
const doc = new jsPDF();
const activeTabContent = document.querySelector('.dm-tab-content.active');
const activeTabId = activeTabContent.id;
let fileName = "demographics_migration_analysis.pdf";
doc.setFontSize(18);
doc.setTextColor(getComputedStyle(document.documentElement).getPropertyValue('--dm-primary-color').trim());
let initialY = 20;
const addText = (text, x, y, size = 12, color = getComputedStyle(document.documentElement).getPropertyValue('--dm-dark-text-color').trim()) => {
doc.setFontSize(size);
doc.setTextColor(color);
doc.text(text, x, y);
return y + (size * 0.5); // Approximate line height
};
const addTable = (head, body, startY) => {
doc.autoTable({
head: [head],
body: body,
startY: startY,
theme: 'grid',
headStyles: {
fillColor: getComputedStyle(document.documentElement).getPropertyValue('--dm-primary-color').trim(),
textColor: getComputedStyle(document.documentElement).getPropertyValue('--dm-white-color').trim(),
fontStyle: 'bold'
},
styles: { font: 'helvetica', fontSize: 10, cellPadding: 2 },
didDrawPage: function (data) { // Add footer with page number
doc.setFontSize(8);
doc.setTextColor(100);
doc.text('Page ' + doc.internal.getNumberOfPages(), data.settings.margin.left, doc.internal.pageSize.height - 10);
}
});
return doc.lastAutoTable.finalY + 10;
};
const addChartToPDF = (chartInstance, yPos) => {
if (chartInstance && chartInstance.canvas) {
try {
const imgData = chartInstance.toBase64Image('image/png', 1.0);
const imgProps = doc.getImageProperties(imgData);
const pdfWidth = doc.internal.pageSize.getWidth() - 30; // page width - margins
const pdfHeight = (imgProps.height * pdfWidth) / imgProps.width;
if (yPos + pdfHeight > doc.internal.pageSize.getHeight() - 20) { // Check if chart fits
doc.addPage();
yPos = 20;
}
doc.addImage(imgData, 'PNG', 15, yPos, pdfWidth, pdfHeight);
return yPos + pdfHeight + 10;
} catch (e) { console.error("Error adding chart to PDF:", e); return yPos; }
}
return yPos;
};
if (activeTabId === 'dm-demographics-tab') {
const year = document.getElementById('dm-year-select-demo').value;
const state = document.getElementById('dm-state-select-demo').value;
fileName = `demographics_${state}_${year}.pdf`;
doc.text(`Demographic Overview: ${state} (${year})`, 14, initialY);
initialY += 10;
if (this.stateDemographics[year] && this.stateDemographics[year][state]) {
const data = this.stateDemographics[year][state];
initialY = addText(`Population: ${this.formatNumber(data.population)}`, 14, initialY);
initialY = addText(`Median Household Income: $${this.formatNumber(data.medianHouseholdIncome)}`, 14, initialY);
initialY = addText(`Age Distribution:`, 14, initialY);
const ageTableBody = Object.entries(data.ageDistribution).map(([group, percent]) => [group, `${percent}%`]);
initialY = addTable(['Age Group', 'Percentage'], ageTableBody, initialY);
initialY = addChartToPDF(this.charts['ageDistribution'], initialY);
} else {
initialY = addText(`No data available for ${state} in ${year}.`, 14, initialY);
}
} else if (activeTabId === 'dm-migration-tab') {
const year = document.getElementById('dm-year-select-mig').value;
const state = document.getElementById('dm-state-select-mig').value;
fileName = `migration_${state}_${year}.pdf`;
doc.text(`Migration Flow Analysis: ${state} (${year})`, 14, initialY);
initialY += 10;
if (this.migrationFlows[year] && state) {
let totalInflow = 0, totalOutflow = 0;
const inflows = [], outflows = [];
this.migrationFlows[year].forEach(flow => {
if (flow.to === state) { totalInflow += flow.movers; inflows.push({ state: flow.from, movers: flow.movers }); }
if (flow.from === state) { totalOutflow += flow.movers; outflows.push({ state: flow.to, movers: flow.movers }); }
});
inflows.sort((a,b) => b.movers - a.movers);
outflows.sort((a,b) => b.movers - a.movers);
initialY = addText(`Total In-Flow: ${this.formatNumber(totalInflow)}`, 14, initialY);
initialY = addText(`Total Out-Flow: ${this.formatNumber(totalOutflow)}`, 14, initialY);
initialY = addText(`Net Migration: ${this.formatNumber(totalInflow - totalOutflow)}`, 14, initialY, 12, totalInflow - totalOutflow >= 0 ? getComputedStyle(document.documentElement).getPropertyValue('--dm-secondary-color').trim() : getComputedStyle(document.documentElement).getPropertyValue('--dm-accent-color').trim() );
initialY += 5;
initialY = addText(`Top In-Flow Sources:`, 14, initialY);
const inflowTableBody = inflows.slice(0,5).map(s => [s.state, this.formatNumber(s.movers)]);
initialY = addTable(['State', 'Movers'], inflowTableBody.length ? inflowTableBody : [['No inflow data in sample','']], initialY);
initialY = addChartToPDF(this.charts['inflowChart'], initialY);
if (initialY > doc.internal.pageSize.getHeight() - 60) { doc.addPage(); initialY = 20; } // Check space before next section
initialY = addText(`Top Out-Flow Destinations:`, 14, initialY);
const outflowTableBody = outflows.slice(0,5).map(s => [s.state, this.formatNumber(s.movers)]);
initialY = addTable(['State', 'Movers'], outflowTableBody.length ? outflowTableBody : [['No outflow data in sample','']], initialY);
initialY = addChartToPDF(this.charts['outflowChart'], initialY);
} else {
initialY = addText(`No migration data available for ${state} in ${year}.`, 14, initialY);
}
} else if (activeTabId === 'dm-comparison-tab') {
const year = document.getElementById('dm-year-select-comp').value;
const state1 = document.getElementById('dm-state1-select-comp').value;
const state2 = document.getElementById('dm-state2-select-comp').value;
fileName = `comparison_${state1}_vs_${state2}_${year}.pdf`;
doc.text(`State Comparison: ${state1} vs ${state2} (${year})`, 14, initialY);
initialY += 10;
const data1 = this.stateDemographics[year] ? this.stateDemographics[year][state1] : null;
const data2 = this.stateDemographics[year] ? this.stateDemographics[year][state2] : null;
if (data1 && data2) {
const getMigrationStats = (stateNameForStats) => {
let totalInflow = 0, totalOutflow = 0;
if (this.migrationFlows[year]) {
this.migrationFlows[year].forEach(flow => {
if (flow.to === stateNameForStats) totalInflow += flow.movers;
if (flow.from === stateNameForStats) totalOutflow += flow.movers;
});
}
return { totalInflow, totalOutflow, netMigration: totalInflow - totalOutflow };
};
const migStats1 = getMigrationStats(state1);
const migStats2 = getMigrationStats(state2);
const compTableBody = [
['Population', this.formatNumber(data1.population), this.formatNumber(data2.population)],
['Median Income', `$${this.formatNumber(data1.medianHouseholdIncome)}`, `$${this.formatNumber(data2.medianHouseholdIncome)}`],
['Age (65+)', `${data1.ageDistribution['65+'] || 'N/A'}%`, `${data2.ageDistribution['65+'] || 'N/A'}%`],
['Net Migration', this.formatNumber(migStats1.netMigration), this.formatNumber(migStats2.netMigration)]
];
initialY = addTable(['Metric', state1, state2], compTableBody, initialY);
initialY = addChartToPDF(this.charts['comparisonPopulation'], initialY);
} else {
initialY = addText(`Data not available for comparison in ${year}.`, 14, initialY);
}
} else {
doc.text('No active tab found or data to report.', 14, initialY);
}
// Add generated date footer on the last page if not handled by autoTable's didDrawPage for all pages
const finalPageNum = doc.internal.getNumberOfPages();
doc.setPage(finalPageNum); // Go to last page
doc.setFontSize(8);
doc.setTextColor(100); // Gray
doc.text(`Report generated on: ${new Date().toLocaleDateString()} ${new Date().toLocaleTimeString()}`, 14, doc.internal.pageSize.getHeight() - 10);
doc.save(fileName);
}
};
document.addEventListener('DOMContentLoaded', () => {
dmAnalyzer.init();
});