covid-disease-spread/index.html
2020-04-06 00:14:50 +02:00

320 lines
13 KiB
HTML

<!DOCTYPE html>
<html>
<head>
<title>Covid19 interactive map</title>
<meta charset="utf-8"/>
<!-- Social media -->
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="Covid-19 interactive map" />
<meta name="twitter:image" content="https://vane.github.io/covid-disease-spread/card.png" />
<meta name="twitter:creator" content="@szczepano">
<link rel="canonical" href="https://vane.github.io/covid-disease-spread/" />
<meta property="og:title" content="Covid19 interactive map" />
<meta property="og:description" content="Covid19 interactive map" />
<meta property="og:url" content="https://vane.github.io/covid-disease-spread/" />
<meta property="og:site_name" content="Covid19 interactive map" />
<meta property="og:image" content="https://vane.github.io/covid-disease-spread/card.png" />
<meta property="og:image:height" content="848" />
<meta property="og:image:width" content="2186" />
<meta property="og:type" content="article" />
<meta property="article:published_time" content="2020-04-05T22:59:14" />
<script type="application/ld+json">
{"mainEntityOfPage":{"@type":"WebPage","@id":"https://vane.github.io/covid-disease-spread/"},"url":"https://vane.github.io/covid-disease-spread/","image":{"width":2186,"height":848,"url":"https://vane.github.io/covid-disease-spread/card.png","@type":"imageObject"},"author":{"@type":"Person","name":"Michal Szczepanski"},"headline":"Covid 19 interactive map","dateModified":"2020-04-05T22:59:14","description":"Covid19 interactive map","datePublished":"2020-04-05T22:59:14","@type":"BlogPosting","@context":"https://schema.org"}</script>
<!-- end -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.1/leaflet.css"/>
<style type="text/css">
.leaflet-container {
background-color: #c5e8ff;
}
table {
border-collapse: collapse;
width: 100%;
}
th, td {
border-bottom: 1px solid #ddd;
text-align: center;
}
</style>
</head>
<body>
<div style="display:flex;">
<div>
<div id="map" style="width: 534px; height: 350px"></div>
<div>
<span><a target="_blank" href="https://www.ecdc.europa.eu/en/publications-data/download-todays-data-geographic-distribution-covid-19-cases-worldwide">Data source - ecdc.europa.eu</a></span>
<br/>
<span><a target="_blank" href="https://geojson-maps.ash.ms/">Map geojson source - geojson-maps.ash.ms</a></span>
<br />
<span><a target="_blank" href="https://github.com/vane/covid-disease-spread">Github repository</a></span>
</div>
</div>
<div style="padding-left:10px;">
<h1></h1>
<label>Autoplay <input id="autoplay_checkbox" type="checkbox" checked></label>
<br />
<label>Date <span id="date_range_current">2019-12-31</span>
<input id="date_range" style="width:400px;" type="range" step="1" min="0" max="0"></label>
<br />
<div id="coronacases" style="max-height:280px;overflow:auto;padding-top:10px;"></div>
</div>
</div>
<script src="https://code.jquery.com/jquery-1.10.2.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.1/leaflet.js"></script>
<script>
// helper
var gid = function(id) {
return document.getElementById(id);
}
// maps
var mapPath = 'data/110m.geo.json'
var mapStyle = {
stroke: true,
weight: 1,
fill: true,
fillColor: '#fff',
fillOpacity: 1
}
var countryLayer = {};
// gradient
var colors = {
start: [255, 255, 255],
end: [255, 0, 0],
gradients: [],
gradientsCol: [],
range: 50,
autoplaySpeed: 250,
}
// https://stackoverflow.com/questions/24016456/how-to-programmatically-create-n-sequential-equidistant-colors-ranging-from-dark
var linearGradient = function (n) {
var gradients = [];
var gradientsCol = [];
var dRGB = [];
for (var i = 0;i<colors.start.length;i++) {
var color1 = colors.start[i];
var color2 = colors.end[i];
var c = (color2 - color1)/((n/2) - 1);
dRGB.push(c);
}
for (var k = 0;k<n;k++) {
var col = [];
for(var j = 0;j<colors.start.length;j++) {
var dx = dRGB[j];
var color1 = colors.start[j];
var c = parseInt(color1+k*dx);
col.push(c);
}
gradientsCol.push('rgb('+col.join(',')+')');
gradients.push(col);
}
colors.gradients = gradients;
colors.gradientsCol = gradientsCol;
}
linearGradient(colors.range+1);
$.getJSON(mapPath, function (data) {
var map = L.map('map').setView([45, 0], 1.05)
L.geoJson(data, {
clickable: false,
style: mapStyle,
onEachFeature: function (feature, layer) {
// console.log(feature, feature.properties.iso_a2);
countryLayer[feature.properties.iso_a2] = layer;
layer.on('mouseover', function () {
layer.setStyle({
fillColor: '#0000ff'
})
})
layer.on('mouseout', function () {
layer.setStyle({
fillColor: '#fff'
})
})
},
}).addTo(map)
}).then(function () {
console.log('finish drawing countries');
$.get('data/download.csv').then(function (data) {
var today = new Date()
var covidData = {}
covidData.country = {};
covidData.end = {day: today.getDate(), month: today.getMonth(), year: today.getFullYear()}
covidData.start = {country: 'CN', day: 31, month: 12, year: 2019}
data.split('\n').forEach(function (row, i) {
if (i > 1) {
var a = row.split(',')
var date = a[0]
var day = a[1]
var month = a[2]
var year = a[3]
var cases = parseInt(a[4])
var deaths = parseInt(a[5])
var geoId = a[7]
var population = parseInt(a[9])
if (!covidData.country[geoId]) {
covidData.country[geoId] = {data: {}, population: population, ctotal: 0, dtotal: 0}
}
covidData.country[geoId].data[year+"-"+month+"-"+day] = {cases: cases, deaths: deaths, cgrow:0, dgrow:0}
}
});
return covidData;
}).then(function(covidData) {
// country disease calculate
var start = covidData.start;
// end date
var end = new Date(covidData.end.year, covidData.end.month, covidData.end.day + 1);
var countries = covidData.country;
var dateFormat, ccode, country, stats;
covidData.totalDays = 0;
covidData.maxCasesCountry = '';
covidData.maxCases = 0;
covidData.maxDeathsCountry = '';
covidData.maxDeaths = 0;
// let's fill gaps
var countryCache = {};
// https://stackoverflow.com/questions/4345045/javascript-loop-between-date-ranges
// iterate over dates
for(var s = new Date(start.year, start.month - 1, start.day);s < end;s.setDate(s.getDate() + 1)) {
dateFormat = s.getFullYear()+"-"+(s.getMonth() + 1)+"-"+s.getDate();
// console.log(dateFormat)
// iterate over countries
for(ccode in countries) {
country = countries[ccode];
// precalculate stats
if(country.data[dateFormat]) {
stats = country.data[dateFormat];
country.ctotal += stats.cases;
country.dtotal += stats.deaths;
if(country.dtotal > covidData.maxDeaths) {
covidData.maxDeaths = country.dtotal;
covidData.maxDeathsCountry = ccode;
}
if(country.ctotal > covidData.maxCases) {
covidData.maxCases = country.ctotal;
covidData.maxCasesCountry = ccode;
}
stats.cgrow = country.ctotal;
stats.dgrow = country.dtotal;
countryCache[ccode] = stats;
} else {
// let's fill gaps
country.data[dateFormat] = countryCache[ccode]
}
}
// let's have total days so we can use it in range control
covidData.totalDays += 1;
}
gid('date_range').max = covidData.totalDays - 1;
console.log('total days', covidData.totalDays);
//console.log(colors.steps);
return covidData;
}).then(function(covidData) {
// initialize controls
// console.log(covidData);
var calcTimeout = -1;
var autoPlayIntervalId = 0;
var coronacases = gid('coronacases');
var autoplay = function() {
var value = parseInt(gid('date_range').value);
if (value >= covidData.totalDays - 1) {
value = 0;
}
// console.log(value);
gid('date_range').value = value + 1;
colorCountries(value);
};
var colorCountries = function(value) {
var dt = new Date(covidData.start.year, covidData.start.month - 1, covidData.start.day);
dt.setDate(dt.getDate() + parseInt(value));
var dformat = dt.getFullYear()+"-"+(dt.getMonth()+1)+"-"+dt.getDate();
gid('date_range_current').innerText = dformat;
var colorStep = covidData.maxCases / colors.range;
var coronaCountries = [];
// console.log('color step', colorStep);
var layer, country, stats, colorIndex, props;
for(var ccode in countryLayer) {
layer = countryLayer[ccode];
country = covidData.country[ccode];
if(country) {
stats = country.data[dformat];
if(stats && stats.cgrow > 0) {
colorIndex = colors.gradientsCol[Math.ceil(stats.cgrow / colorStep)];
layer.setStyle({
fillColor: colorIndex,
});
props = layer.feature.properties;
// lets create list of countries here
coronaCountries.push({
'code': ccode,
'cases': stats.cgrow,
'name': props.name,
'deaths':stats.dgrow,
'dcases':stats.cases,
'ddeaths':stats.deaths,
});
// console.log(ccode, stats, colorIndex);
} else {
layer.setStyle({
fillColor: mapStyle.fillColor,
});
}
//console.log(ccode, Math.floor(country.ctotal / colorStep));
} else {
layer.setStyle({
fillColor: mapStyle.fillColor,
});
}
}
// now sort the countries and build the list control
coronaCountries.sort(function(a, b) {
if(a.cases > b.cases) {
return -1;
} else if (a.cases < b.cases) {
return 1;
}
return 0;
});
var coronaText = '<table><thead><th>Country</th><th>Total Cases</th><th>Total Deaths</th>';
coronaText += '<th>Day Cases</th><th>Day Deaths</th></thead><tbody>';
coronaCountries.forEach(function(c) {
coronaText += '<tr><td><b>'+c.name+'</b></td><td>'+c.cases+'</td>'+'<td>'+c.deaths+'</td>'+'<td>'+c.dcases+'</td>'+'<td>'+c.ddeaths+'</td>'+'</tr>';
})
coronaText += '</tbody></table>';
coronacases.innerHTML = coronaText;
};
var rangeValueChange = function(e) {
clearTimeout(calcTimeout);
clearInterval(autoPlayIntervalId);
gid('autoplay_checkbox').checked = false;
var value = e.target.value;
calcTimeout = setTimeout(function() {
colorCountries(value);
}, 250);
};
var autoplayChange = function() {
var checked = gid('autoplay_checkbox').checked;
if(!checked) {
clearInterval(autoPlayIntervalId);
} else {
autoPlayIntervalId = setInterval(autoplay, colors.autoplaySpeed);
}
// console.log(checked);
}
if(gid('autoplay_checkbox').checked) {
autoPlayIntervalId = setInterval(autoplay, colors.autoplaySpeed);
}
// console.log(gid('date_range').max, gid('date_range').min, gid('date_range').value);
gid('autoplay_checkbox').addEventListener('change', autoplayChange);
gid('date_range').addEventListener('change', rangeValueChange);
gid('date_range').addEventListener('input', rangeValueChange);
return covidData;
});
})
</script>
</body>
</html>