Commit cb11ac19 authored by Albert Hornos Vidal's avatar Albert Hornos Vidal
Browse files

feat: change incoming risk flowmpaps widget for incidence map

parent c01b5b6e
......@@ -22,6 +22,9 @@ other = "Interactive resources"
[cv19_risk_maps]
other = "COVID-19 Risk maps"
[cv19_incidence_map]
other = "COVID-19 Incidence map"
[variant_distribution]
other = "SARS-CoV-2 Variant distribution"
......
......@@ -54,10 +54,13 @@
<h2>{{ i18n "interactive_resources" }}</h2>
<div class="card-deck">
<div class="card dp-card">
<h5 class="card-header">{{ i18n "cv19_risk_maps" }}</h5>
<h5 class="card-header">{{ i18n "cv19_incidence_map" }}</h5>
<div class="card-body">
<p class="card-text"><a href="https://flowmaps.life.bsc.es">Flow-Maps</a> is a system for monitoring COVID-19 outbreaks and mobility-associated risk by integrating health information, population-level mobility patterns into a Geographical Information System.</p>
<div id="risk-maps"></div>
<div class="map_container">
<div id="map_incidence"></div>
<div class="hoverinfo" id="hoverinfo"></div>
</div>
<p class="text-muted">{{ i18n "developed_by" }} <a target="_blank" href="http://life.bsc.es/compbio">Computational Biology group</a> and <a target="_blank" href="https://www.bsc.es/discover-bsc/organisation/scientific-structure/national-institute-bioinformatics-elixir-node-0">INB coordination team</a>, BSC</p>
</div>
<div class="card-footer">
......@@ -82,5 +85,5 @@
<a href="/resources" class="see-all-link arrow">{{ i18n "see_all_resources" }}</a>
</section>
</main>
<script>createRiskMaps("risk-maps");</script>
<script>createRiskMaps("map_incidence", 400);</script>
{{ end }}
......@@ -31,7 +31,7 @@
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.0/dist/css/bootstrap.min.css" integrity="sha384-B0vP5xmATw1+K9KRQjQERJvTumQW0nPEzvF6L/Z6nronJ3oUOFUFpCjEUQouq2+l" crossorigin="anonymous">
<!-- Bootstrap Icons -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.4.0/font/bootstrap-icons.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.4.0/font/bootstrap-icons.css">
<!-- JQuery, needed for Bootstrap and DataTables -->
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
......@@ -46,6 +46,6 @@
<!-- FlowMaps CSS and JS (JavaScript is needed before rendering the widget in homepage) -->
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
<script src="/third_party/flowmaps-maps/flowmaps-maps.js"></script>
<link rel="stylesheet" href="/third_party/flowmaps-maps/flowmaps-maps.css">
<script src="/third_party/widget-incidencia-bsc/flowmaps-maps.js"></script>
<link rel="stylesheet" href="/third_party/widget-incidencia-bsc/flowmaps-maps.css">
</head>
# Default ignored files
/shelf/
/workspace.xml
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.6" project-jdk-type="Python SDK" />
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/widget-incidencia-bsc.iml" filepath="$PROJECT_DIR$/.idea/widget-incidencia-bsc.iml" />
</modules>
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
\ No newline at end of file
<!-- ABOUT THE PROJECT -->
## Code structure
The package is formed for the next files:
- **cnig_provinces.geojson**: Store in a geojson the information about the province shapes for Spain
- **flowmaps-maps.js**: It contains the javascript functions for building the map. Firstly, makes a request to flowmaps API, retrieve the data, and build the map.
- **flowmaps-maps.css**: Contains basic styles for the maps and the *on hover* menu.
- **flowmaps-maps.html**: Contains an example on how this package can be used.
## Usage notes
After the package folder in your project, import the javascript and the css file into your template you want to use the maps on.
```
<script type="text/javascript" src="flowmaps-maps.js"></script>
<link rel="stylesheet" href="flowmaps-maps.css">
```
Once the files are imported, you can use the main function in order to generate the maps. The function is `createRiskMaps`, and has one parameter, the div where you want to build the map (string). For example, if you want to create the map widget on a div called "myDiv", you should, using javascript:
```
createRiskMaps("myDiv")
```
Check out the example `flowmaps-maps-html` for further details.
## Built With
The next libraries are used in order to build the maps:
* [Bootstrap](https://getbootstrap.com)
* [JQuery](https://jquery.com)
* [Plotly](https://plotly.com/javascript/)
.hoverinfo {
position: absolute;
top: 0px;
left: 0px;
border: 2px solid black;
background-color: white;
margin: 10px;
padding: 10px;
width: 315px;
font-size: 13px;
visibility: hidden;
}
#container {
position: absolute;
top: 200px;
left: 500px;
height: 550px;
width:1000px;
background-color: red;
}
\ No newline at end of file
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<!-- LIBRARIES -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
<!-- CUSTOM -->
<script type="text/javascript" src="flowmaps-maps.js"></script>
<link rel="stylesheet" href="flowmaps-maps.css">
</head>
<body>
<div class="map_container">
<div id="map_incidence"></div>
<div class="hoverinfo" id="hoverinfo"></div>
</div>
</body>
</html>
var selected_province = null;
var apiURL = 'https://flowmaps.life.bsc.es/api'
var colorIncidence = '#000059' // #050559 #03032e #030333 #000059
var colorBase = '#e9e9e9'
var plotly_options = {
displaylogo: false,
displayModeBar: false,
modeBarButtonsToRemove: ['toggleHover', 'hoverClosestGeo', 'pan2d', 'select2d', 'lasso2d'],
responsive: true,
}
var date_str = null;
function set_risk_dates() {
// Set the date_str to the max date of flowmaps has data from the sources
return fetch(apiURL + '/incidence_dates')
.then(response => response.json())
.then(function(data) {
date_str = data['max_date']
});
}
async function createRiskMaps(div, height) {
// Get the data from the API REST, and then, draw the maps. It's needed because the scale of the colorbar
// has to be common.
await set_risk_dates()
let filters = {
"ev": "ES.covid_cpro",
"start_date": date_str,
"end_date": date_str,
}
let filters2 = {
"type": "population",
"date": '2020-03-01',
"layer": "cnig_provincias",
}
Promise.all([
fetch('cnig_provincias.geojson'),
fetch(apiURL+'/incidence/?where='+JSON.stringify(filters)),
fetch(apiURL+'/layers.data.consolidated/?where='+JSON.stringify(filters2)+'&max_results=500'),
]).then(function (responses) {
return Promise.all(responses.map(function (response) {
return response.json();
}));
}).then(function (data) {
var provinciasGeojson = data[0];
// covidData: {1: {"active_cases_14": 3, ...}, 2: {...}, 3: {...}}
var covidData = {};
data[1]._items[0].data.forEach(d => {
covidData[d.id] = d;
});
// popData: {1: 300, 2: 450, 3: 403, ...}
var popData = {};
data[2]._items.forEach(d => {
popData[d.id] = d;
})
var provincesNames = {};
provinciasGeojson.features.forEach(feat => provincesNames[feat.id] = feat.name);
var locations = Object.keys(covidData);
var layout = {
mapbox: {
center: {
lon: -4, lat: 40.095178477814555
},
zoom: 4.5,
style: 'carto-positron',
},
height: height,
margin: {"r":0,"t":0,"l":0,"b":0},
};
var data = [create_data_for_map(locations, covidData, popData, provincesNames, provinciasGeojson)];
Plotly.newPlot(div, data, layout, plotly_options).then(gd => {
gd.on('plotly_click', (e) => {
selected_province = e.points[0].location;
// Border of the province selected bigger
$('#' + div)[0].layout.mapbox.layers = [
{
"color": "black",
"line": {"width": 2.5},
"opacity": 1,
"sourcetype": "geojson",
"type": "line",
"source": {
"type": "FeatureCollection",
"features": [provinciasGeojson.features.find(y=>y.id == selected_province)]
}
}
];
Plotly.redraw(div)
// Open abs tab
has_to_recreate_local_map = true;
$("#abs-tab").click();
});
gd.on('plotly_hover', (e) => {
var customdata = e.points[0].customdata
var z = e.points[0].z
var text = `Date: ${date_str}<br>` +
`Province: ${customdata[0]}<br>` +
`Population: ${customdata[1]}<br>` +
`Daily incidence:<br>` +
` - Daily incidence: ${customdata[2]}<br>` +
` - Daily incidence per 100k population: ${customdata[3]}<br>` +
`Accumulated incidence:<br>` +
` - Incidence (7 days): ${customdata[4]}<br>` +
` - Incidence (14 days): ${customdata[5]}<br>` +
` - Incidence (14 days) per 100k population: ${customdata[6]}<br>`
document.getElementById('hoverinfo').innerHTML = text;
document.getElementById('hoverinfo').style.visibility = 'visible';
})
gd.on('plotly_unhover', (e) => {
document.getElementById('hoverinfo').style.visibility = 'hidden';
});
})
});
}
function round(num, decimals=0){
var k = Math.pow(10, decimals)
return Math.round((num + Number.EPSILON) * k) / k
}
function splitTextInLines(text, wordsPerLine=5, lineSep='\n') {
return [...Array(parseInt(text.split(' ').length/wordsPerLine + 1)).keys()].map(x=>text.split(' ').slice(x*wordsPerLine,(x+1)*wordsPerLine)).map(x=>x.join(' ')).join(lineSep)
}
function buildCustomData(id, covidData, provincesNames, popData) {
return [
provincesNames[id],
round(popData[id].population),
covidData[id].new_cases,
round(100000*covidData[id].new_cases/popData[id].population, 2),
covidData[id].active_cases_7,
covidData[id].active_cases_14,
round(100000*covidData[id].active_cases_14/popData[id].population, 2)
]
}
function create_data_for_map(locations, covidData, popData, provincesNames, provinciasGeojson) {
return {
type: "choroplethmapbox",
locations: locations,
z: locations.map(x=>round(100000*covidData[x].active_cases_14/popData[x].population, 2)),
zmin: 0,
zmax: 600,
customdata: locations.map(x=>buildCustomData(x, covidData, provincesNames, popData)),
geojson: provinciasGeojson,
hovertemplate: "%{customdata[0]}<extra></extra>",
marker: {
opacity: 0.6,
},
colorbar: {
title: {
text: "Incidence (14 days) per 100k population",
side: "right"
},
},
colorscale: [['0.0', colorBase], ['1.0', colorIncidence]], // "Reds"
};
}
// INITIALIZATION OF THE MAP
// The width of the map is automatically responsive to the container div
// The height has to be specified here below, as Plotly doesn't support dynamic height.
createRiskMaps('map_incidence', 400);
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment