Public
Edited
Mar 15
2 forks
Importers
29 stars
Insert cell
Insert cell
Insert cell
import { createReglCamera, createInteractions } from 'd0ffd921b8188ba9'
Insert cell
Insert cell
function reglCanvas (width, height, dpi, reglOptions) {
dpi = dpi === undefined ? devicePixelRatio : dpi;
reglOptions = reglOptions || {}
var canvas = document.createElement("canvas");
canvas.width = dpi * width;
canvas.height = dpi * height;
canvas.style.width = width + "px";
const regl = createREGL(Object.assign({}, reglOptions, {pixelRatio: dpi, canvas}));
canvas.value = regl;
canvas.__reglConfig = {dpi, reglOptions}
return canvas;
}
Insert cell
md`## reusableReglCanvas`
Insert cell
function reusableReglCanvas (existingCanvas, config) {
var dpi = config.dpi === undefined ? devicePixelRatio : config.dpi
var reglOptions = config.reglOptions || {}
var newConfig = {dpi, reglOptions}
var needsNewContext = !existingCanvas ||
JSON.stringify(newConfig) !== JSON.stringify(existingCanvas.__reglConfig)
if (!needsNewContext) {
existingCanvas.width = Math.floor(config.width * dpi)
existingCanvas.height = Math.floor(config.height * dpi)
existingCanvas.style.width = `${width}px`
return existingCanvas
}
if (existingCanvas && existingCanvas.value && existingCanvas.value.destroy) {
existingCanvas.value.destroy()
delete existingCanvas.value
}
return reglCanvas(config.width, config.height, dpi, reglOptions)
}
Insert cell
Insert cell
function reglCanvasWithOptions (reglOptions) {
return function (width, height, dpi) {
return reglCanvas(width, height, dpi, reglOptions);
}
}
Insert cell
function getOrAttachReglFO (svg, width, height, dpi, reglOptions) {
let fo = svg.selectAll('.regl-canvas')
.data([null]).join(enter => enter.append('foreignObject'))
.attr('class', 'regl-canvas')
.attr('width', width)
.attr('height', height)
//.style('position', 'relative')
//.style('z-index', -1)

fo.selectAll('canvas')
.data([null]).join(enter => enter.append('xhtml:canvas'))
.attr('width', width * dpi)
.attr('height', height * dpi)
.style('width', width + 'px')
.style('height', height + 'px')

const reglCanvas = fo.select('canvas').node()
const regl = reglCanvas.value = reglCanvas.value || createREGL(Object.assign(reglOptions || {}, {
canvas: reglCanvas,
pixelRatio: dpi,
}))
regl.data = regl.data || {}
return reglCanvas;
}
Insert cell
Insert cell
function createLayerStack (w, h, dpi, layerConstructors) {
w = Math.floor(w);
h = Math.floor(h);
dpi = dpi === undefined ? devicePixelRatio : dpi;
const dpiWidth = Math.floor(w * dpi);
const dpiHeight = Math.floor(h * dpi);
const container = document.createElement('div');
container.style.position = "relative";
container.style.width = w + 'px';
container.style.height = h + 'px';
function createLayer(ctor, i) {
let element;
let value = ctor(w, h, dpi);
if (value instanceof Element) {
element = value;
value = element.value || element;
} else {
element = value.canvas || value;
}
element.style.position = "absolute";
element.style.top = 0
element.style.left = 0;
element.style.width = '100%';
element.style.height = '100%';
element.style.zIndex = i;
container.appendChild(element);
return value;
}
let layers;
if (Array.isArray(layerConstructors)) {
layers = layerConstructors.map(createLayer);
} else {
let layerNames = Object.keys(layerConstructors);
layers = {};
layerNames.forEach((name, i) => {
layers[name] = createLayer(layerConstructors[name], i);
})
}
return {
layers,
container,
dpiWidth,
dpiHeight,
dpi,
width: w,
height: h
};
}
Insert cell
Insert cell
function createReglViewportConfiguration (regl) {
const viewport3 = mat3create();
let command = regl({
scissor: {
enable: true,
box: {
x: (ctx, props) => ctx.pixelRatio * props.margin.l,
y: (ctx, props) => ctx.pixelRatio * props.margin.b,
width: (ctx, props) => ctx.framebufferWidth - ctx.pixelRatio * (props.margin.r + props.margin.l),
height: (ctx, props) => ctx.framebufferHeight - ctx.pixelRatio * (props.margin.t + props.margin.b)
}
},
viewport: {
x: (ctx, props) => ctx.pixelRatio * props.margin.l,
y: (ctx, props) => ctx.pixelRatio * props.margin.b,
width: (ctx, props) => ctx.framebufferWidth - ctx.pixelRatio * (props.margin.r + props.margin.l),
height: (ctx, props) => ctx.framebufferHeight - ctx.pixelRatio * (props.margin.t + props.margin.b)
},
uniforms: {
viewportResolution: (ctx, props) => [ctx.viewportWidth, ctx.viewportHeight],
framebufferResolution: ctx => [ctx.framebufferWidth, ctx.framebufferHeight],
inverseViewportResolution: (ctx, props) => [1 / ctx.viewportWidth, 1 / ctx.viewportHeight],
inverseFramebufferResolution: ctx => [1 / ctx.framebufferWidth, 1 / ctx.framebufferHeight],
}
});
return function (viewport, callback) {
command(viewport, callback);
}
}
Insert cell
Insert cell
function createReglLinearScaleConfiguration(regl) {
const matrices = {
view3: mat3create(),
inverseView3: mat3create(),
view: mat4create(),
inverseView: mat4create()
};
const command = regl({
context: {
view3: regl.prop('view3'),
inverseView3: regl.prop('inverseView3'),
view: regl.prop('view'),
inverseView: regl.prop('inverseView')
},
uniforms: {
view3: regl.prop('view3'),
inverseView3: regl.prop('inverseView3'),
view: regl.prop('view'),
inverseView: regl.prop('inverseView')
}
});

return function(xScale, yScale, clbk) {
mat3fromLinearScales(matrices.view3, xScale, yScale);
mat3invert(matrices.inverseView3, matrices.view3);
mat4fromMat3(matrices.view, matrices.view3);
mat4fromMat3(matrices.inverseView, matrices.inverseView3);
command(matrices, clbk);
};
}
Insert cell
Insert cell
createReglMap = function (regl, opts) {
opts = opts || {};
let transform = opts.transform === undefined ? 'inverseView' : opts.transform;
return regl({
vert: `
precision highp float;
${transform ? `uniform mat4 ${transform};` : ''}
attribute vec2 aUV;
varying vec2 xy;
void main () {
xy = ${transform ? `(${transform} * vec4(aUV, 0, 1)).xy` : 'aUV'};
gl_Position = vec4(aUV, 0, 1);
}`,
attributes: {aUV: [-4, -4, 4, -4, 0, 4]},
depth: {enable: false},
primitive: 'triangles',
count: 3,
})
}
Insert cell
function mat3ViewportFromLinearScales (out, xScale, yScale) {
let xRange = xScale.range();
let yRange = yScale.range();
let w = xRange[1] - xRange[0];
let h = yRange[0] - yRange[1];
out[0] = 0.5 * w;
out[1] = 0;
out[2] = 0;

out[3] = 0;
out[4] = -0.5 * h;
out[5] = 0;

out[6] = 0.5 * w + xRange[0];
out[7] = 0.5 * h + yRange[1];
out[8] = 1;
return out;
}
Insert cell
function mat3fromLinearScales (out, xScale, yScale) {
let xDomain = xScale.domain()
let yDomain = yScale.domain()
let xs = 2 / (xDomain[1] - xDomain[0])
let ys = 2 / (yDomain[1] - yDomain[0])
out[0] = xs
out[1] = 0
out[2] = 0
out[3] = 0
out[4] = ys
out[5] = 0
out[6] = -1 - xs * xDomain[0]
out[7] = -1 - ys * yDomain[0]
out[8] = 1
return out;
}
Insert cell
Insert cell
function createViewport (opts, margin) {
return {
width: opts.width,
height: opts.height,
dpi: opts.dpi,
margin: Object.assign({t: 0, r: 0, b: 0, l: 0}, margin || {}),
};
}
Insert cell
Insert cell
function viewportAxes (viewport, xScale, yScale, opts) {
opts = opts || {};
viewport.margin = viewport.margin === undefined ? {t: 0, r: 0, b: 0, l: 0} : viewport.margin;
opts.xLabelSpacing = opts.xLabelSpacing === undefined ? 80 : opts.xLabelSpacing
opts.yLabelSpacing = opts.yLabelSpacing === undefined ? 80 : opts.yLabelSpacing
let xAxis = opts.xAxis === undefined ? d3.axisBottom : opts.xAxis
let yAxis = opts.yAxis === undefined ? d3.axisLeft : opts.yAxis
let xRange = xScale.range();
let yRange = xScale.range();
viewport.width = viewport.width === undefined ? (xRange[1] - xRange[0]) : viewport.width;
viewport.height = viewport.height === undefined ? (yRange[1] - yRange[0]) : viewport.height;
return function (selection) {
let xa = selection.select('g.x.axis');
if (xa.empty()) {
xa = selection.append("g").attr("class", "x axis")
}
xa.attr("transform", "translate(0," + (viewport.height - viewport.margin.b) + ")")
.call(xAxis(xScale)
.ticks(Math.floor((viewport.width - viewport.margin.l - viewport.margin.r) / opts.xLabelSpacing)))

let ya = selection.select('g.y.axis');
if (ya.empty()) {
ya = selection.append("g").attr("class", "y axis")
}
ya.attr('transform', 'translate(' + viewport.margin.l + ', 0)')
.call(yAxis(yScale)
.ticks(Math.floor((viewport.height - viewport.margin.t - viewport.margin.b) / opts.yLabelSpacing)))
return selection;
};
}
Insert cell
function constrainLinearScaleAspectRatio (newScale, refScale, aspectRatio) {
let newDomain = newScale.domain();
let refDomain = refScale.domain();
let newRange = newScale.range();
let refRange = refScale.range();
let newDomainLength = newDomain[1] - newDomain[0];
let newRangeLength = newRange[1] - newRange[0];
let refDomainLength = refDomain[1] - refDomain[0];
let refRangeLength = refRange[1] - refRange[0];
let refRes = refRangeLength / refDomainLength;
let newRes = newRangeLength / newDomainLength;
let currentAspect = Math.abs(newRes / refRes);
let newDomainCenter = 0.5 * (newDomain[0] + newDomain[1]);
newScale.domain([
newDomainCenter - newDomainLength * 0.5 * currentAspect / aspectRatio,
newDomainCenter + newDomainLength * 0.5 * currentAspect / aspectRatio
]);
return newScale;
}
Insert cell
function persistentZoom (xScale, yScale, originalXScale, originalYScale, callback) {
return d3.zoom().on('zoom', function () {
let range
let t = d3.event.transform;

range = xScale.range().map(t.invertX, t);
xScale.domain(originalXScale.domain())
xScale.domain(range.map(xScale.invert, xScale));

range = yScale.range().map(t.invertY, t);
yScale.domain(originalYScale.domain())
yScale.domain(range.map(yScale.invert, yScale));
});
}
Insert cell
function createTextureLookupTable(w, h, stride) {
stride = stride || 2;
var n = w * h * stride;

var out = new Float32Array(n);

for (var i = 0, iStride = 0; iStride < n; i++, iStride += stride) {
out[iStride] = ((i % w) + 0.5) / w;
out[iStride + 1] = (((i / w) | 0) + 0.5) / h;
}

return out;
}
Insert cell
Insert cell
function canWriteToFBOOfType(regl, type) {
type = type || "float";
if (!regl.hasExtension(`oes_texture_${type.replace(" ", "_")}`)) return false;
let floatFBO, uintFBO, draw, transfer;
try {
floatFBO = regl.framebuffer({
colorType: type,
colorFormat: "rgba",
radius: 1
});

uintFBO = regl.framebuffer({
colorType: "uint8",
colorFormat: "rgba",
radius: 1
});

draw = regl({
vert: `
precision highp float;
attribute vec2 aXY;
void main () {
gl_Position = vec4(aXY, 0, 1);
gl_PointSize = 1.0;
}`,
frag: `
precision highp float;
void main () {
gl_FragColor = vec4(1, 0, 0, 1);
}`,
primitive: "points",
count: 1,
attributes: {
aXY: [0, 0]
},
depth: { enable: false }
});

transfer = regl({
vert: `
precision highp float;
attribute vec2 aXY;
void main () {
gl_Position = vec4(aXY, 0, 1);
}`,
frag: `
precision highp float;
void main () {
gl_FragColor = vec4(1, 0, 0, 1);
}`,
attributes: {
aXY: [-4, -4, 4, -4, 0, 4]
},
count: 3,
primitive: "triangles",
depth: { enable: false }
});

draw();
var data;
uintFBO.use(() => {
transfer();
data = regl.read();
});

return data[0] !== 0 && data[1] === 0 && data[2] === 0 && data[3] !== 0;
} catch (e) {
console.error(e);
return false;
} finally {
if (floatFBO) floatFBO.destroy();
if (uintFBO) uintFBO.destroy();
if (draw) draw.destroy();
if (transfer) transfer.destroy();
}
}
Insert cell
Insert cell
stack = createLayerStack(width, height, devicePixelRatio, {
regl: (w, h, dpi) => reglCanvas(w, h, dpi),
canvas: DOM.context2d,
svg: DOM.svg
})
Insert cell
viewport = createViewport(stack, {t: 10, r: 15, b: 22, l: 37})
Insert cell
Insert cell
{
let regl = stack.layers.regl;
let ctx = stack.layers.canvas;
let svg = d3.select(stack.layers.svg);
let xScale = d3.scaleLinear()
.domain([-3, 3])
.range([viewport.margin.l, viewport.width - viewport.margin.r])
.clamp(true);
let yScale = constrainLinearScaleAspectRatio(
d3.scaleLinear()
.domain([-1.5, 1.5])
.range([viewport.height - viewport.margin.b, viewport.margin.t])
.clamp(true),
xScale, 1);
let update;
let points = [[1, -1], [1, 1], [-1, -1], [-1, 1]];
let pointsBuffer = regl.buffer(points);
svg.selectAll('g.axes').remove();
let axes = svg.append('g')
.attr('class', 'axes')
.call(viewportAxes(viewport, xScale, yScale))
let g = svg.selectAll('g.handles').remove();
g = svg.selectAll('g.handles')
.data([points])
.enter().append('g')
.attr('class', 'handles');
let handles = g.selectAll('circle')
.data(d => d).enter().append('circle')
.attr('cursor', 'move')
.attr('r', 5)
.attr('stroke-width', 30)
.attr('stroke', 'transparent')
.attr('fill', 'blue')
.call(d3.drag()
.on("drag", (d) => {
d[0] = xScale.invert(d3.event.x);
d[1] = yScale.invert(d3.event.y);
update();
}));
update = function update () {
handles
.attr('cx', data => xScale(data[0]))
.attr('cy', data => yScale(data[1]));
pointsBuffer.subdata(points)
regl.poll();
regl.clear({color: [1, 1, 1, 1]})
configureViewport(viewport, ({viewport3}) => {
configureLinearScales(xScale, yScale, ({view3}) => {
drawStrip({points: pointsBuffer, count: points.length});
})
})
let ctxTransform = mat3multiply(mat3create(),
mat3ViewportFromLinearScales(mat3create(), xScale, yScale),
mat3fromLinearScales(mat3create(), xScale, yScale));

ctx.clearRect(0, 0, stack.width, stack.height);
ctx.beginPath();
ctx.lineWidth = 4;
ctx.strokeStyle = '#3a3';
ctx.moveTo.apply(ctx, vec2transformMat3([], points[1], ctxTransform));
ctx.lineTo.apply(ctx, vec2transformMat3([], points[2], ctxTransform));
ctx.moveTo.apply(ctx, vec2transformMat3([], points[3], ctxTransform));
ctx.lineTo.apply(ctx, vec2transformMat3([], points[0], ctxTransform));
ctx.stroke();
}
update();
yield stack.container
}
Insert cell
configureLinearScales = createReglLinearScaleConfiguration(regl)
Insert cell
regl = stack.layers.regl
Insert cell
height = width * 0.5
Insert cell
configureViewport = createReglViewportConfiguration(regl);
Insert cell
drawStrip = regl({
vert: `
precision highp float;
uniform mat4 view;
attribute vec2 xy;
void main () {
gl_Position = view * vec4(xy, 0, 1);
}`,
frag: `
precision highp float;
void main () {
gl_FragColor = vec4(1.0, 0.8, 0.8, 1);
}`,
attributes: {
xy: regl.prop('points')
},
primitive: 'triangle strip',
depth: {enable: false},
count: regl.prop('count')
});
Insert cell
d3 = require("d3@5")
Insert cell
createREGL = require("regl")
Insert cell
import {mat4create, mat4fromMat3} from '@rreusser/gl-mat4'
Insert cell
import {mat3create, mat3invert, mat3multiply} from '@rreusser/gl-mat3'
Insert cell
import {vec2transformMat3} from '@rreusser/gl-vec2'
Insert cell
/*
// unused
context: {
viewport3: (ctx, props) => {
let dpi = ctx.pixelRatio;
let w = ctx.framebufferWidth / dpi - (props.margin.l + props.margin.r);
let h = ctx.framebufferHeight / dpi - (props.margin.t + props.margin.b);
viewport3[0] = 0.5 * w;
viewport3[1] = 0;
viewport3[2] = 0;

viewport3[3] = 0;
viewport3[4] = -0.5 * h;
viewport3[5] = 0;

viewport3[6] = (0.5 * w + props.margin.l);
viewport3[7] = (0.5 * h + props.margin.t);
viewport3[8] = 1;

return viewport3;
}
}
*/
Insert cell
import {PINNED} from '691ae3b95f02db79@1157'
Insert cell
Insert cell
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