Last active
December 19, 2019 19:51
-
-
Save Pessimistress/dc2becf3809c67dc443b4dbab1b9a46f to your computer and use it in GitHub Desktop.
Deck.gl + Mapbox Brushing Arcs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<html> | |
<head> | |
<title>US County-to-County Migration</title> | |
<meta name='viewport' content='initial-scale=1,maximum-scale=1,user-scalable=no' /> | |
<script src="https://unpkg.com/deck.gl@^6.2.0/deckgl.min.js"></script> | |
<script src="https://api.tiles.mapbox.com/mapbox-gl-js/v0.50.0/mapbox-gl.js"></script> | |
<link rel="stylesheet" type="text/css" href="https://api.tiles.mapbox.com/mapbox-gl-js/v0.50.0/mapbox-gl.css"> | |
<script src="https://d3js.org/d3.v5.min.js"></script> | |
<style type="text/css"> | |
body { width: 100vw; height: 100vh; margin: 0; } | |
</style> | |
</head> | |
<body> | |
</body> | |
<script type="text/javascript"> | |
mapboxgl.accessToken = 'pk.eyJ1IjoidWJlcmRhdGEiLCJhIjoiY2pudzRtaWloMDAzcTN2bzN1aXdxZHB5bSJ9.2bkj3IiRC8wj3jLThvDGdA'; | |
const {MapboxLayer, ArcLayer, ScatterplotLayer} = deck; | |
const DATA_URL = 'https://raw.githubusercontent.com/uber-common/deck.gl-data/master/examples/arc/counties.json'; | |
// migrate out | |
const SOURCE_COLOR = [166, 3, 3]; | |
// migrate in | |
const TARGET_COLOR = [35, 181, 184]; | |
const RADIUS_SCALE = d3.scaleSqrt().domain([0, 8000]).range([1000, 20000]); | |
const WIDTH_SCALE = d3.scaleLinear().domain([0, 1000]).range([1, 4]); | |
const map = new mapboxgl.Map({ | |
container: document.body, | |
style: 'mapbox://styles/mapbox/light-v9', | |
center: [-100, 40.7], | |
zoom: 3 | |
}); | |
let countiesLayer; | |
let arcsLayer; | |
map.on('load', () => { | |
d3.json(DATA_URL).then(loadData); | |
}); | |
map.on('mousemove', ({point}) => { | |
if (arcsLayer) { | |
arcsLayer.setProps({mousePosition: [point.x, point.y]}); | |
} | |
}); | |
function renderLayers({arcs, counties}) { | |
countiesLayer = new MapboxLayer({ | |
type: ScatterplotLayer, | |
id: 'counties', | |
data: counties, | |
opacity: 1, | |
pickable: true, | |
// onHover: this._onHover, | |
getRadius: d => RADIUS_SCALE(d.total), | |
getColor: d => (d.net > 0 ? TARGET_COLOR : SOURCE_COLOR) | |
}); | |
arcsLayer = new MapboxLayer({ | |
type: ArcBrushingLayer, | |
id: 'arcs', | |
data: arcs, | |
brushRadius: 100000, | |
getStrokeWidth: d => WIDTH_SCALE(d.value), | |
opacity: 1, | |
getSourcePosition: d => d.source, | |
getTargetPosition: d => d.target, | |
getSourceColor: SOURCE_COLOR, | |
getTargetColor: TARGET_COLOR | |
}); | |
map.addLayer(countiesLayer, 'waterway-label'); | |
map.addLayer(arcsLayer); | |
} | |
function loadData(data) { | |
const arcs = []; | |
const counties = []; | |
const pairs = {}; | |
data.features.forEach((county, i) => { | |
const {flows, centroid: targetCentroid} = county.properties; | |
const value = {gain: 0, loss: 0}; | |
Object.keys(flows).forEach(toId => { | |
value[flows[toId] > 0 ? 'gain' : 'loss'] += flows[toId]; | |
const pairKey = i < toId ? `${i}-${toId}` : `${toId}-${i}`; | |
const sourceCentroid = data.features[toId].properties.centroid; | |
const gain = Math.sign(flows[toId]); | |
// eliminate duplicates arcs | |
if (pairs[pairKey]) { | |
return; | |
} | |
pairs[pairKey] = true; | |
arcs.push({ | |
target: gain > 0 ? targetCentroid : sourceCentroid, | |
source: gain > 0 ? sourceCentroid : targetCentroid, | |
value: Math.abs(flows[toId]) | |
}); | |
}); | |
// add point at arc target | |
counties.push({ | |
...value, | |
position: targetCentroid, | |
net: value.gain + value.loss, | |
total: value.gain - value.loss, | |
name: county.properties.name | |
}); | |
}); | |
// sort counties by radius large -> small | |
counties.sort((a, b) => Math.abs(b.net) - Math.abs(a.net)); | |
renderLayers({arcs, counties}); | |
} | |
class ArcBrushingLayer extends ArcLayer { | |
getShaders() { | |
// use customized shaders | |
return Object.assign({}, super.getShaders(), { | |
inject: { | |
'vs:#decl': ` | |
uniform vec2 mousePosition; | |
uniform float brushRadius; | |
`, | |
'vs:#main-end': ` | |
float brushRadiusPixels = project_scale(brushRadius); | |
vec2 sourcePosition = project_position(instancePositions.xy); | |
bool isSourceInBrush = distance(sourcePosition, mousePosition) <= brushRadiusPixels; | |
vec2 targetPosition = project_position(instancePositions.zw); | |
bool isTargetInBrush = distance(targetPosition, mousePosition) <= brushRadiusPixels; | |
if (!isSourceInBrush && !isTargetInBrush) { | |
vColor.a = 0.0; | |
} | |
`, | |
'fs:#main-start': ` | |
if (vColor.a == 0.0) discard; | |
` | |
} | |
}); | |
} | |
draw(opts) { | |
const {brushRadius = 1e6, mousePosition} = this.props; | |
// add uniforms | |
const uniforms = Object.assign({}, opts.uniforms, { | |
brushRadius: brushRadius, | |
mousePosition: mousePosition ? | |
this.projectPosition(this.unproject(mousePosition)).slice(0, 2) : [0, 0] | |
}); | |
super.draw(Object.assign({}, opts, {uniforms})); | |
} | |
} | |
</script> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment