Public
Edited
Aug 2, 2023
Insert cell
Insert cell
Insert cell
polygonDemo(points, {
change: function (ctx, points) {
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.strokeStyle = "black";
ctx.setLineDash([8, 8]);
ctx.beginPath();
for (let p of points) {
ctx.lineTo(...p);
}
ctx.closePath();
ctx.stroke();
let pts = points;
let f =
config.scheme == "LR"
? (pts) => lr(pts, config.degree)
: config.scheme == "4 points"
? (pts) => fourPoint(pts)
: (pts) => lr4(pts, config.degree);
for (let i = 0; i < config.subdiv; i++) pts = f(pts);
ctx.setLineDash([]);
ctx.strokeStyle = "blue";
ctx.fillStyle = "rgba(0,0,255,0.2)";
ctx.beginPath();
for (let p of pts) {
ctx.lineTo(...p);
}
ctx.closePath();
ctx.fill();
ctx.stroke();
ctx.fillStyle = "black";
for (let p of points) {
ctx.beginPath();
ctx.arc(...p, 4, 0, Math.PI * 2);
ctx.fill();
}
}
})
Insert cell
points = [
[100, 100],
[400, 100],
[400, 400],
[250, 250],
[100, 400]
]
Insert cell
//
// Canvas and interaction code for editing a polygon
//
function polygonDemo(points, options = {}) {
const {
width = 800,
height = 600,
change = (ctx, points, i) => {},
doubleClick = (ctx, points, i) => {}
} = options;

const canvas = htl.html`<canvas width=${width} height=${height}>`;
const ctx = canvas.getContext("2d");
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 = 0; 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];
}
}
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
//
// Lane Riesenfeld subdivision step
//
function lr(points, degree = 2, closed = true) {
// duplicate
let v = [];
if (!closed) for (let i = 1; i < degree; i++) v.push(points[0]);
for (let p of points) v.push(p, p);
const n = v.length;
const next = closed ? (i) => (i + 1) % n : (i) => Math.min(n - 1, i + 1);
// average
for (let d = 1; d <= degree; d++) {
let u = [];
for (let i = 0; i < n; i++) {
let p = v[i];
let q = v[next(i)];
u.push([(p[0] + q[0]) / 2, (p[1] + q[1]) / 2]);
}
v = u;
}
return v;
}
Insert cell
//
// Lane-Riesenfeld subdivision variant with 4-point refinement
//
function lr4(points, degree = 2, closed = true, w = config.w) {
// refine
let v;
if (closed) {
v = points;
} else {
let [first, last] = [points[0], points[points.length - 1]];
let [prefix, suffix] = [[], []];
for (let i = 0; i < degree; i++) {
prefix.push(first);
suffix.push(last);
}
v = [...prefix, ...points, ...suffix];
}
v = fourPoint(v, closed, w);
// average
let n = v.length;
const next = closed ? (i) => (i + 1) % n : (i) => Math.min(n - 1, i + 1);
const prev = closed ? (i) => (i + n - 1) % n : (i) => Math.max(0, i - 1);
for (let d = 1; d < degree; d++) {
let u = [];
for (let i = 0; i < n; i++) {
const p = [v[prev(i)], v[i], v[next(i)], v[next(next(i))]];
let q = [0, 0];
for (let j of [0, 1])
q[j] =
-w * p[0][j] +
(0.5 + w) * p[1][j] +
(0.5 + w) * p[2][j] -
w * p[3][j];
u.push(q);
}
v = u;
}
return v;
}
Insert cell
//
// Four point subdivision (Dyn-Levin-Gregory)
//
function fourPoint(points, closed = true, w = config.w) {
// refine
let n = points.length;
const next = closed ? (i) => (i + 1) % n : (i) => Math.min(n - 1, i + 1);
const prev = closed ? (i) => (i + n - 1) % n : (i) => Math.max(0, i - 1);
let v = [];
for (let i = 0; i < n; i++) {
const p = [
points[prev(i)],
points[i],
points[next(i)],
points[next(next(i))]
];
let q = [0, 0];
for (let j of [0, 1])
q[j] =
-w * p[0][j] + (0.5 + w) * p[1][j] + (0.5 + w) * p[2][j] - w * p[3][j];
v.push(points[i], q);
}
return v;
}
Insert cell
import { vec2 } from "@esperanc/vec2-utils"
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