This repository has been archived by the owner on Feb 15, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'main' of github.com:DerLobi/impfdashboard-scriptable-wi…
…dget into main
- Loading branch information
Showing
1 changed file
with
1 addition
and
1 deletion.
There are no files selected for viewing
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
{"always_run_in_app":false,"icon":{"color":"blue","glyph":"syringe"},"name":"Impfdashboard","script":"","share_sheet_inputs":[]} | ||
{"always_run_in_app":false,"icon":{"color":"blue","glyph":"syringe"},"name":"Impfdashboard","script":"// Impfdashboard Widget\n//\n// MIT License\n//\n// Copyright (c) 2021 Christian Lobach\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all\n// copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\n\nconst localizedStrings = {\n 'first.vaccinations': {\n de: 'Mindestens\\nErstgeimpfte',\n en: 'At least first\\nvaccinations',\n },\n 'cumulative.doses': {\n de: 'Verabreichte\\nImpfdosen',\n en: 'Administered\\ndoses',\n },\n 'fully.vaccinated': {\n de: 'Vollständig\\nGeimpfte',\n en: 'Fully\\nvaccinated',\n },\n\n};\n\nconst widget = new ListWidget();\nconst { locale, language } = Device;\nconst horizontalPadding = 12;\nconst verticalPadding = 8;\n\nconfig.widgetFamily ||= 'large';\n\nconst titleFont = Font.mediumSystemFont(config.widgetFamily == 'large' ? 12 : 10);\nconst valueFont = Font.mediumSystemFont(config.widgetFamily == 'large' ? 20 : 16);\n\nconst lightBlue = new Color('#9DCCE5');\nconst darkBlue = new Color('#3392C5');\n\nconst dateFormatter = new DateFormatter();\ndateFormatter.dateFormat = 'E';\n\nwidget.setPadding(verticalPadding, horizontalPadding, verticalPadding, horizontalPadding);\nwidget.url = 'https://impfdashboard.de';\n\n\nlet data = await loadData();\n\nconst headerStack = widget.addStack();\nconst header = headerStack.addText('💉 Impfdashboard'.toUpperCase());\nheader.font = Font.mediumSystemFont(10);\n\nwidget.addSpacer(4);\n\nbuildLayout(widget);\n\nScript.setWidget(widget);\nScript.complete();\nwidget.presentLarge();\n\nfunction buildLayout(widget) {\n const chart = createChart(data.dosesLastWeeks, config.widgetFamily);\n\n switch (config.widgetFamily) {\n case 'large':\n const largeOuterVStack = widget.addStack();\n largeOuterVStack.layoutVertically();\n largeOuterVStack.centerAlignContent();\n largeOuterVStack.addSpacer();\n const largeHStack = largeOuterVStack.addStack();\n largeHStack.setPadding(0, 16, 0, 16);\n createStack(largeHStack, data.firstVaccinations);\n largeHStack.addSpacer();\n createStack(largeHStack, data.dosesCumulative);\n largeHStack.addSpacer();\n createStack(largeHStack, data.fullVaccinations);\n largeOuterVStack.addSpacer();\n largeOuterVStack.addImage(chart).centerAlignImage();\n\n const largePadded = largeOuterVStack.addStack();\n largePadded.setPadding(16, 0, 16, 0);\n largePadded.addSpacer();\n createStack(largePadded, data.dosesToday, true, true);\n largePadded.addSpacer();\n\n break;\n case 'medium':\n const outerHStack = widget.addStack();\n\n const vStack = outerHStack.addStack();\n vStack.setPadding(verticalPadding, 0, verticalPadding, 0);\n vStack.layoutVertically();\n createStack(vStack, data.firstVaccinations);\n vStack.addSpacer(verticalPadding);\n createStack(vStack, data.fullVaccinations);\n outerHStack.addSpacer(horizontalPadding);\n const secondaryStack = outerHStack.addStack();\n secondaryStack.layoutVertically();\n\n secondaryStack.addImage(chart);\n secondaryStack.addSpacer();\n const mediumPadded = secondaryStack.addStack();\n mediumPadded.addSpacer();\n createStack(mediumPadded, data.dosesToday, true, true);\n mediumPadded.addSpacer();\n\n break;\n default:\n const outerVStack = widget.addStack();\n outerVStack.layoutVertically();\n outerVStack.centerAlignContent();\n const hStack = outerVStack.addStack();\n createStack(hStack, data.firstVaccinations);\n hStack.addSpacer();\n createStack(hStack, data.fullVaccinations);\n outerVStack.addImage(chart);\n\n const padded = outerVStack.addStack();\n padded.addSpacer();\n createStack(padded, data.dosesToday, true, true);\n padded.addSpacer();\n break;\n }\n}\n\nfunction createStack(superView, data, inverse = false, centerAlignText = false) {\n const vStack = superView.addStack();\n vStack.layoutVertically();\n if (centerAlignText) {\n vStack.centerAlignContent();\n }\n if (inverse == false) {\n const title = vStack.addText(data.title);\n title.font = titleFont;\n if (centerAlignText) {\n title.centerAlignText();\n }\n }\n const value = vStack.addText(data.stringValue);\n value.font = valueFont;\n value.textColor = darkBlue;\n if (centerAlignText) {\n value.centerAlignText();\n }\n if (inverse) {\n const title = vStack.addText(data.title);\n title.font = titleFont;\n if (centerAlignText) {\n title.centerAlignText();\n }\n }\n\n return vStack;\n}\n\nasync function loadData() {\n const url = 'https://github.com/DerLobi/impfdashboard-scriptable-widget/raw/main/data/data.json';\n const request = new Request(url);\n const records = await request.loadJSON();\n\n const lastRecord = records[records.length - 1];\n\n const data = {\n firstVaccinations: {\n title: localized('first.vaccinations'),\n stringValue: lastRecord.impf_quote_erst.toLocaleString(locale, {\n style: 'percent',\n minimumFractionDigits: 0,\n maximumFractionDigits: 2,\n }),\n },\n fullVaccinations: {\n title: localized('fully.vaccinated'),\n stringValue: lastRecord.impf_quote_voll.toLocaleString(locale, {\n style: 'percent',\n minimumFractionDigits: 0,\n maximumFractionDigits: 2,\n }),\n },\n dosesCumulative: {\n title: localized('cumulative.doses'),\n stringValue: new Intl.NumberFormat(locale, { notation: 'compact', compactDisplay: 'short', maximumFractionDigits: 1 }).format(lastRecord.dosen_kumulativ),\n },\n dosesToday: {\n title: new Date(lastRecord.date).toLocaleString(locale, {\n weekday: 'long',\n year: '2-digit',\n month: '2-digit',\n day: '2-digit',\n }),\n stringValue: `+${lastRecord.dosen_differenz_zum_vortag.toLocaleString()}`,\n },\n dosesLastWeeks: records.map((record) => ({\n date: record.date,\n amount: record.dosen_differenz_zum_vortag,\n })),\n };\n\n return data;\n}\n\nfunction createChart(data, widgetFamily) {\n let size = new Size(400, 120);\n let dataSeries = data;\n\n switch (widgetFamily) {\n case 'large':\n size = new Size(800, 400);\n break;\n case 'medium':\n size = new Size(640, 240);\n dataSeries = data.slice(data.length - 7, data.length);\n break;\n default:\n dataSeries = data.slice(data.length - 7, data.length);\n break;\n }\n\n const ctx = new DrawContext();\n ctx.opaque = false;\n ctx.respectScreenScale = true;\n ctx.size = size;\n\n const sorted = [...dataSeries];\n sorted.sort((lhs, rhs) => rhs.amount - lhs.amount);\n const maximum = sorted[0].amount;\n\n const textHeight = 20;\n const availableHeight = size.height - textHeight;\n const spacing = 4;\n const barWidth = (size.width - ((dataSeries.length - 1) * spacing)) / dataSeries.length;\n\n ctx.setFont(Font.mediumSystemFont(18));\n ctx.setTextColor(Color.gray());\n ctx.setTextAlignedCenter();\n\n for (i = 0; i < dataSeries.length; i++) {\n const day = dataSeries[i];\n const path = new Path();\n const x = (i * spacing + i * barWidth);\n const value = day.amount;\n const heightFactor = value / maximum;\n const barHeight = heightFactor * availableHeight;\n const rect = new Rect(x, size.height - barHeight, barWidth, barHeight);\n path.addRoundedRect(rect, 4, 4);\n ctx.addPath(path);\n\n if (i == dataSeries.length - 1) {\n ctx.setFillColor(darkBlue);\n } else {\n ctx.setFillColor(lightBlue);\n }\n\n ctx.fillPath();\n\n const textRect = new Rect(x, size.height - barHeight - textHeight - 2, barWidth, textHeight);\n const date = new Date(day.date);\n const formattedDate = dateFormatter.string(date);\n\n ctx.drawTextInRect(dateFormatter.string(date), textRect);\n }\n\n const image = ctx.getImage();\n\n return image;\n}\n\nfunction localized(key) {\n if (localizedStrings[key] == null) {\n return key;\n }\n return localizedStrings[key][language()] || localizedStrings[key].en;\n}\n","share_sheet_inputs":[]} |