// SARS-CoV-2-Viz // Animated COVID case count visualization // Copyright 2022 Edward L. Platt const covidUrl = "https://raw.githubusercontent.com/CSSEGISandData/COVID-19/master/csse_covid_19_data/csse_covid_19_time_series/time_series_covid19_confirmed_US.csv"; // Parse covid tsv function parseCovidData(tsv, onProgress) { let rows = tsv.split(/[\r\n]+/); let keys = rows[0].split("\t"); let dates = keys.slice(11); // Initialize data array let dataForDay = []; for (const date of dates) { dataForDay.push([]); } // Iterate through non-header rows const rowCount = rows.length - 1; let percent = 0; for (const [index, row] of rows.slice(1).entries()) { onProgress("0 of ${rowCount} rows"); const rowData = tsvRowToJSON(keys, row); // Only update progress when it has actually changed let newPercent = Math.floor(100 * index / rowCount); if (newPercent > percent) { percent = newPercent; } let FIPS = rowData.FIPS; if (typeof(FIPS) === "undefined") { continue; } FIPS = FIPS.slice(0, FIPS.indexOf(".") || FIPS.length).padStart(5, "0"); let lastSeven = []; for (const [index, date] of dates.entries()) { const newCases = index == 0 ? parseInt(rowData[date]) : parseInt(rowData[date]) - parseInt(rowData[dates[index - 1]]); // Update array for running average if (lastSeven.length == 7) { lastSeven.shift(); } lastSeven.push(newCases); const mean = Math.round( lastSeven.reduce((x, y) => x + y) / lastSeven.length); dataForDay[index].push({ FIPS: FIPS, date: date, count: newCases, sevenDayMean: mean }); } } onProgress("${rowCount} of ${rowCount}"); // Calculate maximum case count for each day let maxSoFar = 0; for (const [day, date] of dates.entries()) { let dayMax = Math.max( ...dataForDay[day].map( d => d.sevenDayMean)); maxSoFar = Math.max(maxSoFar, dayMax); // Add data to each county for current day for (let [countyIndex, d] of dataForDay[day].entries()) { d.dayMax = dayMax; d.maxSoFar = maxSoFar; } } // Convert day's data from array to object keyed on FIPS return dataForDay.map(data => { const entries = new Map(data.map(d => [d.FIPS, d])); return Object.fromEntries(entries); }); } async function getData(onProgress) { const content = await fetchWithProgress(covidUrl, onProgress); return content; } async function parseData(content, onProgress) { onProgress("initializing..."); let tsv = csvToTSV( content, (progress) => onProgress("parsing csv: {$progress}")); let dataForDay = parseCovidData( tsv, (progress) => onProgress("building data structure: {$progress}")); onProgress("done"); return dataForDay; } /* Add normalization data to each data element in dataForDay. Parameters dataForDay: [{FIPS_1: d1, FIPS_2: d2, ...}, ... ] */ function normalizeData(dataForDay, population) { let maxSoFar = 0; let maxSoFarPer100KCap = 0; let normalizedForDay = []; for (const [day, data] of dataForDay.entries()) { let normalized = {}; // Add normalized counts for (let [FIPS, d] of Object.entries(data)) { const countyPop = population[FIPS]; if (FIPS && countyPop && !Number.isNaN(countyPop) && countyPop > 0) { d.sevenDayMeanPer100KCap = 100000 * d.sevenDayMean / countyPop; normalized[FIPS] = d; } } // Calculate daily maxima let dayMax = Math.max( ...Object.entries(normalized).map( (entry) => entry[1].sevenDayMean)); let dayMaxPer100KCap = Math.max( ...Object.entries(normalized).map( (entry) => entry[1].sevenDayMeanPer100KCap)); maxSoFar = Math.max(maxSoFar, dayMax); maxSoFarPer100KCap = Math.max(maxSoFarPer100KCap, dayMaxPer100KCap); // Add data to each county for current day for (let [FIPS, d] of Object.entries(normalized)) { d.dayMax = dayMax; d.maxSoFar = maxSoFar; d.dayMaxPer100KCap = dayMaxPer100KCap; d.maxSoFarPer100KCap = maxSoFarPer100KCap; } // Add normalized datum to result normalizedForDay.push(normalized); } return normalizedForDay; }