{
const context = DOM.context2d(width, width - width / 5);
context.textAlign = "center";
const offset = 20;
const start = [offset, width / 2 + 100];
const end = [width - offset, width / 2 + 100];
line(start, end, context);
const connected = {};
for (let i = 0; i < points.length; ++i) {
connected[i] = [];
}
for (let i = 0; i < points.length; ++i) {
const [nom, denom] = points[i];
const val = nom / denom;
const p = pointOnLine([start, end], val);
circle([...p, 2], context);
if (n < 20) {
if (val === 0 || val === 1) {
context.fillText(nom, p[0], p[1] + 15);
} else {
context.fillText(nom, p[0], p[1] + 15);
context.fillText("-", p[0], p[1] + 22);
context.fillText(denom, p[0], p[1] + 28);
}
}
for (let j = 0; j < points.length; ++j) {
const [otherNom, otherDenom] = points[j];
const otherVal = otherNom / otherDenom;
const criteria = otherNom * denom - otherDenom * nom;
if (Math.abs(criteria) === 1 && !connected[j].includes(i)) {
const other = pointOnLine([start, end], otherVal);
const half = (otherVal + nom / denom) / 2;
const halfPoint = pointOnLine([start, end], half);
context.beginPath();
context.arc(...halfPoint, dist2D(p, other) / 2, 0, Math.PI, 1);
context.stroke();
connected[i].push(j);
}
}
}
return context.canvas;
}