Skip to content

Instantly share code, notes, and snippets.

@Pessimistress
Last active December 19, 2019 19:51
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save Pessimistress/dc2becf3809c67dc443b4dbab1b9a46f to your computer and use it in GitHub Desktop.
Save Pessimistress/dc2becf3809c67dc443b4dbab1b9a46f to your computer and use it in GitHub Desktop.
Deck.gl + Mapbox Brushing Arcs
<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