Skip to content

Instantly share code, notes, and snippets.

@Chaos53925
Forked from rafaelmaeuer/vaccination-stats.js
Last active February 3, 2023 17:19
Show Gist options
  • Save Chaos53925/5617c8a70ddbaec918b2d6804755c92f to your computer and use it in GitHub Desktop.
Save Chaos53925/5617c8a70ddbaec918b2d6804755c92f to your computer and use it in GitHub Desktop.
A Scriptable widget that shows the amount of people who have received the corona vaccination in Germany
// Version 1.4.7
// 03.02.2023
//
// Original von marco79cgn
//
// Mit Caching und Fallback
// Neue API mit TSV-Parsing
// Dark-Mode Unterstützung
// Dark-Mode kann nun deaktiviert werden
// Bei deaktivierung kann man auch auswählen ob man dauerhaft den Dark- oder Lightmode möchte
// Setup Assistent für unversierte Nutzer hinzugefügt (erweiterbar auf neue Funktionen)
// Das öffnen der Url ist nun abschaltbar
// Mediumsize Ansicht hinzugefügt
// Neue standart Ansicht für die Medium Size Ansicht hinzugefügt
// Alte Ansicht als Alternative Ansicht beibehalten
let darkModeSupport = 2
let urlSupport = 1
let mediumDesign = 1
let widgetInputRAW = args.widgetParameter
let dark
let url
let design
let textColors = new Color("000000")
if (!config.runsInWidget && config.runsInApp) {
const prompt = new Alert()
prompt.message = 'Möchtest du den Setup Assistant starten?'
prompt.addAction('Ja')
prompt.addCancelAction('Nein')
if (await prompt.presentAlert() === 0) {
await setupAssistant()
}
}
// Setup Assistent für unversierte Nutzer um die nötgen Parameter zu erhalten
async function setupAssistant() {
let para = ''
let m
let u
let d
const darkModePrompt = new Alert()
darkModePrompt.message = 'Welche Darkmode Einstellung möchtest du verwenden?'
darkModePrompt.addAction('Automatisch')
darkModePrompt.addAction('Festgelegt')
if (await darkModePrompt.presentAlert() === 0) {
d = "2"
} else {
const darkModeSetPrompt = new Alert()
darkModeSetPrompt.message = 'Bitte leg deine Darkmode Einstellung fest.'
darkModeSetPrompt.addAction('Light')
darkModeSetPrompt.addAction('Dark')
if (await darkModeSetPrompt.present() === 0) {
d = "1"
} else {
d = "0"
}
}
if (d !== null) {
const urlSupportPrompt = new Alert()
urlSupportPrompt.message = 'Soll die Website beim klicken auf das Widget geöffnet werden?'
urlSupportPrompt.addAction('Ja')
urlSupportPrompt.addAction('Nein')
if (await urlSupportPrompt.presentAlert() === 0) {
u = "1"
} else {
u = "0"
}
}
if (m !== null) {
const mediumDesignPrompt = new Alert()
mediumDesignPrompt.message = 'Soll die Medium Ansicht ein alternatives Aussehen verwenden?'
mediumDesignPrompt.addAction('Ja')
mediumDesignPrompt.addAction('Nein')
if (await mediumDesignPrompt.presentAlert() === 0) {
m = "1"
} else {
m = "0"
}
}
para = `${d}${u}${m}`
console.log('Config: ' + para)
Pasteboard.copy(para)
const promptSuccess = new Alert()
promptSuccess.title = 'Setup abgeschlossen'
promptSuccess.message = 'Die für dich passende Konfiguration wurde generiert und in die Zwischenablage kopiert.\nFüge diese nun in das Feld "Parameter" in den Widget Einstellungen ein.'
promptSuccess.addAction('Schließen')
await promptSuccess.present()
}
// convert paraeters
if (widgetInputRAW !== null) {
const parameter = widgetInputRAW.toString()
dark = parameter.charAt(0)
url = parameter.charAt(1)
design = parameter.charAt(2)
if (dark === "0") {
darkModeSupport = 0
} else if (dark === "1") {
darkModeSupport = 1
} else if (dark === "2" || dark === null) {
darkModeSupport = 2
}
if (url === "0") {
urlSupport = 0
} else if (url === "1" || url === null) {
urlSupport = 1
}
if (design === "0") {
mediumDesign = 0
} else if (design === "1" || design === null) {
mediumDesign = 1
}
}
const resetCache = false;
const cacheMinutes = 3600;
const today = new Date();
const neededTotalVaccinations = 83200000;
let result;
let resultDe;
let width
const h = 5;
const colorLightGreen = new Color("#47b881")
const colorDarkGreen = new Color("#00783e")
const colorGreen0 = new Color("#084D20")
const colorGreen1 = new Color("#208045")
const colorGreen2 = new Color("#39AD64")
const colorGreen3 = new Color("#6FC98E")
let widget = new ListWidget();
widget.setPadding(6, 12, 8, 12);
await getNumbers();
await createWidget();
Script.setWidget(widget);
Script.complete();
if (config.runsInApp) {
widget.presentSmall();
}
async function createWidget() {
if (config.widgetFamily === "medium") {
width = 150
} else {
width = 100
}
// Add background color
if (darkModeSupport === 2) {
// dynamisch
textColors = Color.dynamic(textColors, new Color("ffffff"))
widget.backgroundColor = Color.dynamic(new Color("ffffff"), new Color("1c1c1c"));
} else if (darkModeSupport === 1) {
// light
textColors = Color.dynamic(textColors, textColors)
widget.backgroundColor = Color.dynamic(new Color("ffffff"), new Color("ffffff"));
} else if (darkModeSupport === 0) {
// dark
textColors = Color.dynamic(new Color("ffffff"), new Color("ffffff"))
widget.backgroundColor = Color.dynamic(new Color("1c1c1c"), new Color("1c1c1c"));
}
if (urlSupport === 1) {
widget.url = "https://interaktiv.morgenpost.de/corona-impfungen-deutschland-bundeslaender-weltweit/";
} else {
widget.url = null
}
if (config.widgetFamily === 'medium') {
if (mediumDesign === 1) {
widget.addSpacer();
const dataFirst = {
text: "Erstimpfung",
quota: resultDe.vaccinations1_quota,
vaccination: resultDe.vaccinations1,
progress: createProgressMedium(resultDe.vaccinations1),
};
createProgressStackMediumText(dataFirst);
const dataSecond = {
text: "Zweitimpfung",
quota: resultDe.vaccinations2_quota,
vaccination: resultDe.vaccinations2,
progress: createProgressMedium(resultDe.vaccinations2),
};
createProgressStackMediumText(dataSecond);
const dataThird = {
text: "Erster Booster",
quota: resultDe.vaccinations3_quota,
vaccination: resultDe.vaccinations3,
progress: createProgressMedium(resultDe.vaccinations3),
};
createProgressStackMediumText(dataThird);
const dataFourth = {
text: "Zweiter Booster",
quota: resultDe.vaccinations4_quota,
vaccination: resultDe.vaccinations4,
progress: createProgressMedium(resultDe.vaccinations3),
};
createProgressStackMediumText(dataFourth);
const dataBar = {
progress: createProgressMedium(resultDe.vaccinations1, resultDe.vaccinations2, resultDe.vaccinations3, resultDe.vaccinations4),
};
createProgressStackMediumBar(dataBar);
widget.addSpacer(3);
let calendarStack = widget.addStack();
const date = resultDe.date.split('-'); // destructure date
const lastUpdateDate = new Date(date[0], date[1] - 1, date[2]); // fixes -1 day
const options = { year: 'numeric', month: '2-digit', day: '2-digit' };
let description = widget.addText("Stand: " + lastUpdateDate.toLocaleDateString('de-DE', options));
description.centerAlignText();
description.font = Font.mediumSystemFont(9);
description.textColor = textColors
} else {
width = 100
widget.addSpacer();
const dataFirst = {
text: "Erstimpfung",
quota: resultDe.vaccinations1_quota,
vaccination: resultDe.vaccinations1,
progress: createProgressShort(resultDe.vaccinations1),
};
createProgressStackMedium(dataFirst);
const dataSecond = {
text: "Zweitimpfung",
quota: resultDe.vaccinations2_quota,
vaccination: resultDe.vaccinations2,
progress: createProgressShort(resultDe.vaccinations2),
};
createProgressStackMedium(dataSecond);
const dataThird = {
text: "Erster Booster",
quota: resultDe.vaccinations3_quota,
vaccination: resultDe.vaccinations3,
progress: createProgressShort(resultDe.vaccinations3),
};
createProgressStackMedium(dataThird);
const dataFourth = {
text: "Zweiter Booster",
quota: resultDe.vaccinations4_quota,
vaccination: resultDe.vaccinations4,
progress: createProgressShort(resultDe.vaccinations4),
};
createProgressStackMedium(dataFourth);
widget.addSpacer(3);
let calendarStack = widget.addStack();
const date = resultDe.date.split('-'); // destructure date
const lastUpdateDate = new Date(date[0], date[1] - 1, date[2]); // fixes -1 day
const options = { year: 'numeric', month: '2-digit', day: '2-digit' };
let description = widget.addText("Stand: " + lastUpdateDate.toLocaleDateString('de-DE', options));
description.centerAlignText();
description.font = Font.mediumSystemFont(9);
description.textColor = textColors
}
} else {
widget.addSpacer();
const dataFirst = {
text: "Erstimpfung",
quota: resultDe.vaccinations1_quota,
vaccination: resultDe.vaccinations1,
progress: createProgress(resultDe.vaccinations1),
};
createProgressStack(dataFirst);
const dataSecond = {
text: "Zweitimpfung",
quota: resultDe.vaccinations2_quota,
vaccination: resultDe.vaccinations2,
progress: createProgress(resultDe.vaccinations2),
};
createProgressStack(dataSecond);
const dataThird = {
text: "Booster",
quota: resultDe.vaccinations4_quota + " | " + resultDe.vaccinations3_quota,
vaccination: resultDe.vaccinations4 + " | " + resultDe.vaccinations3,
progress: createProgress(resultDe.vaccinations3, resultDe.vaccinations4),
};
createProgressStack(dataThird);
widget.addSpacer(3);
let calendarStack = widget.addStack();
const date = resultDe.date.split('-'); // destructure date
const lastUpdateDate = new Date(date[0], date[1] - 1, date[2]); // fixes -1 day
const options = { year: 'numeric', month: '2-digit', day: '2-digit' };
let description = widget.addText("Stand: " + lastUpdateDate.toLocaleDateString('de-DE', options));
description.centerAlignText();
description.font = Font.mediumSystemFont(9);
description.textColor = textColors
}
}
function createProgressStack(data) {
let firstDoseStack = widget.addStack();
firstDoseStack.layoutHorizontally();
let amountFirstText = firstDoseStack.addText(data.text);
amountFirstText.font = Font.boldSystemFont(12);
amountFirstText.textColor = textColors
firstDoseStack.addSpacer();
let percentFirstText = firstDoseStack.addText(data.quota + "%");
percentFirstText.font = Font.boldSystemFont(12);
percentFirstText.textColor = textColors
widget.addSpacer(2);
let progressStack = widget.addStack();
progressStack.layoutVertically();
let progressNumberStack = widget.addStack();
progressNumberStack.layoutHorizontally();
const progressText = progressNumberStack.addText(numberWithDots(data.vaccination));
progressText.font = Font.mediumSystemFont(10);
progressText.textColor = textColors
progressStack.addImage(data.progress);
widget.addSpacer(3);
}
function createProgressStackMedium(data) {
let firstDoseStack = widget.addStack();
firstDoseStack.layoutHorizontally();
let amountFirstText = firstDoseStack.addText(data.text);
amountFirstText.font = Font.boldSystemFont(12);
amountFirstText.textColor = textColors
firstDoseStack.addSpacer();
let percentFirstText = firstDoseStack.addText(data.quota + "%");
percentFirstText.font = Font.boldSystemFont(12);
percentFirstText.textColor = textColors
widget.addSpacer(2);
let progressText = widget.addStack();
progressText.layoutHorizontally();
let progressNumberText = progressText.addText(numberWithDots(data.vaccination));
progressNumberText.font = Font.mediumSystemFont(10);
progressNumberText.textColor = textColors
progressText.addSpacer(30);
let progressStack = progressText.addImage(data.progress);
widget.addSpacer(3);
}
function createProgressStackMediumText(data) {
let firstDoseStack = widget.addStack();
firstDoseStack.layoutHorizontally();
let amountFirstText = firstDoseStack.addText(data.text);
amountFirstText.font = Font.boldSystemFont(12);
amountFirstText.textColor = textColors
firstDoseStack.addSpacer();
let percentFirstText = firstDoseStack.addText(data.quota + "%");
percentFirstText.font = Font.boldSystemFont(12);
percentFirstText.textColor = textColors
let progressNumberStack = widget.addStack();
progressNumberStack.layoutHorizontally();
const progressText = progressNumberStack.addText(numberWithDots(data.vaccination));
progressText.font = Font.mediumSystemFont(10);
progressText.textColor = textColors
widget.addSpacer(1);
}
function createProgressStackMediumBar(data) {
let progressStack = widget.addStack();
progressStack.layoutVertically();
progressStack.addImage(data.progress);
widget.addSpacer(1);
}
// get images from local storage or download them once (currently not used)
//async function getImage(image) {
// let fm = FileManager.local();
// let dir = fm.documentsDirectory();
// let path = fm.joinPath(dir, image);
// if (fm.fileExists(path)) {
// return fm.readImage(path);
// } else {
// // download once
// let imageUrl;
// switch (image) {
// case "vac-logo.png":
// imageUrl = "https://i.imgur.com/ZsBNT8E.png";
// break;
// case "calendar-icon.png":
// imageUrl = "https://i.imgur.com/Qp8CEFf.png";
// break;
// default:
// console.log(`Sorry, couldn't find ${image}.`);
// }
// let req = new Request(imageUrl);
// let loadedImage = await req.loadImage();
// fm.writeImage(path, loadedImage);
// return loadedImage;
// }
//}
function numberWithDots(x) {
return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ".");
}
async function getNumbers() {
// Set up the file manager.
const files = FileManager.local();
// Set up cache
const cachePath = files.joinPath(
files.cacheDirectory(),
"api-cache-covid-vaccination-numbers-mopo-tsv"
);
const cacheExists = files.fileExists(cachePath);
const cacheDate = cacheExists ? files.modificationDate(cachePath) : 0;
// Get Data
try {
// If cache exists and it's been less than 60 minutes since last request, use cached data.
if (
cacheExists && !resetCache &&
today.getTime() - cacheDate.getTime() < cacheMinutes * 60 * 1000
) {
console.log("Get from Cache");
result = files.readString(cachePath);
} else {
console.log("Get from API");
const req2 = new Request(
"https://cdn.fnki.de/corona-data/vaccinations.rki.tsv"
);
result = await req2.loadString();
console.log("Write Data to Cache");
try {
files.writeString(cachePath, result);
} catch (e) {
console.log("Creating Cache failed!");
console.log(e);
}
}
} catch (e) {
console.error(e);
if (cacheExists) {
console.log("Get from Cache");
result = files.readString(cachePath);
} else {
console.log("No fallback to cache possible. Due to missing cache.");
}
}
await setTotalVacNoForGermany(result);
}
async function setTotalVacNoForGermany(result) {
const cols = result.split(/\r?\n/);
const data = cols.map(col => col.split('\t'));
data.pop(); // remove last (empty) element
const name = data[0];
const vals = data[data.length - 1];
const date = name.indexOf('date');
const vac1 = name.indexOf('vaccinations1');
const vac2 = name.indexOf('vaccinations2');
const vac3 = name.indexOf('vaccinations3');
const vac4 = name.indexOf('vaccinations4');
const vac1Quota = name.indexOf('vaccinations1_quota');
const vac2Quota = name.indexOf('vaccinations2_quota');
const vac3Quota = name.indexOf('vaccinations3_quota');
const vac4Quota = name.indexOf('vaccinations4_quota');
resultDe = {
date: vals[date],
vaccinations1: vals[vac1],
vaccinations1_quota: vals[vac1Quota],
vaccinations2: vals[vac2],
vaccinations2_quota: vals[vac2Quota],
vaccinations3: vals[vac3],
vaccinations3_quota: vals[vac3Quota],
vaccinations4: vals[vac4],
vaccinations4_quota: vals[vac4Quota],
};
}
function createProgress(firstVacNo, secondVacNo) {
const context = new DrawContext();
context.size = new Size(width, h);
context.opaque = false;
context.respectScreenScale = true;
context.setFillColor(new Color("#d2d2d7"));
const path = new Path();
path.addRoundedRect(new Rect(0, 0, width, h), 3, 2);
context.addPath(path);
context.fillPath();
context.setFillColor(colorLightGreen);
const path1 = new Path();
const path1width =
(width * firstVacNo) / neededTotalVaccinations > width
? width
: (width * firstVacNo) / neededTotalVaccinations;
path1.addRoundedRect(new Rect(0, 0, path1width, h), 3, 2);
context.addPath(path1);
context.fillPath();
if (secondVacNo) {
const path2 = new Path();
const path2width =
(width * secondVacNo) / neededTotalVaccinations > width
? width
: (width * secondVacNo) / neededTotalVaccinations;
path2.addRoundedRect(new Rect(0, 0, path2width, h), 3, 2);
context.addPath(path2);
context.setFillColor(colorDarkGreen)
context.fillPath();
}
return context.getImage();
}
function createProgressMedium(firstVacNo, secondVacNo, thirdVacNo, fourthVacNo) {
const context = new DrawContext();
context.size = new Size(width, h);
context.opaque = false;
context.respectScreenScale = true;
context.setFillColor(new Color("#d2d2d7"));
const path = new Path();
path.addRoundedRect(new Rect(0, 0, width, h), 3, 2);
context.addPath(path);
context.fillPath();
const path1 = new Path();
const path1width =
(width * firstVacNo) / neededTotalVaccinations > width
? width
: (width * firstVacNo) / neededTotalVaccinations;
path1.addRoundedRect(new Rect(0, 0, path1width, h), 3, 2);
context.addPath(path1);
context.fillPath();
context.setFillColor(colorGreen3);
if (secondVacNo) {
const path2 = new Path();
const path2width =
(width * secondVacNo) / neededTotalVaccinations > width
? width
: (width * secondVacNo) / neededTotalVaccinations;
path2.addRoundedRect(new Rect(0, 0, path2width, h), 3, 2);
context.addPath(path2);
context.setFillColor(colorGreen2)
context.fillPath();
}
if (thirdVacNo) {
const path2 = new Path();
const path2width =
(width * thirdVacNo) / neededTotalVaccinations > width
? width
: (width * thirdVacNo) / neededTotalVaccinations;
path2.addRoundedRect(new Rect(0, 0, path2width, h), 3, 2);
context.addPath(path2);
context.setFillColor(colorGreen1)
context.fillPath();
}
if (fourthVacNo) {
const path2 = new Path();
const path2width =
(width * fourthVacNo) / neededTotalVaccinations > width
? width
: (width * fourthVacNo) / neededTotalVaccinations;
path2.addRoundedRect(new Rect(0, 0, path2width, h), 3, 2);
context.addPath(path2);
context.setFillColor(colorGreen0)
context.fillPath();
}
return context.getImage();
}
function createProgressShort(firstVacNo) {
const context = new DrawContext();
context.size = new Size(width, h);
context.opaque = false;
context.respectScreenScale = true;
context.setFillColor(new Color("#d2d2d7"));
const path = new Path();
path.addRoundedRect(new Rect(0, 0, width, 3), 2, 2);
context.addPath(path);
context.fillPath();
context.setFillColor(colorLightGreen);
const path1 = new Path();
const path1width =
(width * firstVacNo) / neededTotalVaccinations > width
? width
: (width * firstVacNo) / neededTotalVaccinations;
path1.addRoundedRect(new Rect(0, 0, path1width, 3), 2, 2);
context.addPath(path1);
context.fillPath();
return context.getImage();
}
//
// Bitte bis zum Ende kopieren
//
@Chaos53925
Copy link
Author

Standart Ansichten für Small und Medium.
Stand: 03.02.2023

A7F21224-8857-4F25-BA59-ED829BA8A10E_1_201_a

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment