Demographics & Migration Flow Analyzer

Demographics & Migration Flow Analyzer

This tool uses sample data for demonstration. For real-world analysis, connect to live data sources like the U.S. Census Bureau API.

State Demographic Overview

Select a state and year to see its demographic overview.

State Migration Flow Analysis

Select a state and year to see its migration flow analysis.

Top In-Flows

Top Out-Flows

State Comparison

Select two states and a year to compare their metrics.

Net Migration: ${this.formatNumber(migStats1.netMigration)}

${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(); });
Scroll to Top