Introduction
Cette visualisation permet d'explorer les différents types de voies de la ville de Paris. Au total, elle possède plus de 40 types de voies et 6513 polygones les représentants.
L'ensemble est parfaitement fluide malgré la manipulation de 3500 éléments (pour les rues) en même temps. C'est aussi une occasion pour nous de mettre à jour l'intégration de D3JS
sur une carte Leaflet avec les dernières versions de ces librairies. Les données proviennent du site Open Data Paris
et se présentent sous la forme d'un fichier geoJSON. Sur la carte vous pouvez utiliser les fonctionnalités de zoom, passer votre souris sur toutes les voies de Paris pour connaitre leur nom et faire apparaitre toutes les voies d'un même type
grâce au panneau de commande situé à droite.
Initialisation
Nous avons choisi un fond de carte neutre et sans libellé pour donner toute sa place aux interactions. Avec la version 1.7 de Leaflet l'ajout de l'overlay pour D3JS n'a pas changé, il faut récupérer le div
prévu à cet
effet par Leaflet. On initialise une fonction de transformation qui permet de passer des latitudes/longitudes de notre fichier geoJSON à un emplacement X,Y en pixel correspondant avec Leaflet. Les propriétés passées à la fonction
addCommandControl
sont visibles dans le code source et contiennent la liste des types de voies.
var CartoDB_DarkMatterNoLabels = L.tileLayer('https://{s}.basemaps.cartocdn.com/dark_nolabels/{z}/{x}/{y}{r}.png', {
attribution: '© OpenStreetMap contributors © CARTO',
subdomains: 'abcd',
maxZoom: 20
});
let map = L.map('map').setView([48.85341, 2.3488], 13);
map.addLayer(CartoDB_DarkMatterNoLabels);
let svg = d3.select(map.getPanes().overlayPane).append("svg"),
voies = svg.append("g").attr("class", "leaflet-zoom-hide");
let transform = d3.geoTransform({point: projectPoint}),
path = d3.geoPath().projection(transform);
let tooltip = d3.select("body").append("div")
.attr("class", "map-tooltip")
.style("opacity", 0);
addCommandControl(map, props);
Fonction de projection projectPoint
Cette fonction demande à Leaflet de projeter les coordonnées de notre fichier geoJSON sur la carte.
function projectPoint(x, y) {
var point = map.latLngToLayerPoint(new L.LatLng(y, x));
this.stream.point(point.x, point.y);
}
Construction du panneau de commande
Le panneau est construit à partir des données de voies. Si nous utilisons au départ les fonctions de Leaflet pour construire le premier div
, le reste est géré avec l'innerHTML.
L'ajout des évènements mouseover
et mouseout
est fait avec D3JS mais pourrait être fait avec toute autre librairie JS. Nous utilisons le nom de la class
pour sélectionner
toutes les voies du même type. Ce nom est défini à la construction des polygones juste en dessous.
function addCommandControl(map, props) {
var command = L.control({position: 'topright'});
command.onAdd = function (map) {
var div = L.DomUtil.create('div', 'command');
let innerHTML = '<div class="title"><span>Les types de voies</br> à Paris</div>';
innerHTML += '<div class="subtitle"><span>(Sélectionner un type de voie)</div>';
innerHTML += '<ul class="legend">'
for (let [key, value] of props) {
innerHTML += '<li name="' + key + '">' + (value.label ? value.label : "Les portiques d'Orléans") + ' (' + value.count + ')</li>';
}
innerHTML += '</ul>';
div.innerHTML = innerHTML;
return div;
};
command.addTo(map);
Array.from(document.getElementsByTagName('li')).forEach(e => {
d3.select(e)
.on("mouseover", function(d) {
let className = e.getAttribute('name');
d3.selectAll('.' + className).style('opacity', 1);
})
.on("mouseout", function(d) {
let className = e.getAttribute('name');
d3.selectAll('.' + className).style('opacity', 0);
});
});
}
Ajout des voies sur la carte et gestion des interactions
Cette dernière partie du code contient des éléments essentiels au bon fonctionnement de l'interaction :
.style("pointer-events", "auto")
permet de rétablir les évènements de souris sur les polygones représentant les voies. Par défaut Leaflet les désactive
map.on("zoomend", reset);
indique sur quel évènement il faut recalculer le path
des polygones. Depuis la version 1.0 de Leaflet, il ne faut plus
utiliser viewreset
mais zoomend
sinon cela ne fonctionne pas. Pour la petite histoire, c'est lié à l'introduction de la fonctionnalité flyTo
d3.json('voie.geojson').then(function(geojson) {
var features = voies
.selectAll("path")
.data(geojson.features)
.enter()
.append("path")
.attr("class", d => d.properties.c_desi)
.style("pointer-events", "auto")
.style("fill", "none")
.style("stroke", d => props.get(d.properties.c_desi) ? props.get(d.properties.c_desi).color : "white")
.style("stroke-width", "2")
.style("opacity", 0)
.on("mouseover", function(event) {
d3.select(this).style('opacity', 1);
tooltip.html(d3.select(this).data()[0].properties.l_longmin)
.style('opacity', 1)
.style('color', props.get(d3.select(this).attr('class')) ? props.get(d3.select(this).attr('class')).color : "white")
.style('left', (event.clientX + 30) + 'px')
.style('top', (event.clientY - 30) + 'px')
})
.on("mouseout", function(event) {
d3.select(this).style('opacity', 0);
tooltip.style('opacity', 0)
});
map.on("zoomend", reset);
reset();
function reset() {
var bounds = path.bounds(geojson),
topLeft = bounds[0],
bottomRight = bounds[1];
svg.attr("width", bottomRight[0] - topLeft[0])
.attr("height", bottomRight[1] - topLeft[1])
.style("left", topLeft[0] + "px")
.style("top", topLeft[1] + "px");
voies.attr("transform", "translate(" + -topLeft[0] + "," + -topLeft[1] + ")");
features.attr("d", path);
}
});
Conclusion
Nous terminons ici notre tutoriel. N'hésitez pas à regarder le code source pour voir la partie CSS qui gère l'affichage du tooltip, le panneau de contrôle et sa scrollbar.
COMMENTAIRES