Public
Edited
Feb 10, 2024
1 star
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
function polygonDemo(ctx, points, change) {
const canvas = ctx.canvas,
width = canvas.width,
height = canvas.height;

let selected = -1;
let moved = false;
let mouse;
change(ctx, points, -1);

// Disable context menu
canvas.oncontextmenu = () => {
return false;
};

canvas.onmousedown = function (event) {
mouse = [event.offsetX, event.offsetY];
selected = -1;
let i = 0;
for (let p of points) {
if (vec2.dist(mouse, p) < 8) {
selected = i;
break;
}
i++;
}
if (selected < 0) {
// new point
const n = points.length;
if (n >= 2) {
// Find closest line segment where to insert
let minDist = Infinity;
let closest = -1;
for (let i = 1; i < n; i++) {
const p = points[(i - 1 + n) % n];
const q = points[i];
const d = vec2.distSegment(mouse, p, q);
if (d < minDist) {
[minDist, closest] = [d, i];
}
}
if (vec2.dist(mouse, points[0]) <= minDist) points.unshift(mouse);
else if (vec2.dist(mouse, points[n - 1]) <= minDist) points.push(mouse);
else points.splice(closest, 0, mouse);
change(ctx, points, closest);
} else {
points.push(mouse);
change(ctx, points, n);
}
} else {
moved = false;
}
};
canvas.onmousemove = function (event) {
moved = true;
if (selected < 0) return;
let newMouse = [event.offsetX, event.offsetY];
let v = vec2.sub([], newMouse, mouse);
vec2.add(points[selected], points[selected], v);
mouse = newMouse;
change(ctx, points, selected);
};
canvas.onmouseup = function (event) {
if (!moved && selected != -1 && points.length > 1) {
// click on point
points.splice(selected, 1);
change(ctx, points, selected);
}
selected = -1;
};
return canvas;
}
Insert cell
function arcLengthParameterize(points) {
let accum = [0];
let total = 0;
for (let i = 1; i < points.length; i++) {
total += vec2.dist(points[i - 1], points[i]);
accum[i] = total;
}
return (t) => {
t *= total;
let i = 0;
let j = points.length;
let maxiter = 100;
while (j - i > 1 && maxiter--) {
let k = (i + j) >> 1;
if (accum[k] > t) j = k;
else i = k;
}
if (maxiter == 0) throw "Inf loop";
if (i + 1 >= points.length) return points[points.length - 1];
let alpha = (t - accum[i]) / (accum[i + 1] - accum[i]);
return vec2.lerp([], points[i], points[i + 1], alpha);
};
}
Insert cell
function paletteFromCurve(options = {}) {
const {
width = 800,
height = 600,
image = null,
ncolors = 5,
cornerColors = ["red", "blue", "green", "white"]
} = options;
const ctx = DOM.context2d(width, height, 1);
const canvas = ctx.canvas;
if (image) {
ctx.drawImage(image, 0, 0, width, height);
} else {
const interpolateTop = d3.interpolateHsl(cornerColors[0], cornerColors[1]);
const interpolateBottom = d3.interpolateHsl(
cornerColors[2],
cornerColors[3]
);
for (let x = 0; x < width; x++) {
const c0 = interpolateTop(x / width);
const c1 = interpolateBottom(x / width);
const gradient = ctx.createLinearGradient(x, 0, x, height);
gradient.addColorStop(0, c0);
gradient.addColorStop(1, c1);
ctx.fillStyle = gradient;
ctx.fillRect(x, 0, x + 1, height);
}
}
let imgData = ctx.getImageData(0, 0, width, height);

let points = [
[width * 0.1, height * 0.1],
[width * 0.6, height * 0.4],
[width * 0.9, height * 0.9]
];

let container = htl.html`<div>`;
container.append(canvas);
container.value = null;

let change = (ctx, pts) => {
ctx.putImageData(imgData, 0, 0);
for (let p of pts) {
ctx.fillStyle = "white";
ctx.beginPath();
ctx.arc(...p, 4, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = "black";
ctx.beginPath();
ctx.arc(...p, 3, 0, Math.PI * 2);
ctx.fill();
}
ctx.strokeStyle = "black";
ctx.setLineDash([8, 8]);
ctx.lineWidth = 1;
ctx.beginPath();

for (let i = 0; i < 5; i++) {
pts = fourPoint(pts, false);
}
for (let p of pts) ctx.lineTo(...p);
ctx.stroke();

let curveLookup = arcLengthParameterize(pts);
let delta = 1 / (ncolors - 1);
let palettePoints = [];
for (let i = 0; i < ncolors; i++)
palettePoints.push(curveLookup(i * delta));
ctx.setLineDash([]);
const palette = [];
for (let [x, y] of palettePoints) {
ctx.strokeStyle = "white";
ctx.lineWidth = 2;
ctx.beginPath();
ctx.arc(x, y, 8, 0, Math.PI * 2);
ctx.stroke();
const index = (Math.round(x) + Math.round(y) * width) * 4;
let [r, g, b] = imgData.data.slice(index, index + 3);
palette.push(`rgb(${r},${g},${b})`);
}
container.value = palette;
container.dispatchEvent(new Event("input", { bubbles: true }));
};

polygonDemo(ctx, points, change);

return container;
}
Insert cell
function paletteDisplay(palette, options = {}) {
const { width = 300, height = 40 } = options;
const div = htl.html`<div>`;
Object.assign(div.style, { display: "inline-block" });
const n = palette.length;
const w = width / n;
const r = `${Math.min(w, height) / 2}px`;
for (let i = 0; i < n; i++) {
const color = palette[i];
const sample = htl.html`<div>`;
Object.assign(sample.style, {
borderRadius:
i == 0 ? `${r} 0 0 ${r}` : i == n - 1 ? `0 ${r} ${r} 0` : "0",
width: `${width / palette.length}px`,
height: `${height}px`,
display: "inline-block",
background: color
});
div.append(sample);
}
return div;
}
Insert cell
mutable image = null
Insert cell
{
mutable image = await imgfile.image();
}
Insert cell
function lagrange([x0, y0], [x1, y1], [x2, y2]) {
const l0 = (x) => ((x - 1) * (x - 2)) / 2;
const l1 = (x) => ((x - 0) * (x - 2)) / -1;
const l2 = (x) => ((x - 0) * (x - 1)) / 2;
return (t) => {
let [a, b, c] = [l0(t * 2), l1(t * 2), l2(t * 2)];
return [x0 * a + x1 * b + x2 * c, y0 * a + y1 * b + y2 * c];
};
}
Insert cell
import { vec2, lr, lr4, fourPoint } from "@esperanc/lane-riesenfeld-subdivision"
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