Published
Edited
Oct 13, 2021
Fork of Regl
1 fork
Insert cell
Insert cell
viewof census = Inputs.range([1790, 2010], {label: "Census year", step: 10})
Insert cell
Insert cell
proj = {
return d3.geoAlbersUsa().translate([0, 0]).scale(3)
}
Insert cell
projected
Insert cell
hidden = ({size: 70e-11})
Insert cell
each_point_represents = 75
Insert cell
ticker = {
// Continuously updates
const t = regl.frame(() => {
regl.clear({ color: [0, 0.1, 0.26, 1] });
drawBunny({ixes});
})
invalidation.then(() => t.cancel())
return t
}
Insert cell
drawBunny = regl({
frag: `
precision mediump float;
void main () {
gl_FragColor = vec4(1.0, 1.0, 0.5, 1.) * .1;
}`,
vert: `
precision mediump float;
// input an index for each item.
attribute float ix;
attribute float a_x;
attribute float a_y;
attribute float a_radius;

uniform float u_time;
uniform vec2 u_aspect;

uniform float u_size_factor;
// General purpose random number.

const float tau = 3.14159265358 * 2.;

highp float ix_to_random(in float ix, in float seed) {
// For high numbers, taking the log avoids coincidence.
highp float seed2 = log(ix + .1) + 1.;
vec2 co = vec2(seed2, seed);
highp float a = 12.9898;
highp float b = 78.233;
highp float c = 43758.5453;
highp float dt= dot(co.xy ,vec2(a,b));
highp float sn= mod(dt,3.14);
return fract(sin(sn) * c);
}

highp vec2 box_muller(in float ix, in float seed) {
// Box-Muller transform gives you two gaussian randoms for two uniforms.
// It's the best deal on the boardwalk.
highp float U = ix_to_random(ix, seed);
highp float V = ix_to_random(ix, seed + 17.123123);
return vec2(
sqrt(-2.*log(U))*cos(tau*V),
sqrt(-2.*log(U))*sin(tau*V)
);
}


vec3 spherical_jitter(in float ix, in float angle) {
// ix: a random seed that is stable for the point.
// angle: a number in radians expressing how many turns around the sphere to take.
// if linked to time, points will animate.

// Start by randomly distributing a point on the unit sphere from
// three gaussian coordinates.
vec2 g1 = box_muller(ix, 10.);
vec2 g2 = box_muller(ix, 13.);
vec3 pos = vec3(g1.xy, g2.x);
pos = pos / sqrt(dot(pos, pos));

// We're going to rotate it around three angles.
// First, an arbitrary alpha.
float alpha = 3.;
float gamma = -.1;

// Finally, the angle of rotation. This is how far along the particular circular path we've gone.

float beta = ix_to_random(ix, 2.) * tau - angle;

vec3 pos_3d = pos *
mat3(1., 0., 0.,
0., cos(alpha), -sin(alpha),
0., sin(alpha), cos(alpha)) *
mat3(cos(beta), 0., sin(beta),
0., 1., 0.,
-sin(beta), 0., cos(beta)) *
mat3(cos(gamma), -sin(gamma), 0.,
sin(gamma), cos(gamma), 0.,
0., 0., 1.);

return pos_3d;
}

void main () {
if (a_x * a_y == 0.) {
gl_Position = vec4(100., 100., 100., 100.);
return;
}
gl_PointSize = 1.;
vec3 pos_3d = spherical_jitter(ix, u_time);
if (pos_3d.z < 0.) {
// throw away points on the back side of the sphere.
gl_Position = vec4(100., 100., 100., 100.);
return;
}
float radius = pow(a_radius * u_size_factor, 1./3.);
vec2 jitter = pos_3d.xy * radius / u_aspect;
vec2 xy = vec2(a_x, a_y * -1.) / u_aspect + jitter;
gl_Position = vec4(xy.x, xy.y, 0., 1.);
}`,
primitive: "points",
count: N,
attributes: {
ix: ixes,
a_x: my_buffers.x,
a_y: my_buffers.y,
a_radius: my_buffers.pop
},
blend: {
enable: true,
func: {
srcRGB: 'src alpha',
srcAlpha: 'src alpha',
dstRGB: 'one minus src alpha',
dstAlpha: 'one minus src alpha',
},
},
depth: {enable: false},
uniforms: {
u_aspect: ctx => {
const ar = ctx.viewportWidth / ctx.viewportHeight;
console.log(ar > 1 ? [ar, 1] : [1, 1 / ar]);
return ar > 1 ? [ar, 1] : [1, 1 / ar];
},
u_size_factor: () => hidden.size,
u_time: ({time}) => time
}
})
Insert cell
md`# Data

`
Insert cell
my_buffers = {
const pop = regl.buffer(arrays.r)
const x = regl.buffer(arrays.x)
const y = regl.buffer(arrays.y)
invalidation.then(() => {
pop.destroy();
x.destroy();
y.destroy();
})
return {pop, x, y}
}
Insert cell
cities_raw = d3.csv("https://raw.githubusercontent.com/CreatingData/Historical-Populations/master/merged.csv")
Insert cell
cities = cities_raw.filter(d => d['Place Type'] !== "")
Insert cell
cities.filter(d => d[2010] > 1000000)
Insert cell
projected = {
const reformatted = []
for (let city of cities) {
const [x, y] = proj([+city.lon, +city.lat]) || [0, 0] // offscreen for null
reformatted.push([x, y])
}
return reformatted
}
Insert cell
pops = {
let pops = []
for (let city of cities) {
pops.push(+city[census])
}
return pops
}
Insert cell
N = Math.floor(d3.sum(pops) / each_point_represents)
Insert cell
fractional_pops = {
const total = d3.sum(pops)
const fracs = pops.map(d => d/total)
return d3.cumsum(fracs)
}
Insert cell
Insert cell
arrays = ({
x: coords.x,
y: coords.y,
r: random_ixes.pops
})
Insert cell
coords = {
const x = new Float32Array(random_ixes.ixes.length)
const y = new Float32Array(random_ixes.ixes.length)
let i = 0;
for (let i = 0; i < N; i++) {
const ix = random_ixes.ixes[i]
x[i] = projected[ix][0]
y[i] = projected[ix][1]
}
return {x, y}
}
Insert cell
random_ixes = {
const ixes = new Int32Array(N)
const y = new Float32Array(N)
const r = new Float32Array(N)
const names = []
for (let i = 0; i < N; i++) {
// get a random city
const ix = d3.bisect(fractional_pops, Math.random())
r[i] = pops[ix]
ixes[i] = ix
names[i] = cities[ix].title
}
return {ixes, pops: r, names}
}
Insert cell
seeds = new Float32Array(d3.range(N))
Insert cell
ixes = regl.buffer(seeds)
Insert cell
regl = (await require("regl"))({ canvas })
Insert cell
us_geojson = d3.json('https://raw.githubusercontent.com/PublicaMundi/MappingAPI/master/data/geojson/us-states.json')
Insert cell
d3 = require('d3')
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