325 lines
13 KiB
HTML
325 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] = {
|
|
cgrow: stats.cgrow,
|
|
dgrow: stats.dgrow,
|
|
cases: 0,
|
|
deaths: 0,
|
|
};
|
|
} 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>
|