Published
Edited
Oct 28, 2018
6 stars
Insert cell
Insert cell
Insert cell
{
let circle = [NaN, NaN, 0];
const context = DOM.context2d(width, height);
yield context.canvas;

for (let i = 0, n = polygon.length - 1; i < n; ++i) {
const pi0 = polygon[i], pi1 = polygon[i + 1];
for (let j = i + 1; j < n; ++j) {
const pj0 = polygon[j], pj1 = polygon[j + 1];
for (let k = j + 1; k < n; ++k) {
const pk0 = polygon[k], pk1 = polygon[k + 1];
context.clearRect(0, 0, width, height);

const l0 = lineExtend(pi0, pi1);
const l1 = lineExtend(pj0, pj1);
const l2 = lineExtend(pk0, pk1);
context.save();
context.beginPath();
context.moveTo(...l0[0]);
context.lineTo(...l0[1]);
context.moveTo(...l1[0]);
context.lineTo(...l1[1]);
context.moveTo(...l2[0]);
context.lineTo(...l2[1]);
context.setLineDash([3, 3]);
context.strokeStyle = "#ccc";
context.stroke();
context.restore();

context.save();
context.beginPath();
context.moveTo(...polygon[0]);
for (let l = 1; l < n; ++l) context.lineTo(...polygon[l]);
context.closePath();
context.strokeStyle = "#ccc";
context.stroke();
context.restore();

const s0 = lineLineBisect(pi0, pi1, pj1, pj0);
const s1 = lineLineBisect(pj0, pj1, pk1, pk0);
if (!s0 || !s1) continue; // Parallel!

const b0 = lineExtend(...s0);
const b1 = lineExtend(...s1);
context.save();
context.beginPath();
context.moveTo(...b0[0]);
context.lineTo(...b0[1]);
context.moveTo(...b1[0]);
context.lineTo(...b1[1]);
context.strokeStyle = "blue";
context.stroke();
context.restore();

if (circle[2] > 0) {
const [cx, cy, cr] = circle;
context.save();
context.beginPath();
context.moveTo(cx + cr, cy);
context.arc(cx, cy, cr, 0, 2 * Math.PI);
context.globalAlpha = 0.2;
context.fill();
context.restore();
}

const [x, y, r] = circleTangent(pi0, pi1, pj0, pj1, pk0, pk1);

let contained = true;
for (let l = 0; l < n; ++l) {
if (l === i || l === j || l === k) continue;
const d = pointLineDistance([x, y], polygon[l], polygon[l + 1]);
if (d + 1e-6 < r) { contained = false; break; }
}

if (contained && r > circle[2]) circle = [x, y, r];

context.save();
context.beginPath();
context.moveTo(x + r, y);
context.arc(x, y, r, 0, 2 * Math.PI);
context.lineWidth = 1.5;
context.strokeStyle = contained ? "black" : "red";
context.stroke();
context.restore();

context.save();
context.beginPath();
context.moveTo(...pi0);
context.lineTo(...pi1);
context.moveTo(...pj0);
context.lineTo(...pj1);
context.moveTo(...pk0);
context.lineTo(...pk1);
context.lineWidth = 1.5;
context.stroke();
context.restore();

yield Promises.delay(1000, context.canvas);
}
}
}

context.clearRect(0, 0, width, height);

context.save();
context.beginPath();
context.moveTo(...polygon[0]);
for (let l = 1, n = polygon.length - 1; l < n; ++l) context.lineTo(...polygon[l]);
context.closePath();
context.strokeStyle = "#ccc";
context.stroke();
context.restore();

const [cx, cy, cr] = circle;
context.save();
context.beginPath();
context.moveTo(cx + cr, cy);
context.arc(cx, cy, cr, 0, 2 * Math.PI);
context.lineWidth = 1.5;
context.strokeStyle = "#000";
context.stroke();
context.restore();
yield context.canvas;
}
Insert cell
polygon = {
replay;
const points = Array.from({length: 15}, () => [
(Math.random() * 3 + 1) / 5 * width,
(Math.random() * 3 + 1) / 5 * height
]);
const hull = d3.polygonHull(points);
return hull.concat([hull[0]]).reverse();
}
Insert cell
function circleTangent(p0, p1, p2, p3, p4, p5) {
const b0 = lineLineBisect(p0, p1, p3, p2);
const b1 = lineLineBisect(p2, p3, p5, p4);
if (!b0 || !b1) return; // Parallel!
const i = lineLineIntersect(...b0, ...b1);
return [...i, pointLineDistance(i, p0, p1)];
}
Insert cell
function pointLineDistance([x0, y0], [x2, y2], [x1, y1]) {
const x21 = x2 - x1, y21 = y2 - y1;
return (y21 * x0 - x21 * y0 + x2 * y1 - y2 * x1) / Math.sqrt(y21 * y21 + x21 * x21);
}
Insert cell
function lineLineBisect([x0, y0], [x1, y1], [x2, y2], [x3, y3]) {
const x02 = x0 - x2, y02 = y0 - y2;
const x10 = x1 - x0, y10 = y1 - y0;
const x32 = x3 - x2, y32 = y3 - y2;
const l = y32 * x10 - x32 * y10;
if (Math.abs(l) < 1e-3) return; // Parallel!
const l10 = Math.sqrt(x10 ** 2 + y10 ** 2);
const l32 = Math.sqrt(x32 ** 2 + y32 ** 2);
const ti = (x32 * y02 - y32 * x02) / l;
const xi = x0 + ti * x10, yi = y0 + ti * y10;
return [[xi, yi], [xi + x10 / l10 + x32 / l32, yi + y10 / l10 + y32 / l32]];
}
Insert cell
function lineLineIntersect([x0, y0], [x1, y1], [x2, y2], [x3, y3]) {
const x02 = x0 - x2, y02 = y0 - y2;
const x10 = x1 - x0, y10 = y1 - y0;
const x32 = x3 - x2, y32 = y3 - y2;
const t = (x32 * y02 - y32 * x02) / (y32 * x10 - x32 * y10);
return [x0 + t * x10, y0 + t * y10];
}
Insert cell
function lineExtend([x0, y0], [x1, y1]) {
return [
rayExtend([x0, y0], [x1 - x0, y1 - y0]),
rayExtend([x1, y1], [x0 - x1, y0 - y1])
];
}
Insert cell
function rayExtend([x0, y0], [vx, vy]) {
let t = Infinity, c, x, y;
if (vy < 0) { // top
if ((c = (0 - y0) / vy) < t) y = 0, x = x0 + (t = c) * vx;
} else if (vy > 0) { // bottom
if ((c = (height - y0) / vy) < t) y = height, x = x0 + (t = c) * vx;
}
if (vx > 0) { // right
if ((c = (width - x0) / vx) < t) x = width, y = y0 + (t = c) * vy;
} else if (vx < 0) { // left
if ((c = (0 - x0) / vx) < t) x = 0, y = y0 + (t = c) * vy;
}
return [x, y];
}
Insert cell
height = 600
Insert cell
d3 = require("d3-polygon@1")
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