Last active
December 21, 2021 00:44
-
-
Save drewkerr/a4df29873571c9f4e45afddf88d67faf to your computer and use it in GitHub Desktop.
COVID-19 Cases in Bendigo, Victoria, Australia
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const url = 'https://covid-sheets-mirror.web.app/api?' | |
const sid = '1nUUU5zPRPlhAXM_-8R7lsMnAkK4jaebvIL5agAaKoXk' | |
function params(obj) { | |
return Object.entries(obj) | |
.map(([key, val]) => `${key}=${encodeURI(val)}`).join("&") | |
} | |
const cases = url + params({ | |
cache: true, | |
sheet: sid, | |
range: 'Deaths Recoveries!A:H' | |
}) | |
const state = url + params({ | |
cache: true, | |
sheet: sid, | |
range: 'Daily Count States!A:E' | |
}) | |
const local = url + params({ | |
cache: true, | |
sheet: sid, | |
range: 'Victorian LGAs!A:D' | |
}) | |
function parseCSV(str, len) { | |
let arr = []; | |
let quote = false; | |
let col, c; | |
for (let row = col = c = 0; c < str.length && row < len; c++) { | |
let cc = str[c], nc = str[c+1]; | |
arr[row] = arr[row] || []; | |
arr[row][col] = arr[row][col] || ''; | |
if (cc == '"' && quote && nc == '"') { arr[row][col] += cc; ++c; continue; } | |
if (cc == '"') { quote = !quote; continue; } | |
if (cc == ',' && !quote) { ++col; continue; } | |
if (cc == '\r' && nc == '\n' && !quote) { ++row; col = 0; ++c; continue; } | |
if (cc == '\n' && !quote) { ++row; col = 0; continue; } | |
if (cc == '\r' && !quote) { ++row; col = 0; continue; } | |
arr[row][col] += cc; | |
} | |
return arr; | |
} | |
function arrayHash(arr) { | |
let head = arr[0] | |
let body = arr.slice(1) | |
return body.map(row => { | |
return row.reduce((acc, v, i) => { | |
let key = head[i] | |
acc[key] = v | |
return acc | |
}, {}) | |
}) | |
} | |
async function getData(url, len) { | |
let req = new Request(url) | |
let txt = await req.loadString() | |
let csv = await parseCSV(txt, len) | |
return await arrayHash(csv) | |
} | |
let casesData = await getData(cases, 2) | |
let stateData = await getData(state, 28*8+1) | |
let localData = await getData(local, 80) | |
let vicData = stateData.filter(data => data["State/territory"] == "VIC") | |
let locData = localData.filter(data => data["LGA"] == "GREATER BENDIGO") | |
let graph = stateData.reduce((a, b) => { | |
let date = b["Date announced"] | |
let cases = parseInt(b["New cases"]) || 0 | |
if (!a[date]) a[date] = cases | |
else a[date] += cases | |
return a | |
}, {}) | |
const weekavg = (n) => Object.values(graph).slice(0 + n, 7 + n).reduce((a, b) => a + b, 0) / 7 | |
let growth = weekavg(0) / weekavg(1) | |
let month = Object.values(graph).slice(0, 28) | |
let data = { | |
"Local active cases": locData[0]["Active Cases"], | |
"Local total cases": locData[0]["Total Cases"], | |
"Victoria new cases": vicData[0]["New cases"], | |
"Victoria total cases": vicData[0]["Cumulative confirmed"], | |
"Australia new cases": Object.values(graph)[0].toString(), | |
"Growth factor (7-day)": growth.toFixed(2), | |
"Australia total cases": casesData[0]["Confirmed (total)"], | |
"Australia total deaths": casesData[0]["Deceased"] | |
} | |
function columnGraph(data, width, height) { | |
let max = Math.max(...data) | |
let context = new DrawContext() | |
context.size = new Size(width, height) | |
context.opaque = false | |
context.setFillColor(Color.white()) | |
data.forEach((value, index) => { | |
let w = width / (2 * data.length - 1) | |
let h = value / max * height | |
let x = width - (index * 2 + 1) * w | |
let y = height - h | |
let rect = new Rect(x, y, w, h) | |
context.fillRect(rect) | |
}) | |
return context | |
} | |
function createWidget() { | |
let w = new ListWidget() | |
w.setPadding(16, 16, 16, 8) | |
function gradient(start, end) { | |
let startColor = new Color(start) | |
let endColor = new Color(end) | |
let gradient = new LinearGradient() | |
gradient.colors = [startColor, endColor] | |
gradient.locations = [0.0, 1] | |
w.backgroundGradient = gradient | |
} | |
if (growth < 1) gradient("37c25a", "1cb943") | |
else gradient("ee676c", "eb5056") | |
let head = `COVID-19 growth ${data["Growth factor (7-day)"]} ` | |
let headText = w.addText(head) | |
headText.textColor = Color.white() | |
headText.textOpacity = 0.8 | |
headText.textSize = 10 | |
headText.rightAlignText() | |
let image = columnGraph(month, 400, 50).getImage() | |
w.addImage(image) | |
w.addSpacer() | |
let body = { | |
"BENDIGO": ` ${data["Local total cases"]} (${data["Local active cases"]} active)`, | |
"VICTORIA": ` ${data["Victoria total cases"]} (+${data["Victoria new cases"]})`, | |
"AUSTRALIA": ` ${data["Australia total cases"]} (+${data["Australia new cases"]})` | |
} | |
Object.entries(body).forEach(([key, value]) => { | |
let labelText = w.addText(key) | |
labelText.textColor = Color.white() | |
labelText.textOpacity = 0.6 | |
labelText.textSize = 10 | |
let bodyText = w.addText(value) | |
bodyText.textColor = Color.white() | |
bodyText.textSize = 14 | |
}) | |
return w | |
} | |
if (config.runsInWidget) { | |
let w = createWidget() | |
Script.setWidget(w) | |
Script.complete() | |
} else { | |
let table = new UITable() | |
let row = new UITableRow() | |
row.isHeader = true | |
row.addText('COVID-19 Statistics in Victoria') | |
table.addRow(row) | |
Object.entries(data).forEach(([key, value]) => { | |
let row = new UITableRow() | |
row.addText(key) | |
row.addText(value).rightAligned() | |
table.addRow(row) | |
}) | |
if (config.runsWithSiri) { | |
Speech.speak(`There are ${data["Local active cases"]} active cases in Bendigo, and ${data["Victoria new cases"]} new cases in Victoria today.`) | |
} | |
table.present() | |
Script.complete() | |
} | |
if (config.runsInApp) { | |
let w = createWidget() | |
w.presentSmall() | |
Script.complete() | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment