- Isha Berry, isha.berry@mail.utoronto.ca
- John Brownstein, John.Brownstein@childrens.harvard.edu
- Emily Cohn, Emily.Cohn@childrens.harvard.edu
- Lauren Goodwin, Lauren.Goodwin@childrens.harvard.edu
- Bernardo Gutierrez, bernardo.gutierrez@zoo.ox.ac.uk
- Sarah Hill, sarah.hill@zoo.ox.ac.uk
- Erin Hulland, ehulland@uw.edu
- Moritz Kraemer, moritz.kraemer@zoo.ox.ac.uk
- Anastasia Lambrou, anastasia.lambrou@jhu.edu
- Sabrina Li, sabrina.li@ouce.ox.ac.uk
- Alyssa Loskill, aloskill@bu.edu
- Sumiko Mekaru, srmekaru@gmail.com
- Julia Morgan, morgaj5@uw.edu
- Katelynn O’Brien, katelynn.obrien@childrens.harvard.edu
- David Pigott, pigottdm@uw.edu
- Oliver Pybus, oliver.pybus@zoo.ox.ac.uk
- Sam Scarpino, s.scarpino@northeastern.edu
- Kara Sewalk, Kara.Sewalk@childrens.harvard.edu
- Lin Wang, lin.wang@pasteur.fr
- Jessie Wu, chiehhsi.wu@gmail.com
- Bo Xu, xu-b15@mails.tsinghua.edu.cn
- Alex Zarebski, aezarebski@gmail.com




