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

The Making of a Coronavirus Dashboard

The Making of a Coronavirus Dashboard

Philipp Naderer

September 30, 2022
Tweet

More Decks by Philipp Naderer

Other Decks in Programming

Transcript

  1. Philipp Naderer-Puiu | @botic The Making of a Coronavirus Dashboard

    ORF.at/corona/daten
  2. Outline 1. (Open) Data around COVID 2. Data Pipelines 3.

    ORF.at's dashboard vue-corona 4. Widgetize parts of a Vue SPA
  3. ORF.at Dashboard Peak ~ 500.000 PI / day

  4. Embeddable 
 Components for our (data) journalists

  5. 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?
  6. Deaths per Day ~ 3.100 additional COVID deaths a week

    ago https://orf.at/stories/3260942/
  7. Preparing the Data download.sh distribute.sh Transform, Merge, Optimize Static File

    Cluster 
 Apache
  8. 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, …
  9. Key Facts • Vue 2 with vuex + vue-router •

    67 Components • Graphing with Plotly JS / D3 • Maps with Lea fl et ORF.at Dashboard
  10. 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
  11. Dive into vue-corona!

  12. app.js widgets.js vue-corona

  13. 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!
  14. 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="" /> ✨
  15. 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
  16. 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>
  17. 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>
  18. 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); });
  19. 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`; });
  20. 4⃣ Chooser

  21. 4⃣ Embed in Stories {{{ <script 
 defer 
 src="//orf.at/corona/daten/js/widgets.js"

    
 data-area-key="9" 
 data-component="Incidence" ></script> }}}
  22. Your Custom 
 Dashboard

  23. ORF.at/corona

  24. ORF.at Geodaten https://youtu.be/tCKYzx4jaDU?t=6990

  25. dev.ORF.at

  26. Extra Slides

  27. 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 🗓
  28. 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.
  29. • Icons: fl aticon.com / Smashicons & Freepic