Published
Edited
May 6, 2019
1 fork
Insert cell
Insert cell
viewof map = {
const container = html`<div style='height:460px;' />`;

// Give the container dimensions.
yield container;

const map = new mapboxgl.Map({
container,
center: [5.456252, 52.137609],
zoom: 6,
style: "mapbox://styles/mapbox/dark-v10",
antialias: true,
});

// Wait until the map loads.
map.on("load", () => {
map.addLayer(flowsLayer, 'road-label');
map.addLayer(locationsLayer, 'road-label');
});
}
Insert cell
flowsLayer = ({
id: 'flows',
type: 'custom',

onAdd: function (map, gl) {
this.program = gl.createProgram();
gl.attachShader(this.program, compileShader(gl, gl.VERTEX_SHADER, `
precision highp float;
uniform mat4 u_matrix;
attribute vec3 a_origin;
attribute vec3 a_dest;
attribute vec4 a_color;
attribute vec3 a_square_vert;
varying vec2 v_square_pos;
varying vec4 v_color;
void main() {
v_square_pos = a_square_vert.xy; // position within [-1, 1] square
v_color = a_color;
gl_Position = u_matrix * vec4(mix(a_origin.xy, a_dest.xy, v_square_pos.x), 0.0, 1.0) +
vec4(a_square_vert.x, 10.0*a_square_vert.y, 0.0, 0.0);
}
`));
gl.attachShader(this.program, compileShader(gl, gl.FRAGMENT_SHADER, `
precision highp float;
varying vec2 v_square_pos;
varying vec4 v_color;
void main() {
gl_FragColor = v_color;
}
`));
gl.linkProgram(this.program);
// Square vertices
this.aSquareVert = gl.getAttribLocation(this.program, "a_square_vert");
this.squareVerts = new Float32Array([
0, -1, 0,
0, 1, 0,
1, -1, 0,
1, 1, 0,
]);
this.squareVertsBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, this.squareVertsBuffer);
gl.bufferData(gl.ARRAY_BUFFER, this.squareVerts, gl.STATIC_DRAW);

// Flows
this.aOrigin = gl.getAttribLocation(this.program, "a_origin");
this.aDest = gl.getAttribLocation(this.program, "a_dest");
this.aColor = gl.getAttribLocation(this.program, "a_color");
const originCoords = new Float32Array(flows.length * 3);
const destCoords = new Float32Array(flows.length * 3);
const colors = new Float32Array(flows.length * 4);
for (let i = 0; i < flows.length; i++) {
const { origin, dest, count } = flows[i];
originCoords[i * 3] = origin.x;
originCoords[i * 3 + 1] = origin.y;
originCoords[i * 3 + 2] = i;
destCoords[i * 3] = dest.x;
destCoords[i * 3 + 1] = dest.y;
destCoords[i * 3 + 2] = i;
const {r,g,b} = d3.rgb(flowColorScale(count));
colors[i * 4] = r/255;
colors[i * 4 + 1] = g/255;
colors[i * 4 + 2] = b/255;
colors[i * 4 + 3] = flowOpacityScale(count);
}
this.originCoordsBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, this.originCoordsBuffer);
gl.bufferData(gl.ARRAY_BUFFER, originCoords, gl.STATIC_DRAW);
this.destCoordsBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, this.destCoordsBuffer);
gl.bufferData(gl.ARRAY_BUFFER, destCoords, gl.STATIC_DRAW);
this.colorsBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, this.colorsBuffer);
gl.bufferData(gl.ARRAY_BUFFER, colors, gl.STATIC_DRAW);
},

