Creator: Petar Veličković (original)
A directed web graph whose nodes are documents and edges are hyperlinks, including self-links, the structure underlying the PageRank algorithm.
#import "@preview/cetz:0.5.3": canvas, draw
#import draw: bezier, circle, content, line
#set page(width: auto, height: auto, margin: 8pt, fill: none)
#set text(size: 12pt)
#canvas({
let radius = .42
let (dx, dy) = (2.3, 1.85)
let nodes = (
d0: (0, 0),
d1: (0, -dy),
d2: (dx, 0),
d3: (2 * dx, 0),
d4: (3 * dx, -dy),
d5: (3 * dx, dy),
d6: (3 * dx, 0),
)
for (idx, (name, pos)) in nodes.pairs().enumerate() {
circle(pos, radius: radius, fill: rgb("#cdd3da").transparentize(40%), stroke: 1.1pt, name: name)
content(name, $d_#idx$)
}
let arrow = (mark: (end: "stealth", fill: black, scale: .7), stroke: black + 1.4pt)
// bezier doesn't clip at node borders like line does, so start/end curved
// edges on the node rim in the direction of the control point
let rim(name, toward) = {
let (cx, cy) = nodes.at(name)
let (tx, ty) = toward
let len = calc.sqrt((tx - cx) * (tx - cx) + (ty - cy) * (ty - cy))
(cx + (tx - cx) / len * radius, cy + (ty - cy) / len * radius)
}
let curved(from, to, ctrl) = bezier(rim(from, ctrl), rim(to, ctrl), ctrl, ..arrow)
// self loop drawn as a bezier starting/ending on the node boundary
let self-loop(name, angle) = {
let center = nodes.at(name)
let endpoint(offset, dist) = (
center.at(0) + dist * radius * calc.cos(angle + offset),
center.at(1) + dist * radius * calc.sin(angle + offset),
)
bezier(
endpoint(-14deg, 1),
endpoint(14deg, 1),
endpoint(-24deg, 3.3),
endpoint(24deg, 3.3),
..arrow,
)
}
for (from, to, ctrl) in (
("d2", "d0", (dx / 2, 1.0)),
("d0", "d2", (dx / 2, -1.0)),
("d1", "d2", (dx * .85, -dy * .85)),
("d3", "d4", (2.35 * dx, -dy * .9)),
("d4", "d6", (3 * dx + .85, -dy / 2)),
("d6", "d4", (3 * dx - .85, -dy / 2)),
) { curved(from, to, ctrl) }
for (from, to) in (("d2", "d3"), ("d6", "d3"), ("d5", "d6")) { line(from, to, ..arrow) }
for (name, angle) in (("d1", 180deg), ("d2", 90deg), ("d3", 90deg), ("d5", 0deg), ("d6", 0deg)) {
self-loop(name, angle)
}
content(
(1.5 * dx, -dy - radius - 0.3),
anchor: "north",
[nodes $d_i$: web documents #h(1.5em) arrows: hyperlinks (loops: self-links)],
)
})