All data used to produce this map are exclusively collected from publicly available sources including government reports and news media
Novel Coronavirus (COVID-19)
- Number of cases:
50+
25-49
10-24
< 10
Last updated:
$(document).ready(function() { // json files - append date/time string to prevent caching var staticCasesJson = './totals.json?nocache=' + (new Date()).getTime(); var animatedCasesJson = './dailies.json?nocache=' + (new Date()).getTime(); var latestCountsJson = './latestCountsStatic.json?nocache=' + (new Date()).getTime();
var transitionEnd = 'webkitTransitionEnd otransitionend oTransitionEnd msTransitionEnd transitionend'; var showModal = function() { // switch elements to have 'display' value (block, flex) but keep hidden via opacity $('.modal-wrapper').addClass('is-block').outerWidth(); $('.modal').addClass('is-flex').outerWidth(); // add css class for transitioning via opacity $('.modal-wrapper').addClass('is-visible'); $('.modal').addClass('is-visible'); } var closeModal = function() { $('.modal-wrapper, .modal') .removeClass('is-visible') .one(transitionEnd, function() { $('.modal-wrapper').removeClass('is-block'); $('.modal').removeClass('is-flex'); }); }
$('#contributors').click(function() { showModal(); }) $('.modal-backdrop, .modal-cancel').click(function() { closeModal(); })
// begin animation of markers $('#spread').click(function() { clearMarkers(markers); clearMarkers(animatedMarkers); startAnimation(); })
// clear markers from map function clearMarkers(markersArray) { for (var i = 0; i < markersArray.length; i++) { markersArray[i].remove(); } markersArray = [] } // set marker styles function setMarkerStyle(caseCount) { if (caseCount < 10) { pinClass = 'pin4'; } else if (caseCount>= 10 && caseCount <= 24) { pinClass = 'pin3'; } else if (caseCount >= 25 && caseCount <= 49) { pinClass = 'pin2'; } else if (caseCount >= 50) { pinClass = 'pin1'; } return pinClass }
// format date as yyyy-mm-dd function parseDate(str) { var mdy = str.split('.'); var formattedDate = new Date(mdy[2], mdy[1], mdy[0]); return formattedDate.getFullYear() + '-' + ('0' + formattedDate.getMonth()).slice(-2) + '-' + ('0' + formattedDate.getDate()).slice(-2); }
// pase date for comparison function parseDateForCompare(str) { var mdy = str.split('-'); return new Date(mdy[0], mdy[1]-1, mdy[2]); }
var today = new Date(); var formattedToday = today.getFullYear() + '-' + ('0' + (today.getMonth() + 1)).slice(-2) + '-' + ('0' + today.getDate()).slice(-2);
// get latest case counts from scraped data $.getJSON(latestCountsJson, function(json) { var counts = json[0].caseCount; var formattedDate = json[0].date; $('#total-cases').html(counts); $('#last-updated-date').html(formattedDate); });
// establish map mapboxgl.accessToken = 'pk.eyJ1Ijoia2F0ZWx5bm5vYnJpZW4iLCJhIjoiY2sxa3ZjbTR4MDA2bTNjcGR4cXIwZnJidSJ9.wrkfV082TaX5P65i2L8upg'; var map = new mapboxgl.Map({ container: 'map', // HTML container id style: 'mapbox://styles/katelynnobrien/ck5y23wmc0aso1iqtbww5nzrr', center: [10, 0], // starting position as [lng, lat] zoom: 1 }); // Add zoom and rotation controls to the map. map.addControl(new mapboxgl.NavigationControl());
// Set arrays to store markers var markers = []; var animatedMarkers = [];
// create marker and put on map function createMarker(cssClass, lon, lat, markerArray, popup) { var customMarker = document.createElement('div'); customMarker.className = cssClass var marker = new mapboxgl.Marker(customMarker) if (popup) { marker.setPopup(popup) } marker .setLngLat([lon, lat]) .addTo(map) markerArray.push(marker); }
// animate spread of disease function startAnimation() { $.getJSON(animatedCasesJson, function(json) { var numDates = json.length var count = 0; map.flyTo({ zoom: 3, center: [102,24] }); function animateMarkers() { var dateDetails = json[count] var date = Object.keys(dateDetails); var compareDate = parseDateForCompare(date[0]); if ((today.getTime() - compareDate.getTime()) < 0) { setTimeout(function() { staticMarkers(); // finish animation on static pins with popups }, 600); } else { var locations = dateDetails[date] if (date[0] === '2020-01-20') { map.flyTo({ zoom: 1, center: [10,0] }); } setTimeout(function() { $('#spread-date').show().html(date[0]); if (count === 0) { createMarker('pin1', 114.2797, 30.596415, animatedMarkers); } locations.map(function(location, i) { var caseCount = location.caseCount var pinClass = location.pin.split('.'); if (location.longitude !== '' && location.latitude !== '') { createMarker(pinClass[0],location.longitude, location.latitude, animatedMarkers); } }) count++; animateMarkers(); }, 600); } } // add initial marker to Wuhan to fix discrepencies with missing date_confirmation createMarker('pin2', 114.2797, 30.596415, animatedMarkers); animateMarkers() }) } function staticMarkers() { $('#spread-date').hide().html(); $.getJSON(staticCasesJson, function(json) { // the 2 datasets that drive the rest // var outsideHubeiCases = json.outside_Hubei // var hubeiCases = json.Hubei // var totalCases = json.totalCases // placeMarkers(outsideHubeiCases) // placeMarkers(hubeiCases) var allCases = json.data placeMarkers(allCases) function placeMarkers(locationsData) { $.each(locationsData, function(k, v) { var uniqueLat = !Number.isNaN(v.latitude) ? v.latitude : '' var uniqueLon = !Number.isNaN(v.longitude) ? v.longitude : '' var caseCount = v.cases var uniqueCity = v.city var uniqueProvince = v.province var uniqueCountry = v.country var pinClass = '' if ((uniqueLat !== '' && uniqueLat !== '#REF!') && (uniqueLon !== '' && uniqueLon !== '#REF!')) { pinClass = setMarkerStyle(caseCount); var city = v.city ? v.city+', ' : ''; var province = v.province ? v.province+', ' : ''; var country = v.country ? v.country : ''; var age = v.age ? v.age : ''; var sex = v.sex ? v.sex : ''; var symptoms = v.symptoms ? v.symptoms : ''; var dateConfirmation = v.date_confirmation ? v.date_confirmation : ''; var printDate = ''; var source = v.source ? v.source : ''; if (caseCount === 1) { var age = (age !== undefined && age !== '') ? '
' : ''; var sex = (sex !== undefined && sex !== '') ? '
' : ''; var symptoms = (symptoms !== undefined && symptoms !== '') ? '
' : ''; if (dateConfirmation) { var formattedDate = parseDate(dateConfirmation) var printDate = '
'; } var source = source ? '
' : ''; } else { sex = ''; age = ''; symptoms = ''; source = ''; }
popupContent = '
' + city + province + country + '
' + '
' + sex + age + symptoms + printDate + source ;
var popup = new mapboxgl.Popup().setHTML(popupContent) createMarker(pinClass, uniqueLon, uniqueLat, markers, popup); }
}) clearMarkers(animatedMarkers); animatedMarkers = []; } }); }
// load initial markers staticMarkers();
})