Public
Edited
Jan 10
2 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
function draw(c0, c1, c2, c3, opts={}) {
let {
bounds = 'auto',
interactive = true,
show_curvatures = true,
show_more = true,
show_regions = false,
n = 0
} = opts;
let { d3Canvas, context, canvas_width, canvas_height } = createCanvas(opts);
// Set the size
context.clearRect(0, 0, canvas_width, canvas_height);
let R = -1.1 / Number(c0[2]);
let xcenter = 0, ycenter = 0;

// Set the view rectangle - either automatically
// or based on specified bounds
if (bounds == 'set') {
xcenter = opts.xcenter;
ycenter = opts.ycenter;
R = opts.R;
for (let circle of [c0, c1, c2, c3]) {
circle[0] -= xcenter;
circle[1] -= ycenter;
}
}
// Set scale functions and path generator
let x_scale = d3
.scaleLinear()
.domain([-R, R])
.range([0, canvas_width]);
let y_scale = d3
.scaleLinear()
.domain([R, -R])
.range([0, canvas_height]);
let r_scale = d3
.scaleLinear()
.domain([0, R])
.range([0, canvas_width / 2]);
let path = d3
.line()
.x(d => x_scale(d[0]))
.y(d => y_scale(d[1]));

// This option is used to generate an explanatory image on zooming
// later in this notebook.
if (show_regions) {
context.beginPath();
let r = -1 / Number(c0[2]);
context.moveTo(x_scale(r), y_scale(0));
context.arc(x_scale(0), y_scale(0), r_scale(r), 0, 2 * Math.PI);
context.fillStyle = '#dddd00';
context.fill();

let [v1, v2, v3] = inner_triangle(c1, c2, c3);
let scaling_xy = (v) => [x_scale(v[0]), y_scale(v[1])]
let [x1, y1] = scaling_xy(v1);
let [x2, y2] = scaling_xy(v2);
let [x3, y3] = scaling_xy(v3);
context.beginPath();
context.moveTo(x1, y1);
context.lineTo(x2, y2);
context.lineTo(x3, y3);
context.closePath();
context.fillStyle = '#0000dd';
context.fill();
}

// Generate the gasket and store
let all_circles = show_more ? gasket(c0, c1, c2, c3, R) : [c0, c1, c2, c3];

let divisor = 2n;
let to_text = (k) => {
if (!factor_out) return String(k);
let i = 0n;
while (k % divisor == 0n) { i++; k = k / divisor;}
if (i == 0n) return String(k);
let power = i == 1n ? '' : String(i).split('').map(d => '⁰¹²³⁴⁵⁶⁷⁸⁹'.split('')[d]).join('');
if (k == 1n) return String(divisor) + power;
return String(divisor) + power + '·' + k;
}
let to_textColor = (k) => {
if (isPrimeMemo(k)) {
return 'red';
} else if (factor_out) {
while (k % divisor == 0n) k = k / divisor;
if (isPrimeMemo(k)) return 'blue';
}
return 'black';
}
// Draw most of the circles
drawCircles(context, all_circles.slice(4), x_scale, y_scale, r_scale,
'transparent', show_curvatures, to_text, to_textColor);

// Draw and shade the first three interior circles
drawCircles(context, [c1, c2, c3], x_scale, y_scale, r_scale,
'#dddddd', show_curvatures, to_text, to_textColor);

// Draw the exterior circle
drawCircles(context, [c0], x_scale, y_scale, r_scale,
'transparent', false);
if (show_curvatures) {
let [x, y, k] = c0;
let r = 1 / Number(-k);
context.font = `50px Times New Roman`;
context.fillStyle = to_textColor(-k);
context.fillText(k, x_scale(x-0.85*r), y_scale(y+0.85*r));
context.stroke();
context.closePath();
}

// Set up the zooming behavior
if (interactive) {
d3Canvas.call(
d3
.zoom()
.scaleExtent([0, 8])
// On zoom, we'll simply draw the circles that have already been generated
.on('zoom', function() {
context.save();
context.clearRect(0, 0, canvas_width, canvas_height);
context.beginPath();
// drawCircles(context, d3.event.transform.apply(all_circles), x_scale, y_scale, r_scale,
// 'transparent', false);
for (const [x, y, k] of all_circles) {
context.beginPath();
const r = Math.abs(1 / Number(k));
let [xxr, yyr] = d3.event.transform.apply([
x_scale(x + r),
y_scale(y)
]);
context.moveTo(xxr, yyr);
let [xx, yy] = d3.event.transform.apply([x_scale(x), y_scale(y)]);
context.arc(xx, yy, xxr - xx, 0, 2 * Math.PI);
context.stroke();
context.closePath();
}
for (let [x, y, k] of [c1, c2, c3]) {
context.beginPath();
const r = Math.abs(1 / Number(k));
let [xxr, yyr] = d3.event.transform.apply([
x_scale(x + r),
y_scale(y)
]);
context.moveTo(xxr, yyr);
let [xx, yy] = d3.event.transform.apply([x_scale(x), y_scale(y)]);
context.arc(xx, yy, xxr - xx, 0, 2 * Math.PI);
context.fillStyle = '#dddddd';
context.fill();
context.stroke();
context.closePath();
}
context.restore();
})
// On zoom-end, we'll refine, regenerate, and redraw
.on('end', function() {
// Reset the window based on the transform.
let [new_xmin, new_ymin] = d3.event.transform.invert([0, canvas_height]);
let [new_xmax, new_ymax] = d3.event.transform.invert([canvas_width, 0]);
new_xmin = x_scale.invert(new_xmin);
new_ymin = y_scale.invert(new_ymin);
new_xmax = x_scale.invert(new_xmax);
new_ymax = new_ymin + new_xmax - new_xmin;

// Draw to the existing canvas using the new view window
draw(c0, c1, c2, c3, {
canvas: d3Canvas.node(),
xcenter: (new_xmax + new_xmin) / 2,
ycenter: (new_ymax + new_ymin) / 2,
R: (new_xmax - new_xmin) / 2,
bounds: 'set'
});

// Reset the canvas transform since drawing is based on
// the window we just computed.
d3.zoom().transform(d3Canvas, d3.zoomIdentity);
})
);
}

return d3Canvas.node();
}
Insert cell
function createCanvas(opts) {
let canvas_width, canvas_height, d3Canvas, context;

let {
canvas = 'new',
bounds = 'auto',
interactive = true,
show_curvatures = true,
show_more = true,
show_regions = false,
n = 0
} = opts;

if (canvas == 'new') {
canvas_width = width <= 720 ? width : 0.7 * width
canvas_height = canvas_width;
d3Canvas = d3
.create('canvas')
.attr('width', canvas_width)
.attr('height', canvas_height);
context = d3Canvas.node().getContext('2d');
context.textAlign = "center";
context.textBaseline = 'middle';
}
// Redraw to the canvas when called later.
else {
d3Canvas = d3.select(opts.canvas);
canvas_width = opts.canvas.width;
canvas_height = opts.canvas.height;
context = opts.canvas.getContext('2d');
}

return { d3Canvas, context, canvas_width, canvas_height };
}
Insert cell
function drawCircles(context, circles, x_scale, y_scale, r_scale, fillStyle, show_curvatures, to_text, to_textStyle) {
for (let [x, y, k] of circles) {
context.beginPath();
k = k < 0n ? -k : k;
let r = 1 / Number(k);
context.moveTo(x_scale(x + r), y_scale(y));
context.arc(x_scale(x), y_scale(y), r_scale(r), 0, 2 * Math.PI);
context.fillStyle = fillStyle;
context.fill();
context.stroke();
if (show_curvatures) {
let s = r_scale(r / 2);
context.font = `${s}px Times New Roman`;
context.fillStyle = to_textStyle(k);
context.fillText(to_text(k), x_scale(x), y_scale(y));
context.closePath();
}
}
}
Insert cell
Insert cell
function gasket(c0, c1, c2, c3, R) {
// A lot like Mike's function
let Bound = Math.round(1000 / R);
return [
c0,
c1,
c2,
c3,
...circles(c1, c2, c3, c0, Bound, R),
...circles(c2, c3, c0, c1, Bound, R),
...circles(c3, c0, c1, c2, Bound, R),
...circles(c0, c1, c2, c3, Bound, R)
];
}
Insert cell
function* circles(c0, c1, c2, c3, Bound, R) {
// This function has substantial additions
const c = circle(c0, c1, c2, c3);
// If the curvature of c is too large,
// then we can stop.
if (c[2] > Bound) {
return;
}

let rect = [[-R, -R], [R, -R], [R, R], [-R, R]];
// We now determine whether our viewing rectangle
// intersects the region in which c lies. If not
// we can stop.
let yield_more = false;
if (c0[2] > 0 && c1[2] > 0 && c2[2] > 0) {
let T = inner_triangle(c0, c1, c2);
yield_more = intersect(T, rect);
} else if (c0[2] < 0) {
yield_more = some_sameSide(c1, c2, c, rect);
} else if (c1[2] < 0) {
yield_more = some_sameSide(c0, c2, c, rect);
} else if (c2[2] < 0) {
yield_more = some_sameSide(c0, c1, c, rect);
}
if (yield_more) {
yield c;
yield* circles(c0, c1, c, c2, Bound, R);
yield* circles(c1, c, c2, c0, Bound, R);
yield* circles(c2, c, c0, c1, Bound, R);
}
}
Insert cell
function circle([x0, y0, b0], [x1, y1, b1], [x2, y2, b2], [x3, y3, b3]) {
// Exactly Mike's function. Looks like it's based on Descartes' theorem
const b = 2n * (b0 + b1 + b2) - b3;
return [
(2 * (Number(b0) * x0 + Number(b1) * x1 + Number(b2) * x2) - Number(b3) * x3) / Number(b),
(2 * (Number(b0) * y0 + Number(b1) * y1 + Number(b2) * y2) - Number(b3) * y3) / Number(b),
b
];
}
Insert cell
isPrimeMemo = {
const memo = new Map();
return N => {
if (memo.has(N)) return memo.get(N);
if (N <= 1) return false;
for (let i = 2n; i * i <= N; i++) {
if (N % i === 0n) {
memo.set(N, false);
return false;
}
}
memo.set(N, true);
return true;
};
}
Insert cell
Insert cell
Insert cell
function tangency(c1, c2) {
let [x1, y1, k1] = c1;
let [x2, y2, k2] = c2;
let r1 = 1 / Number(k1);
let r2 = 1 / Number(k2);
let d = r1 + r2;
let x = (x1 * r2 + x2 * r1) / d;
let y = (y1 * r2 + y2 * r1) / d;
return [x, y];
}
Insert cell
function inner_triangle(c1, c2, c3) {
let p = tangency(c1, c2);
let q = tangency(c2, c3);
let r = tangency(c3, c1);
return [p, q, r];
}
Insert cell
function intersect(Tverts, Rverts) {
let T = new Flatten.Polygon(Tverts);
let R = new Flatten.Polygon(Rverts);

// Edges might intersect
if (T.intersect(R).length > 0) return true;

// R might fully contain T
let Tvs = T.vertices;
for (let i = 0; i < Tvs.length; i++) {
if (R.contains(Tvs[i])) return true;
}

// T might fully contain R
let Rvs = R.vertices;
for (let i = 0; i < Rvs.length; i++) {
if (T.contains(Rvs[i])) return true;
}

// Or they might not intersect at all
return false;
}
Insert cell
function some_sameSide([x0, y0], [x1, y1], [x, y], rect) {
for (let i = 0; i < rect.length; i++) {
if (sameSide([x0, y0], [x1, y1], [x, y], rect[i])) {
return true;
}
}
return false;
}
Insert cell
function sameSide([x0, y0], [x1, y1], [a, b], [c, d]) {
let ab = leftOf([x0, y0], [x1, y1], [a, b]);
let cd = leftOf([x0, y0], [x1, y1], [c, d]);
if ((ab && cd) || (!ab && !cd)) {
return true;
} else {
return false;
}
}
Insert cell
function leftOf([x0, y0], [x1, y1], [x, y]) {
return x1 * (y - y0) + x * (y0 - y1) + x0 * (y1 - y) > 0;
}
Insert cell
Insert cell
aps = [
[[0, 0, -1n], [-1/2, 0, 2n], [1/2, 0, 2n], [0, -2/3, 3n]],
[[0, 0, -2n], [-1/6, 0, 3n], [1/3, 0, 6n], [3/14, -2/7, 7n]],
[[0, 0, -3n], [-2/15, 0, 5n], [1/6, -1/8, 8n], [1/6, 1/8, 8n]],
[[0, 0, -6n], [-5/66, 0, 11n], [8/105, -2/35, 14n], [3/50, 4/50, 15n]],
[
[0, 0, -13n],
[-10/299, 0, 23n],
[14/325, -2/300, 30n],
[22/1235, 9/190, 38n]
],
[
[0, 0, -76n],
[-1/5852, 0, 77n],
[1/77, 0, 5852n],
[2000/154053, -2/5853, 5853n]
]
].map(x=>{ })
Insert cell
Insert cell
Flatten = require('@flatten-js/core')
Insert cell
import {QQ} from '@liuyao12/rational-real-p-adic'
Insert cell
d3 = require('d3@5')
Insert cell
html`<link rel="stylesheet" href="https://unpkg.com/purecss@1.0.0/build/pure-min.css">
<link rel="stylesheet" href="https://unpkg.com/purecss@1.0.0/build/grids-responsive-min.css">`
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