Upgrade to Pro — share decks privately, control downloads, hide ads and more …

The Making of a Coronavirus Dashboard

The Making of a Coronavirus Dashboard

Avatar for Philipp Naderer

Philipp Naderer

September 30, 2022
Tweet

More Decks by Philipp Naderer

Other Decks in Programming

Transcript

  1. Outline 1. (Open) Data around COVID 2. Data Pipelines 3.

    ORF.at's dashboard vue-corona 4. Widgetize parts of a Vue SPA
  2. www.data.gv.at/covid-19/ • Vaccinations (BMSGPK) • Green Pass / Certi fi

    cates (BMSGPK) • „Morgenmeldung“ SKKM ⚠ 
 (BMI / BMSGPK) • „Morgenmeldung“ EMS ⚠ 
 (BMSGPK) • COVID Cases / Deaths (AGES) • Corona-Ampel 🚦 • Re ff (AGES) Where is the data?
  3. Deaths per Day ~ 3.100 additional COVID deaths a week

    ago https://orf.at/stories/3260942/
  4. Why RingoJS? • Our internal runtime of choice since years.

    • Based on the JVM • Multi-threaded JavaScript • JVM Memory Modell with huge heap: -Xms2G -Xmx4G • Java Libraries accessible via JavaScript • Spring, Guava, Apache Commons, Geo-Tools, …
  5. Key Facts • Vue 2 with vuex + vue-router •

    67 Components • Graphing with Plotly JS / D3 • Maps with Lea fl et ORF.at Dashboard
  6. Take Care of User’s Resources Memory Network Full Visit 110

    MB 7.2 MB Cases 20 MB 1.3 MB Vaccinations 43 MB 2 MB
  7. Widgetizing: What is the Goal? <script 
 defer 
 src="//orf.at/corona/daten/js/widgets.js"

    
 data-component="Incidence" 
 data-area-key="9" ></script> • Display a Vue component in a standalone mode. • Embeddable & customizable via <script> • Always use the latest version of vue-corona!
  8. Vue.js Built-in Special Elements <component> and <slot> • Not real

    components, will be compiled away. • Well known: Slots distribute content into components. • Less known: Dynamic components with the meta component. ✨ <component :is="" /> ✨
  9. What do we need? 1⃣ WidgetApp.vue = second root component

    2⃣ widgets.js = initializes WidgetApp + renders it into the DOM 3⃣ webpack con fi guration = packs everything together 4⃣ CMS Integration / Widget Chooser
  10. 1⃣ Create a separate WidgetApp.vue // Import all needed components

    import Ampel from '@/views/Ampel' import Incidence from '@/components/Incidence' import EpiDiff from '@/components/EpiDiff' import Vaccinations from '@/components/Vaccinations' // Bind the meta component to a state <template> <div class="corona-widget"> <component :is="componentName" // One of the imported components. :areaKey="areaKey" // Optional; pre-selects a state. /> </div> </template>
  11. 1⃣ Create a separate WidgetApp.vue // De fi nes the

    WidgetApp component + imports global SCSS styles <script> export default { props: { componentName: String, areaKey: Number, }, components: { 
 Ampel, Incidence, EpiDiff, Vaccinations, }, } </script> <style lang="scss"> .corona-widget { @import "global.scss"; } </style>
  12. 2⃣ Glue everything together in widgets.js const embedScript = document.currentScript;

    
 const componentName = embedScript.dataset.component; const areaKey = embedScript.dataset.areaKey; Promise.all([ import( /* webpackChunkName: "widgets-package" * / 'vue'), import( /* webpackChunkName: "widgets-package" * / './store'), import( /* webpackChunkName: "widgets-package" * / './WidgetApp'), ]).then(([{default: Vue}, {default: store}, {default: WidgetApp}]) => { store.dispatch('LOAD_TEXT_BLURBS'); const $app = document.createElement('div'); embedScript.parentNode.insertBefore($app, embedScript); new Vue({ store, render: h => h(WidgetApp, { props: { componentName, areaKey } }) }).$mount($app); });
  13. 3⃣ vue.config.js – new webpack entry point config .entry('widgets') .add('./src/widgets.js')

    .end() .output .filename((entry) => { 
 // Fixed filename for widgets, drops the hash! if (entry.chunk.id === 'widgets') { return 'js/widgets.js' } 
 // Hashed filename for everything else … return `js/${entry.chunk.name}.${entry.hash.slice(0,8)}.js`; });
  14. 4⃣ Embed in Stories {{{ <script 
 defer 
 src="//orf.at/corona/daten/js/widgets.js"

    
 data-area-key="9" 
 data-component="Incidence" ></script> }}}
  15. Common Technical Errors 🇦🇹 Austria – AGES / BMSGPK /

    BMI • Number of total tests is 0 🙄 • Day in timeline missing 🙈 • CSV fi les with wrong separator 🥹 • Invalid encoding, e.g. Windows-1252 🤖 • Invalid Date of Death, e.g. 01.02.2020 🗓
  16. Common Technical Errors 🇪🇺 ECDC / European Centre for Disease

    Prevention and Control • JSON invalid, e.g. using 1234,0 as Number. • JSON not validating against the known schema. • Inconsistent naming "Dark Gray" vs. "dark grey". • Invalid / mixed encoding instead of UTF-8.