Commit 6b290345 authored by Sven Lautenbach's avatar Sven Lautenbach
Browse files

Merge branch 'feature-types-in-separated-layers' into 'master'

Feature toggle visible categories

See merge request !16
parents 3e903272 8866735f
......@@ -1011,3 +1011,12 @@ header {
max-height: 25px !important;
max-width: 65px !important;
}
.studyLegend {
cursor: pointer;
}
.studyLegend i {
color: black;
font-style: normal;
padding-left: 4px;
}
......@@ -156,10 +156,10 @@ function makeCumPlot(geojsonData, cumPlotDiv) {
}
console.log(cumFreqATMP);
console.log(cumFreqVaccines);
console.log(cumFreqTradChin);
console.log(cumFreqImpHelpers);
// console.log(cumFreqATMP);
// console.log(cumFreqVaccines);
// console.log(cumFreqTradChin);
// console.log(cumFreqImpHelpers);
//----------------------------------------
......@@ -320,4 +320,4 @@ function makeCumPlot(geojsonData, cumPlotDiv) {
var maxDate = regDates[regDates.length - 1]
return maxDate
};
\ No newline at end of file
};
function loadAllMaps() {
mapDivs = ['map-study-categories']
for (i = 0; i < mapDivs.length; i++) {
initMap(mapDivs[i])
}
}
function initMap(mapDiv) {
// get current date and format to the right format
var currentDate = new Date().toISOString().split('T')[0];
var map = L.map(mapDiv, {
zoom: 2,
minZoom: 1,
maxZoom: 12,
center: [0.0, 0.0],
timeDimension: true,
timeDimensionOptions: {
timeInterval: "2020-01-20/".concat(currentDate),
period: "P1D"
},
timeDimensionControl: false,
timeDimensionControlOptions: {
position: "topright",
}
});
L.tileLayer( 'https://cartodb-basemaps-{s}.global.ssl.fastly.net/light_all/{z}/{x}/{y}.png', {
attribution: 'Map tiles by <a href="https://carto.com/">Carto</a>, under <a href="https://creativecommons.org/licenses/by/3.0/">CC BY 3.0.</a> Data by <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>, under ODbL.',
subdomains: ['a','b','c']
}).addTo( map );
// load geojson file
projectCentroidsUrl = 'assets/data/covid-19-trials.geojson';
setTimeout(function(){ map.invalidateSize()}, 400);
var plotDiv = mapDiv.replace('map', 'plot');
var plotCumDiv = mapDiv.replace('map', 'cumplot');
addGeojsonLayer(map, projectCentroidsUrl, mapDiv, plotDiv, plotCumDiv);
// add custom date format to the time dimension control
L.Control.TimeDimensionCustom = L.Control.TimeDimension.extend({
_getDisplayDateFormat: function(date){
return date.toISOString().split('T')[0];
}
});
var timeDimensionControl = new L.Control.TimeDimensionCustom({
position: "topright"
});
map.addControl(timeDimensionControl);
// add legend
if (mapDiv == "map-study-types") {
addLegend_studyType(map);
} else if (mapDiv == "map-study-categories") {
addLegend_studyCategory(map);
}
// add scale bar
var scale = L.control.scale(
{
'imperial': false,
'updateWhenIdle': true
}
);
scale.addTo(map);
// add HeiGIT logo
var logo = L.control({position: 'bottomleft'});
logo.onAdd = function(map){
var div = L.DomUtil.create('div', 'logoContainer');
div.innerHTML= "<img src='assets/img/logos/heigit_logo.png'/>";
return div;
}
logo.addTo(map);
}
function addLegend_studyType(map) {
legend = L.control({position: 'bottomleft'});
legend.onAdd = function (map) {
var div = L.DomUtil.create('div', 'info legend')
div.innerHTML += '<i style="background:orange"></i>Observational Study<br>'
div.innerHTML += '<i style="background:blue"></i>Interventional Study<br>'
div.innerHTML += '<i style="background:grey"></i>Other<br>'
return div;
};
legend.addTo(map);
}
function addLegend_studyCategory(map) {
legend = L.control({position: 'bottomleft'});
legend.onAdd = function (map) {
var div = L.DomUtil.create('div', 'info legend')
div.innerHTML += '<i style="background:#1B9E77"></i>Diagnosis<br>'
div.innerHTML += '<i style="background:#BF5B17"></i>Disease understanding<br>'
div.innerHTML += '<i style="background:#386CB0"></i>Therapeutic stategies<br>'
div.innerHTML += '<i style="background:#7FC97F"></i>Drugs<br>'
div.innerHTML += '<i style="background:#FDC086"></i>Advanced therapy medicinal products<br>'
div.innerHTML += '<i style="background:#BEAED4"></i>Vaccines<br>'
div.innerHTML += '<i style="background:#F0027F"></i>Traditional Chinese medicine<br>'
div.innerHTML += '<i style="background:#FFFF99"></i>Impact on helpers<br>'
//div.innerHTML += '<i style="background:brown"></i>Epidemiological Study<br>'
div.innerHTML += '<i style="background:#666666"></i>Other<br>'
return div;
};
legend.addTo(map);
}
function set_style_studyType(feature) {
if (feature.properties.study_type == "Observational study") {
return {
fillColor: 'orange', color:'black', radius: 7,
fillOpacity: 0.5}
} else if (feature.properties.study_type == "Interventional study") {
return {
fillColor: 'blue',
radius: 7}
} else {
return {
fillColor: 'grey',
radius: 7}
}
}
function set_style_studyCategory(feature) {
varStrokeOpacity= .7
if (feature.properties.classification == "drugs") {
return {
fillColor: '#7FC97F', color:'grey', radius: 7, opacity: varStrokeOpacity,
fillOpacity: 0.5}
} else if (feature.properties.classification == "vaccine") {
return {
fillColor: '#BEAED4',
fillOpacity: 0.5, opacity: varStrokeOpacity,
radius: 7}
} else if (feature.properties.classification == "ATMP") {
return {
fillColor: '#FDC086', color:'grey',
fillOpacity: 0.5, opacity: varStrokeOpacity,
radius: 7}
} else if (feature.properties.classification == "impact on the helpers") {
return {
fillColor: '#FFFF99', color:'grey',
fillOpacity: 0.5, opacity: varStrokeOpacity,
radius: 7}
} else if (feature.properties.classification == "management") {
return {
fillColor: '#386CB0',
fillOpacity: 0.5, opacity: varStrokeOpacity,
radius: 7}
} else if (feature.properties.classification == "traditional chinese medicine") {
return {
fillColor: '#F0027F',
fillOpacity: 0.5, opacity: varStrokeOpacity,
radius: 7}
} else if (feature.properties.classification == "diagnosis") {
return {
fillColor: '#1B9E77',
fillOpacity: 0.5, opacity: varStrokeOpacity,
radius: 7}
} else if (feature.properties.classification == "understanding") {
return {
fillColor: '#BF5B17',
fillOpacity: 0.5, opacity: varStrokeOpacity,
radius: 7}
} else {
return {
fillColor: '#666666',
fillOpacity: 0.5, opacity: varStrokeOpacity,
radius: 7}
}
}
function addGeojsonLayer (map, url, mapDiv, plotDiv, cumPlotDiv) {
var geojsonData = $.ajax({
url:url,
dataType: "json",
success: console.log("clinical trials data successfully loaded."),
/*error: function (xhr) {
alert(xhr.statusText)
}*/
})
// Specify that this code should run once the county data request is complete
$.when(geojsonData).done(function() {
// define default point style
var geojsonMarker = {
radius: 6,
fillColor: "grey",
color: "white",
weight: 1,
opacity: 1,
fillOpacity: 0.8
};
// create geojson layer
if (mapDiv == "map-study-categories") {
// classification layer
layer = L.geoJSON(geojsonData.responseJSON, {
pointToLayer: function (feature, latlng) {
let marker = L.circleMarker(latlng, geojsonMarker);
marker.on("click", function(data) {
circleMarkerClick(data, map)
});
return marker;
},
style: set_style_studyCategory
})
} else if (mapDiv == "map-study-types") {
layer = L.geoJSON(geojsonData.responseJSON, {
pointToLayer: function (feature, latlng) {
let marker = L.circleMarker(latlng, geojsonMarker);
marker.on("click", function(data) {
circleMarkerClick(data, map)
});
return marker;
},
style: set_style_studyType
})
}
timeDimension_layer = L.timeDimension.layer.geoJson(layer)
timeDimension_layer.bindPopup(function (timeDimension_layer) {
// popup with a link to the project page with detailed information
popup = '<p>'+layer.feature.properties.name+'</p>'
return popup;
});
timeDimension_layer.addTo(map)
// fit to layer extent
map.fitBounds(layer.getBounds());
// add plot only for first map
makePlot(geojsonData.responseJSON, plotDiv)
maxDate = makeCumPlot(geojsonData.responseJSON, cumPlotDiv)
console.log(maxDate)
// populate table
var tableDivID = 'clinicalTrialsTable'
populateDataTable(geojsonData.responseJSON, tableDivID)
// add timestamp of date to text
document.getElementById("last_update").innerHTML = maxDate
document.getElementById("trials_count").innerHTML = geojsonData.responseJSON.features.length
})
}
function circleMarkerClick (event, map) {
let properties = event.target.feature.properties
let geometry = event.target.feature.geometry
var popup = new L.Popup();
var center = {lat: geometry.coordinates[1], lng: geometry.coordinates[0]}
var popupContent = `
Name: <b><a target="_blank" href=${properties.weburl}>${properties.name}</a></b></br>
Description: <b>${properties.description}</b></br>
Study type: <b>${properties.study_type}</b></br>
Classification: <b>${properties.classification}</b></br>
Registration Date: <b>${properties.time}</b></br>
Date Enrollment: <b>${properties.dateEnrollment}</b></br>
Contact: <b>${properties.contactEmail}</b>`;
popup.setLatLng(center);
popup.setContent(popupContent);
map.openPopup(popup);
}
function populateDataTable(geojsonData, tableDivID) {
var tableRef = document.getElementById(tableDivID).getElementsByTagName('tbody')[0];
geojsonData.features.forEach(function(element) {
tr = tableRef.insertRow();
td = document.createElement('td')
td.innerHTML = '<p><a target="_blank" href="'+element.properties.weburl+'">'+element.properties.name+'</></p>'
tr.appendChild(td)
td = document.createElement('td')
td.innerHTML = '<p>'+element.properties.description+'</p>'
tr.appendChild(td)
td = document.createElement('td')
td.innerHTML = '<p>'+element.properties.study_type+'</p>'
tr.appendChild(td)
td = document.createElement('td')
td.innerHTML = '<p>'+element.properties.classification+'</p>'
tr.appendChild(td)
td = document.createElement('td')
td.innerHTML = '<p>'+element.properties.time+'</p>'
tr.appendChild(td)
td = document.createElement('td')
td.innerHTML = '<p>'+element.properties.dateEnrollment+'</p>'
tr.appendChild(td)
td = document.createElement('td')
td.innerHTML = '<p>'+element.properties.primaryOutcome+'</p>'
tr.appendChild(td)
})
$('#'+tableDivID + ' tfoot th').each( function () {
var title = $(this).text();
$(this).html( '<input type="text" placeholder="Search '+title+'" />' );
} );
var table = $('#'+tableDivID).DataTable({
"scrollX": true,
scroller: true
}
);
// Put top scroller
$('.dataTables_scrollHead').css({
'overflow-x':'scroll'
}).on('scroll', function(e){
var scrollBody = $(this).parent().find('.dataTables_scrollBody').get(0);
scrollBody.scrollLeft = this.scrollLeft;
$(scrollBody).trigger('scroll');
});
$('.dataTables_length').addClass('bs-select');
// Apply the search
table.columns().every( function () {
var that = this;
$( 'input', this.footer() ).on( 'keyup change clear', function () {
console.log("event")
if ( that.search() !== this.value ) {
that
.search( this.value )
.draw();
}
} );
});
};
L.TimeDimension.Layer.GeoJsonByType = L.TimeDimension.Layer.GeoJson.extend({
initialize: function (geoJsonLayer, options) {
this.geoJsonLayer = geoJsonLayer
this.disabledTaxonomies = []
L.TimeDimension.Layer.GeoJson.prototype.initialize.call(this, geoJsonLayer, options);
},
/**
* Overwrites the _update method to implement the
* clustering feature to the map items vizualization
*/
_update: function () {
if (!this._map)
return;
if (!this._loaded) {
return;
}
var maxTime = this._timeDimension.getCurrentTime(), minTime = 0;
if (this._duration) {
var date = new Date(maxTime);
L.TimeDimension.Util.subtractTimeDuration(date, this._duration, true);
minTime = date.getTime();
}
// new coordinates:
var layer = L.geoJson(null, this._baseLayer.options);
var layers = this._baseLayer.getLayers();
for (var i = 0, l = layers.length; i < l; i++) {
var feature = this._getFeatureBetweenDates(layers[i].feature, minTime, maxTime);
if (feature && this.disabledTaxonomies.indexOf(feature.properties.classification) === -1) {
layer.addData(feature);
if (this._addlastPoint && feature.geometry.type == "LineString") {
if (feature.geometry.coordinates.length > 0) {
var properties = feature.properties;
properties.last = true;
let coordinates = feature.geometry.coordinates[feature.geometry.coordinates.length - 1]
layer.addData({ type: 'Feature', properties: properties, geometry: {type: 'Point', coordinates: coordinates }});
}
}
}
}
if (this._currentLayer) {
this._map.removeLayer(this._currentLayer);
}
if (layer.getLayers().length) {
layer.addTo(this._map);
this._currentLayer = layer;
}
},
});
L.timeDimension.layer.geoJsonByType = function (geoJsonLayer, options) {
return new L.TimeDimension.Layer.GeoJsonByType(geoJsonLayer, options);
};
/**
* TrialsMap class
* Creates a leaft let map instance, makePlot and populate table
*/
class TrialsMap {
/**
* Constructor of TrialsMap that set the class properties,
* set the taxonomies colors and init that leaflet map
* @param {*} mapEl
* @param {*} trialsEl
* @param {*} options
*/
constructor(mapEl, trialsEl, options= {}) {
this.trialsEl = trialsEl
this.mapEl = mapEl
this.loadByTaxonomy = options.loadByTaxonomy || 'categories' // can be category or type
this.lastUpdateEl = options.lastUpdateEl || 'last_update'
this.trialsCountEl = options.trialsCountEl || 'trials_count'
this.plotEl = options.plotEl || this.mapEl.replace('map', 'plot')
this.cumplotEl = options.cumplotEl || this.mapEl.replace('map', 'cumplot')
this.projectCentroidsUrl = 'assets/data/covid-19-trials.geojson'
this.logoSrc = 'assets/img/logos/heigit_logo.png'
this.tileServiceUrl = 'https://cartodb-basemaps-{s}.global.ssl.fastly.net/light_all/{z}/{x}/{y}.png'
this.mapAttribution = 'Map tiles by <a href="https://carto.com/">Carto</a>, under <a href="https://creativecommons.org/licenses/by/3.0/">CC BY 3.0.</a> Data by <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>, under ODbL.'
this.studyCategoryColors = []
this.studyTypeColors = []
// Set study taxonomies (type and categories) colors
this.setTaxonomiesColors()
this.initMap()
}
/**
* Init map component using leaflet and timedimension
* and set the map overlays
*/
initMap = () => {
let mapOptions = this.buildMapOptions()
this.map = L.map(this.mapEl, mapOptions)
L.tileLayer(this.tileServiceUrl, { attribution: this.mapAttribution, subdomains: ['a', 'b', 'c'] }).addTo(this.map)
let that = this
// load geojson file
setTimeout(function () { that.map.invalidateSize() }, 400)
this.addGeojsonLayer()
// add custom date format to the time dimension control
L.Control.TimeDimensionCustom = L.Control.TimeDimension.extend({
_getDisplayDateFormat: function (date) {
return date.toISOString().split('T')[0]
}
});
var timeDimensionControl = new L.Control.TimeDimensionCustom({ position: "topright" })
this.map.addControl(timeDimensionControl)
this.setMapOverlayers()
}
/**
* Build map options object
* @returns {*} options
*/
buildMapOptions = () => {
var currentDate = new Date().toISOString().split('T')[0]
let mapOptions = {
zoom: 2,
minZoom: 1,
maxZoom: 12,
center: [0.0, 0.0],
timeDimension: true,
timeDimensionOptions: {
timeInterval: "2020-01-20/".concat(currentDate),
period: "P1D" },
timeDimensionControl: false,
timeDimensionControlOptions: { position: "topright"}
}
return mapOptions
}
/**
* Set the type and categories study colors
* by creating the studyCategoryColors and
* studyTypeColors props
*/
setTaxonomiesColors = () => {
this.studyCategoryColors = {
drugs : '#7FC97F',
vaccine: '#BEAED4',
ATMP: '#FDC086',
'impact on the helpers': '#FFFF99',
management: '#386CB0',
'traditional chinese medicine': '#F0027F',
diagnosis: '#1B9E77',
understanding: '#BF5B17',
other: '#666666'
}
this.studyTypeColors = {
'Observational study': 'orange',
'Interventional study': 'blue',
}
}
/**
* Set map layers (legends, scale and logo)
* if the layers already exist, remove them
* and add again, refreshing the data
*/
setMapOverlayers = () => {
// add legend
if (this.loadByTaxonomy=== "types") {
this.addLegendStudyType();
} else if (this.loadByTaxonomy== "categories") {
this.addLegendStudyCategory();
}
if (this.scale) {
this.map.removeControl(this.scale)
}
// add scale bar
this.scale = L.control.scale({'imperial': false, 'updateWhenIdle': true });
this.scale.addTo(this.map)
if (this.logo) {
this.map.removeControl(this.logo)
}
// add HeiGIT logo
this.logo = L.control({ position: 'bottomleft' })
let that = this
this.logo.onAdd = function () {
var logoContainer = L.DomUtil.create('div', 'logoContainer')
logoContainer.innerHTML = `<img src='${that.logoSrc}'/>`
return logoContainer
}
this.logo.addTo(this.map)
}
/**
* Add the study type legend overlayer
*/
addLegendStudyType = () => {
// If it was already loaded before, remove before loading again
if (this.legendByType) {
this.map.removeControl(legendByType)
}
this.legendByType = L.control({ position: 'bottomleft' })
let that = this
this.legendByType.onAdd = function () {