|
|
|
// SARS-CoV-2-Viz
|
|
|
|
// Animated COVID case count visualization
|
|
|
|
// Copyright 2022 Edward L. Platt <ed@elplatt.com>
|
|
|
|
|
|
|
|
// Convert csv to tsv
|
|
|
|
// Assumes quoted strings do not contain escaped quotes
|
|
|
|
async function csvToTSV(csv, onProgress) {
|
|
|
|
|
|
|
|
onProgress("parsing CSV: 0 of ? parts");
|
|
|
|
|
|
|
|
let quoteParts = csv.split('"');
|
|
|
|
let tsv = "";
|
|
|
|
const rowCount = quoteParts.length;
|
|
|
|
let percent = 0;
|
|
|
|
let partStack = quoteParts.reverse();
|
|
|
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
let index = 0;
|
|
|
|
let processPart = () => {
|
|
|
|
|
|
|
|
while (true) {
|
|
|
|
|
|
|
|
if (partStack.length == 0) {
|
|
|
|
resolve(tsv);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
part = partStack.pop();
|
|
|
|
|
|
|
|
if (index % 2 == 0) {
|
|
|
|
// Not quoted
|
|
|
|
tsv += part.replace(/,/g, "\t");
|
|
|
|
} else {
|
|
|
|
// Quoted
|
|
|
|
tsv += part;
|
|
|
|
}
|
|
|
|
|
|
|
|
index += 1;
|
|
|
|
let newPercent = Math.floor(100 * index / rowCount);
|
|
|
|
if (newPercent > percent) {
|
|
|
|
percent = newPercent;
|
|
|
|
onProgress(`parsing CSV: ${index} of ${rowCount} parts`);
|
|
|
|
setTimeout(processPart, 0);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
setTimeout(processPart, 0);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function tsvRowToJSON(keys, tsvRow) {
|
|
|
|
let row = {};
|
|
|
|
let rowParts = tsvRow.split("\t");
|
|
|
|
for (const [index, key] of keys.entries()) {
|
|
|
|
row[key] = rowParts[index];
|
|
|
|
}
|
|
|
|
return row;
|
|
|
|
}
|
|
|
|
|
|
|
|
function tsvToJSON(tsv, onProgress) {
|
|
|
|
const lines = tsv.split(/\n/);
|
|
|
|
let columnNames = [];
|
|
|
|
let data = [];
|
|
|
|
const count = lines.length;
|
|
|
|
for (const [row, line] of lines.entries()) {
|
|
|
|
if (row == 0) {
|
|
|
|
columnNames = line.split("\t").map((x) => x.trim());
|
|
|
|
} else {
|
|
|
|
let dataRow = {};
|
|
|
|
for (const [col, cell] of line.split("\t").entries()) {
|
|
|
|
dataRow[columnNames[col]] = cell.trim();
|
|
|
|
}
|
|
|
|
// Limit to US Counties
|
|
|
|
if (dataRow["iso3"] == "USA"
|
|
|
|
&& dataRow["Admin2"] != ""
|
|
|
|
&& dataRow["Admin2"] != "Unassigned"
|
|
|
|
&& dataRow["FIPS"] != "") {
|
|
|
|
data.push(dataRow);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
let progress = count / row;
|
|
|
|
onProgress(progress);
|
|
|
|
}
|
|
|
|
return data;
|
|
|
|
}
|
|
|
|
|