Published
Edited
Dec 3, 2020
1 fork
19 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
//raw =
Insert cell
Insert cell
Insert cell
Insert cell
map.color_func = function(f) {
const balance = (Math.sin(Date.now()/500) + 1)/2
const r1 = 1 - f.properties["2016_Trump"]/f.properties["2016_tot"]
const r2 = 1 - f.properties["2020_Trump"]/f.properties["2020_tot"]
if (f.properties["2020_tot"] === 0) {return [1, 1, 1]}
const {r, g, b} = d3.rgb(colorscale(r1*(1-balance) + r2*balance))
return [r/255, g/255, b/255]
}
Insert cell
size_scale = {
map // need map for the features to exist.
return d3.scaleSqrt().domain(d3.extent(election_districts.features.map(feature => feature.properties['2020_tot'] / feature.properties.pixel_area))).range([0, 3]).clamp(true)
}
Insert cell
map.size_func = areal == "area" ? () => 1 : (feature) => {
const balance = (Math.sin(Date.now()/500) + 1)/2
return size_scale((feature.properties['2020_tot'] * balance + feature.properties['2016_tot'] * (1-balance)) / feature.properties.pixel_area)
}
Insert cell
colorscale = d3.scaleSequential(d3.interpolateRdBu).domain([0.05, .95])
Insert cell
map = new TriMap(div, [election_districts])
Insert cell
md`# TriMap

A handler class to deal with interactions between regl contexts and feather feature sets. I'll use this more later.`
Insert cell
class TriMap {

constructor(div, layers) {
this.div = div
this.regl = wrapRegl({canvas: div, extensions: ["OES_element_index_uint"]})
for (let layer of layers) {
layer.bind_to_regl(this.regl)
}
this.layers = layers;

const {width, height} = div
this.magic_numbers = window_transform(
d3.scaleLinear().domain(layers[0].bbox.x).range([0, width]),
d3.scaleLinear().domain(layers[0].bbox.y).range([0, height]), width, height)
.map(d => d.flat())
this.prepare_div(width, height)
this.set_renderer()
this.regl.frame(() => this.tick())
}
prepare_div(width, height) {
this.zoom = {transform: {k: 1, x: 0, y:0}}
d3.select(this.div).call(d3.zoom().extent([[0, 0], [width, height]]).on("zoom", (event, g) => {
this.zoom.transform = event.transform
}));

return div;
}

get size_func() {
return this._size_function ? this._size_function : () => 1
}
set size_func(f) {
this._size_function = f
}
set color_func(f) {
this._color_function = f
}
get color_func() {
return this._color_function ? this._color_function : () => [.8, .8, .8]
}
tick() {
const { regl } = this
regl.clear({
color: [0, 0, 0, .01],
})
const alpha = 1
const calls = []
for (let feature of this.layers[0]) {
if (feature.properties['2020_tot'] === null) {continue}
const {vertices, coords} = feature;
calls.push({
transform: this.zoom.transform,
color: this.color_func(feature),
centroid: [feature.properties.centroid_x, feature.properties.centroid_y],
size: this.size_func(feature),
alpha: 1,
vertices: vertices,
coords: coords
})
}
this.render_polygons(calls)
}

set_renderer() {
this.render_polygons = this.regl(this.polygon_renderer())
}

polygon_renderer() {
const { regl, magic_numbers } = this;
return {
depth: {
enable: false
},

blend: {enable: true, func: {
srcRGB: 'one',
srcAlpha: 'one',
dstRGB: 'one minus src alpha',
dstAlpha: 'one minus src alpha',
}
},
vert: `
precision mediump float;
attribute vec2 position;
uniform float u_size;
uniform vec2 u_centroid;
varying vec4 fragColor;
uniform float u_k;
uniform float u_time;
uniform vec3 u_color;
varying vec4 fill;

// Transform from data space to the open window.
uniform mat3 u_window_scale;
// Transform from the open window to the d3-zoom.
uniform mat3 u_zoom;
uniform mat3 u_untransform;


// We can bundle the three matrices together here for all shaders.
mat3 from_coord_to_gl = u_window_scale * u_zoom * u_untransform;


float u_scale_factor = 0.5;

void main () {

// scale to normalized device coordinates
// gl_Position is a special variable that holds the position
// of a vertex
vec2 from_center = position-u_centroid;


vec3 p = vec3(from_center * u_size + u_centroid, 1.) * from_coord_to_gl;
gl_Position = vec4(p, 1.0);

gl_PointSize = u_size * (exp(log(u_k)*u_scale_factor));

fragColor = vec4(u_color.rgb, 1.);
//gl_Position = vec4(position / vec2(1., u_aspect), 1., 1.);
}
`,
frag: `
precision highp float;
uniform float u_alpha;
varying vec4 fragColor;

void main() {
gl_FragColor = fragColor * u_alpha;
}`,
attributes: {
position: (_, {coords}) => coords,
},
elements: (_, {vertices}) => vertices,
uniforms: {
u_time: (context, _) => performance.now()/500,
u_k: function(context, props) {
return props.transform.k
},
u_centroid: regl.prop('centroid'),
u_color: (_, {color}) => color ? color : [.8, .9, .2],
u_window_scale: magic_numbers[0].flat(),
u_untransform: magic_numbers[1].flat(),
u_zoom: function(context, props) {
const g = [
// This is how you build a transform matrix from d3 zoom.
[props.transform.k, 0, props.transform.x],
[0, props.transform.k, props.transform.y],
[0, 0, 1],
].flat()
return g
},
u_alpha: regl.prop('alpha'),
u_size: (_, {size}) => size || 1,

},
primitive: "triangles"
}
}


}
Insert cell
wrapRegl = require("regl")
Insert cell
d3 = require("d3@v6")
Insert cell

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more