Skip to content

Instantly share code, notes, and snippets.

@ryanbaumann
Last active July 11, 2022 21:24
Show Gist options
  • Save ryanbaumann/733ba99c5ca1d9d15259081b395e4b00 to your computer and use it in GitHub Desktop.
Save ryanbaumann/733ba99c5ca1d9d15259081b395e4b00 to your computer and use it in GitHub Desktop.
GL JS Feature States with Data Joins
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8' />
<title>Display a map</title>
<meta name='viewport' content='initial-scale=1,maximum-scale=1,user-scalable=no' />
<script src='https://dl.dropbox.com/s/hzalyoaqrmuditi/mapbox-gl.js'></script>
<link href='https://api.tiles.mapbox.com/mapbox-gl-js/v0.45.0/mapbox-gl.css' rel='stylesheet' />
<script src='https://cdnjs.cloudflare.com/ajax/libs/chroma-js/1.3.7/chroma.min.js'></script>
<style>
body {
margin: 0;
padding: 0;
}
#map {
position: absolute;
top: 0;
bottom: 0;
width: 100%;
}
</style>
</head>
<body>
<div id='map'>
</div>
<script>
mapboxgl.accessToken = 'pk.eyJ1IjoicnNiYXVtYW5uIiwiYSI6IjdiOWEzZGIyMGNkOGY3NWQ4ZTBhN2Y5ZGU2Mzg2NDY2In0.jycgv7qwF8MMIWt4cT0RaQ';
var map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/light-v9?optimize=true',
center: [-99.9, 41.5],
zoom: 4
});
var maxValue = 0;
var newdata = new Map();
var breaks = [];
var scale = [];
var getJSON = function(url, callback) {
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.responseType = 'json';
xhr.onload = function() {
var status = xhr.status;
if (status === 200) {
callback(null, xhr.response);
} else {
callback(status, xhr.response);
}
};
xhr.send();
};
map.on('style.load', function() {
var domain = [];
getJSON('https://dl.dropbox.com/s/0vtzw9aw089obxi/population.json',
function(err, data) {
if (err !== null) {
console.log('error fetching file')
} else {
data.forEach(function(row) {
if (!row["Zip Code ZCTA"]) {
return
}
// Normalized population density
let area = row['area'] > 0 ? row['area'] : 1;
maxValue = row["2010 Census Population"] <= maxValue ? maxValue : row["2010 Census Population"];
domain.push(row["2010 Census Population"])
newdata[row["Zip Code ZCTA"]] = {
population: row["2010 Census Population"],
area: row['area'],
ratio: row["2010 Census Population"] / (area / 5280)
}
})
breaks = chroma.limits(domain, 'q', 6);
scale = chroma.scale('OrRd').colors(7);
}
});
initLayers();
});
function initLayers() {
// Add source for state polygons hosted on Mapbox, based on US Census Data:
// https://www.census.gov/geo/maps-data/data/cbf/cbf_state.html
map.addSource("postcodes", {
type: "vector",
url: "mapbox://mapbox.pbi-us-postcodes-v1",
});
map.addLayer({
"id": "postcodes",
"type": "fill",
"source": "postcodes",
"source-layer": "pbi-us-postcodes",
"paint": {
"fill-color": 'rgba(0,0,0,0)',
"fill-opacity": ["case", ['==', ["feature-state", 'hover'], 1], 1, 0.8],
"fill-outline-color": "rgba(255,255,255,0.1)"
}
}, 'waterway-label');
map.addLayer({
"id": "postcodes-line",
"type": "line",
"source": "postcodes",
"source-layer": "pbi-us-postcodes",
"layout": {
'line-join': 'round',
'line-cap': 'round'
},
"paint": {
"line-color": ['case', ['==', ["feature-state", 'hover'], 1], 'black', 'rgba(0,0,0,0)'],
"line-width": {
"stops": [
[0, 1],
[18, 4]
]
}
}
}, 'waterway-label');
function createColorExpression(stops, colors, value) {
var expression = ['interpolate', ['linear'], ['to-number', value]]
for (var i = 0; i < stops.length; i++) {
expression.push(stops[i])
expression.push(colors[i])
}
return expression
}
function setPostcodes() {
map.setPaintProperty('postcodes', 'fill-color', createColorExpression(breaks, scale, ['feature-state', 'colorValue']))
for (var key in newdata) {
map.setFeatureState({
source: 'postcodes',
sourceLayer: 'pbi-us-postcodes',
id: key
}, {
'colorValue': newdata[key]['population'] != undefined ? newdata[key]['population'] : 0
})
}
}
let hoverId = 0;
function debounce(func, wait, immediate) {
var timeout;
return function() {
var context = this,
args = arguments;
var later = function() {
timeout = null;
if (!immediate) func.apply(context, args);
};
var callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func.apply(context, args);
};
};
var popup = new mapboxgl.Popup({
closeButton: false,
closeOnClick: false
});
var onMouseMove = debounce(function(e) {
var features = map.queryRenderedFeatures(e.point, {
layers: ['postcodes']
});
if (!features.length) {
popup.remove();
map.getCanvas().style.cursor = '';
map.setFeatureState({
source: 'postcodes',
sourceLayer: 'pbi-us-postcodes',
id: hoverId
}, { 'hover': 0.5 })
hoverId = 0;
return
}
map.getCanvas().style.cursor = 'pointer';
let newHoverId = features[0].id;
if (newHoverId != hoverId) {
map.setFeatureState({
source: 'postcodes',
sourceLayer: 'pbi-us-postcodes',
id: hoverId
}, { 'hover': 0.5 })
hoverId = newHoverId
}
map.setFeatureState({
source: 'postcodes',
sourceLayer: 'pbi-us-postcodes',
id: hoverId
}, { 'hover': 1 })
popup.setLngLat(map.unproject(e.point))
.setHTML('<b>Population: </b>' + newdata[hoverId]['population'] + ' <br>' +
'<b>Area: </b>' + newdata[hoverId]['area'] + ' <br>' +
'<b>Population/mi^s: </b>' + Math.round(newdata[hoverId]['ratio'], 3))
.addTo(map);
}, 14, true);
map.on('mousemove', onMouseMove);
let setAfterLoad = function(e) {
if (e.sourceId === 'postcodes' && e.isSourceLoaded) {
setPostcodes();
map.off('sourcedata', setAfterLoad)
}
}
if (map.isSourceLoaded('postcodes')) {
setPostcodes();
} else {
map.on('sourcedata', setAfterLoad);
}
};
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment