Public
Edited
Mar 8, 2023
2 forks
Importers
15 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
draw = {
let ctx = canvas.getContext("2d");
ctx.fillStyle = "lightgray";
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "black";

for (let p of points) {
ctx.beginPath();
ctx.fillStyle = p.notsmooth ? "green" : "black";
ctx.arc(p.x, p.y, 5, 0, Math.PI * 2);
ctx.fill();
}

let n = points.length;
if (n > 1) {
if (show.indexOf("o") >= 0) {
ctx.strokeStyle = "#222";
ctx.lineWidth = 0.7;
ctx.beginPath();
let p = points[n - 1];
ctx.moveTo(p.x, p.y);
for (let p of points) ctx.lineTo(p.x, p.y);
ctx.stroke();
}
let m = controlPoints.length;
if (show.indexOf("c") >= 0) {
ctx.strokeStyle = "blue";
ctx.lineWidth = 1;
ctx.beginPath();
let a = controlPoints[1];
ctx.moveTo(a.x, a.y);
for (let i = 0; i < m; i += 3) {
let b = controlPoints[(i + 2) % m];
let c = controlPoints[(i + 3) % m];
let d = controlPoints[(i + 4) % m];
ctx.lineTo(b.x, b.y);
ctx.lineTo(c.x, c.y);
ctx.lineTo(d.x, d.y);
}
ctx.stroke();
}
if (show.includes("s")) {
ctx.strokeStyle = "green";
ctx.fillStyle = "#0F05";
ctx.beginPath();
for (let p of subdiv) ctx.lineTo(p.x, p.y);
ctx.closePath();
ctx.stroke();
ctx.fill();
}
if (show.indexOf("b") >= 0) {
ctx.lineWidth = 1.5;
ctx.strokeStyle = "red";
ctx.fillStyle = "#F005";
ctx.beginPath();
let a = controlPoints[1];
ctx.moveTo(a.x, a.y);
for (let i = 0; i < m; i += 3) {
let b = controlPoints[(i + 2) % m];
let c = controlPoints[(i + 3) % m];
let d = controlPoints[(i + 4) % m];
ctx.bezierCurveTo(b.x, b.y, c.x, c.y, d.x, d.y);
}
ctx.stroke();
ctx.fill();
}
}
}
Insert cell
interaction = {
let selected = -1;
let moved = false;
let mouse;
canvas.oncontextmenu = () => {
return false;
}; // Disable context menu
canvas.onmousedown = function (event) {
mouse = Vec(event.offsetX, event.offsetY);
mouse.notsmooth = false;
selected = -1;
let i = 0;
for (let p of mutable points) {
if (p.dist(mouse) < 8) {
selected = i;
break;
}
i++;
}
if (selected < 0) {
// new point
const pts = mutable points;
const n = pts.length;
if (n > 2) {
// Find closest line segment where to insert
let minDist = Number.POSITIVE_INFINITY;
let closest = -1;
for (let i = 0; i < n; i++) {
const p = pts[(i - 1 + n) % n];
const q = pts[i];
const d = mouse.distSegment(p, q);
if (d < minDist) {
[minDist, closest] = [d, i];
}
}
mutable points = [
...pts.slice(0, closest),
mouse,
...pts.slice(closest)
];
} else {
mutable points = mutable points.concat([mouse]);
}
} else {
moved = false;
}
};
canvas.onmousemove = function (event) {
moved = true;
if (selected < 0) return;
let newMouse = Vec(event.offsetX, event.offsetY);
let { notsmooth } = mutable points[selected];
mutable points[selected] = mutable points[selected].add(
newMouse.sub(mouse)
);
mutable points[selected].notsmooth = notsmooth;
mutable points = mutable points;
mouse = newMouse;
};
canvas.onmouseup = function (event) {
if (!moved && selected != -1) {
// click on point
if (event.button != 0) {
mutable points.splice(selected, 1);
} else {
mutable points[selected].notsmooth = !mutable points[selected]
.notsmooth;
}
}
selected = -1;
mutable points = mutable points;
};
}
Insert cell
Insert cell
Insert cell
Insert cell
subdiv = {
let curve = new Curve();
for (let p of points) {
curve.push(p);
if (p.notsmooth) curve.push(p);
}
for (let i = 0; i < 4; i++) curve = curve.chaikin(true);
return curve;
}
Insert cell
function smoothControlPoints(points, edgeRatio = 0.333) {
let cp = [];
let n = points.length;
if (n > 1) {
let eprev = points[0].sub(points[n - 1]);
let lenprev = eprev.mag();
eprev = eprev.scale(1 / lenprev);
for (let i = 0; i < n; i++) {
let inext = (i + 1) % n;
let enext = points[inext].sub(points[i]);
let lennext = enext.mag();
enext = enext.scale(1 / lennext);
let tangent = eprev.add(enext).normalize();
if (tangent.dot(enext) < 0) tangent = Vec(0, 0).sub(tangent);
if (points[i].notsmooth) {
cp.push(points[i], points[i], points[i]);
} else {
cp.push(
points[i].sub(tangent.scale(lenprev * edgeRatio)),
points[i],
points[i].add(tangent.scale(lennext * edgeRatio))
);
}
eprev = enext;
lenprev = lennext;
}
}
return cp;
}
Insert cell
Insert cell
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