Published
Edited
Oct 15, 2019
2 forks
9 stars
Insert cell
Insert cell
{
const lines = [
[[430, 180], [500, 170]],
[[400, 300], [410, 200]],
[[500, 340], [420, 320]]
].map(line => line.map(([x, y]) => {
return [x / 954 * width, y];
}));

const svg = d3.select(DOM.svg(width, height));

const line = svg.append("g")
.attr("stroke", "#ccc")
.selectAll("line")
.data(lines)
.enter().append("line");

const lineSegment = svg.append("g")
.attr("stroke", "black")
.attr("stroke-width", 1.5)
.selectAll("line")
.data(lines)
.enter().append("line");

const lineBisect = svg.append("g")
.selectAll("line")
.data([
[lines[0], lines[1], +1],
[lines[0], lines[2], +1],
[lines[1], lines[2], +1],
[lines[0], lines[1], -1],
[lines[0], lines[2], -1],
[lines[1], lines[2], -1]
])
.enter().append("line")
.attr("stroke", ([l0, l1, k]) => k > 0 ? "red" : "blue");
const circle = svg.append("g")
.attr("fill", "none")
.attr("stroke", "black")
.attr("stroke-width", 1.5)
.selectAll("circle")
.data([
[+1, +1],
[-1, +1],
[-1, -1],
[+1, -1]
])
.enter().append("circle");

const point = svg.append("g")
.attr("cursor", "move")
.attr("pointer-events", "all")
.attr("stroke", "transparent")
.attr("stroke-width", 30)
.selectAll("circle")
.data(d3.merge(lines))
.enter().append("circle")
.attr("r", 2.5)
.call(d3.drag()
.subject(([x, y]) => ({x, y}))
.on("drag", dragged));

update();
function dragged(d) {
d[0] = d3.event.x;
d[1] = d3.event.y;
update();
}
function update() {
point
.attr("cx", d => d[0])
.attr("cy", d => d[1]);
line.data(lines.map(l => lineExtend(...l)))
.attr("x1", d => d[0][0])
.attr("y1", d => d[0][1])
.attr("x2", d => d[1][0])
.attr("y2", d => d[1][1]);

lineSegment
.attr("x1", d => d[0][0])
.attr("y1", d => d[0][1])
.attr("x2", d => d[1][0])
.attr("y2", d => d[1][1]);

lineBisect.each(function([l0, l1, k]) {
const b01 = lineLineBisect(...l0, ...l1, k);
if (!b01) return;
const b = lineExtend(...b01);
d3.select(this)
.attr("x1", b[0][0])
.attr("y1", b[0][1])
.attr("x2", b[1][0])
.attr("y2", b[1][1]);
});

circle.each(function([k0, k1]) {
const b01 = lineLineBisect(...lines[0], ...lines[1], k0);
const b12 = lineLineBisect(...lines[1], ...lines[2], k1);
if (!b01 || !b12) return;
const b0 = lineExtend(...b01);
const b1 = lineExtend(...b12);
const [x, y] = lineLineIntersect(...b0, ...b1);
const r = Math.abs(pointLineDistance([x, y], ...lines[0]));
d3.select(this)
.attr("cx", x)
.attr("cy", y)
.attr("r", r);
});
}

return svg.node();
}
Insert cell
function pointLineDistance([x0, y0], [x2, y2], [x1, y1]) {
const x21 = x2 - x1, y21 = y2 - y1, l21 = Math.sqrt(y21 ** 2 + x21 ** 2);
return (y21 * x0 - x21 * y0 + x2 * y1 - y2 * x1) / l21;
}
Insert cell
function lineLineBisect([x0, y0], [x1, y1], [x2, y2], [x3, y3], k = 1) {
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 + k * x32 / l32, yi + y10 / l10 + k * 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@5")
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