render: function (gl, matrix) {
gl.useProgram(this.program);
gl.uniformMatrix4fv(gl.getUniformLocation(this.program, "u_matrix"), false, matrix);

// Square vertices
gl.bindBuffer(gl.ARRAY_BUFFER, this.squareVertsBuffer);
gl.enableVertexAttribArray(this.squareVertsBuffer);
gl.vertexAttribPointer(this.aSquareVert, 3, gl.FLOAT, false, 0, 0);
gl.vertexAttribDivisor(this.aSquareVert, 0); // non-instanced

// Flows
gl.bindBuffer(gl.ARRAY_BUFFER, this.originCoordsBuffer);
gl.enableVertexAttribArray(this.aOrigin);
gl.vertexAttribPointer(this.aOrigin, 3, gl.FLOAT, false, 0, 0);
gl.vertexAttribDivisor(this.aOrigin, 1); // instanced

gl.bindBuffer(gl.ARRAY_BUFFER, this.destCoordsBuffer);
gl.enableVertexAttribArray(this.aDest);
gl.vertexAttribPointer(this.aDest, 3, gl.FLOAT, false, 0, 0);
gl.vertexAttribDivisor(this.aDest, 1); // instanced

gl.bindBuffer(gl.ARRAY_BUFFER, this.colorsBuffer);
gl.enableVertexAttribArray(this.aColor);
gl.vertexAttribPointer(this.aColor, 4, gl.FLOAT, false, 0, 0);
gl.vertexAttribDivisor(this.aColor, 1); // instanced

gl.enable(gl.BLEND);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);

gl.drawArraysInstanced(gl.TRIANGLE_STRIP, 0, this.squareVerts.length / 3, flows.length);
}
})
Insert cell
maxFlowMagnitude = d3.max(flows, flow => flow.count)
Insert cell
flowOpacityScale = d3.scalePow()
.exponent(1/1000)
.range([0, 1.0])
.domain([0, maxFlowMagnitude])
Insert cell
flowColorScale = d3.scaleSequentialPow(d3.interpolateRgbBasis(d3.schemeYlGn[9].reverse()))
.exponent(1/3)
.domain([0, maxFlowMagnitude])
Insert cell
locationsLayer = ({
id: 'locations',
type: 'custom',

onAdd: function (map, gl) {
this.program = gl.createProgram();
gl.attachShader(this.program, compileShader(gl, gl.VERTEX_SHADER, `
precision highp float;
uniform mat4 u_matrix;
attribute vec3 a_location;
attribute vec3 a_square_vert;
varying vec2 v_square_pos;
void main() {
v_square_pos = a_square_vert.xy; // position within [-1, 1] square
float radius = a_location.z;
float deform = u_matrix[1][1]/u_matrix[0][0];
gl_Position = u_matrix * vec4(a_location.xy, 0.0, 1.0) +
radius * vec4(a_square_vert.x, a_square_vert.y * deform, 0.0, 0.0);
}
`));
gl.attachShader(this.program, compileShader(gl, gl.FRAGMENT_SHADER, `
precision highp float;
varying vec2 v_square_pos;
void main() {
if (length(v_square_pos) > 1.0) { discard; }
if (length(v_square_pos) > 0.95) {
gl_FragColor = vec4(0.1, 0.1, 0.2, 0.5);
} else {
gl_FragColor = vec4(0.80, 0.90, 0.99, 0.98);
}
}
`));
gl.linkProgram(this.program);
// Square vertices
this.aSquareVert = gl.getAttribLocation(this.program, "a_square_vert");
this.squareVerts = new Float32Array([
// a square to cover the unit circle
-1, -1, 0,
-1, 1, 0,
1, 1, 0,
1, -1, 0,
]);
this.squareVertsBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, this.squareVertsBuffer);
gl.bufferData(gl.ARRAY_BUFFER, this.squareVerts, gl.STATIC_DRAW);

// Location coords
this.aLocationCoords = gl.getAttribLocation(this.program, "a_location");
const locationCoords = new Float32Array(locations.length * 3);
locations.sort((a, b) => d3.ascending(
locationTotals.max.get(a.id) || 0,
locationTotals.max.get(b.id) || 0,
));
for (let i = 0; i < locations.length; i++) {
const { id, x, y } = locations[i];
locationCoords[i * 3] = x;
locationCoords[i * 3 + 1] = y;
// radius and z
locationCoords[i * 3 + 2] = locationRadiusScale(locationTotals.max.get(id));
}
this.locationCoordsBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, this.locationCoordsBuffer);
gl.bufferData(gl.ARRAY_BUFFER, locationCoords, gl.STATIC_DRAW);
},

render: function (gl, matrix) {
gl.useProgram(this.program);
gl.uniformMatrix4fv(gl.getUniformLocation(this.program, "u_matrix"), false, matrix);

// Square vertices
gl.bindBuffer(gl.ARRAY_BUFFER, this.squareVertsBuffer);
gl.enableVertexAttribArray(this.squareVertsBuffer);
gl.vertexAttribPointer(this.aSquareVert, 3, gl.FLOAT, false, 0, 0);
gl.vertexAttribDivisor(this.aSquareVert, 0); // non-instanced

// Location coords
gl.bindBuffer(gl.ARRAY_BUFFER, this.locationCoordsBuffer);
gl.enableVertexAttribArray(this.aLocationCoords);
gl.vertexAttribPointer(this.aLocationCoords, 3, gl.FLOAT, false, 0, 0);
gl.vertexAttribDivisor(this.aLocationCoords, 1); // instanced
gl.enable(gl.BLEND);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);

gl.drawArraysInstanced(gl.TRIANGLE_FAN, 0, this.squareVerts.length / 3, locations.length);
}
})
Insert cell
locationRadiusScale = d3.scalePow(1/2)
.range([1, 40])
.domain([0, d3.max(Array.from(locationTotals.incoming.values()))])
Insert cell
locationTotals = {
const incoming = new Map();
const outgoing = new Map();
for (const { origin, dest, count } of flows) {
incoming.set(dest.id, (incoming.get(dest.id) || 0) + count);
outgoing.set(origin.id, (outgoing.get(origin.id) || 0) + count);
}
const max = new Map();
for (const { id } of locations) {
max.set(id, Math.max(incoming.get(id) || 0, outgoing.get(id) || 0));
}
return { incoming, outgoing, max };
}
Insert cell
function compileShader(gl, type, source) {
const shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
if (gl.getShaderParameter(shader, gl.COMPILE_STATUS)) return shader;
throw new Error(gl.getShaderInfoLog(shader));
}
Insert cell
locationsById = d3.nest().key(d => d.id).rollup(([d]) => d).object(locations)
Insert cell
locations = {
const response = await fetch(
'https://docs.google.com/spreadsheets/d/1Oe3zM219uSfJ3sjdRT90SAK2kU3xIvzdcCW6cwTsAuc/gviz/tq?tq=SELECT%20A,B,C,D&tqx=out:csv&sheet=locations'
);
return d3.csvParse(await response.text()).map(loc => ({
...loc,
...mapboxgl.MercatorCoordinate.fromLngLat({ lng: +loc.lon, lat: +loc.lat }),
}));
}
Insert cell
flows = {
const response = await fetch(
'https://docs.google.com/spreadsheets/d/1Oe3zM219uSfJ3sjdRT90SAK2kU3xIvzdcCW6cwTsAuc/gviz/tq?tq=SELECT%20A,B,C&tqx=out:csv&sheet=flows'
);
const flows = d3.csvParse(await response.text())
.map(({ origin, dest, count }) => ({
origin: locationsById[origin],
dest: locationsById[dest],
count: +count
}));
flows.sort((a, b) => d3.ascending(a.count, b.count));
return flows;
}
Insert cell
d3 = require("d3@5")
Insert cell
mapboxgl = {
// Using a fork of mapbox-gl-js with WebGL 2.0 enabled
const response = await fetch("https://raw.githubusercontent.com/ilyabo/mapbox-gl-js/master/dist/mapbox-gl.js");
const blob = await response.blob();
const gl = await require(URL.createObjectURL(blob)).catch(() => window.mapboxgl);
if (!gl.accessToken) {
gl.accessToken =
"pk.eyJ1IjoiaWx5YWJvIiwiYSI6ImNqdjk0YXUwejBsMmY0ZHBmMHdjM25xdTIifQ.DIX2FlU4IC8uCGZfWeWWkA";
const href = await require.resolve("mapbox-gl@0.54/dist/mapbox-gl.css");
document.head.appendChild(html`<link href=${href} rel=stylesheet>`);
}
return gl;
}
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more