Public
Edited
Jan 20, 2021
Insert cell
Insert cell
// Placeholder for circle
Insert cell
Insert cell
ladybug1 = FileAttachment("ladybug1.png")
Insert cell
ladybug2 = FileAttachment("ladybug2.png");
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
{
var width = 600;
var height = 600;
var svg = d3.select(DOM.svg(width, height))
.style("overflow", "visible");
// Let's define a grid and have the ladybug start in the center
// Reference: https://observablehq.com/@d3/margin-convention
// and this one https://observablehq.com/@d3/scatterplot-with-shapes
var margin = ({top: 10, right: 10, bottom: 10, left: 10})
var x = d3.scaleLinear()
.domain([-1, 1])
.range([margin.left, width - margin.right])
var y = d3.scaleLinear()
.domain([-1, 1])
.range([height - margin.bottom, margin.top])
var xAxis = g => g
.attr("transform", `translate(0,${(height + margin.top - margin.bottom)/2})`)
.call(d3.axisBottom(x))
var yAxis = g => g
.attr("transform", `translate(${(width + margin.left - margin.right)/2},0)`)
.call(d3.axisLeft(y))
var grid = g => g
.attr("stroke", "currentColor")
.attr("stroke-opacity", 0.1)
.call(g => g.append("g")
.selectAll("line")
.data(x.ticks())
.join("line")
.attr("x1", d => 0.5 + x(d))
.attr("x2", d => 0.5 + x(d))
.attr("y1", margin.top)
.attr("y2", height - margin.bottom))
.call(g => g.append("g")
.selectAll("line")
.data(y.ticks())
.join("line")
.attr("y1", d => 0.5 + y(d))
.attr("y2", d => 0.5 + y(d))
.attr("x1", margin.left)
.attr("x2", width - margin.right));
svg.append("g")
.call(xAxis);
svg.append("g")
.call(yAxis);
svg.append("g")
.call(grid);
var drawer = arc_drawer(svg, x, y);
drawer.AddArc([1,0], [-1,0], y(1)-y(0), "red");
drawer.AddArc([-1,0], [1,0], y(1)-y(0), "red");
var angle = 90;
var ladybug = [await ladybug1.url(), await ladybug2.url()];
function Bug(svg) {
var arcs = [];
var bug = svg.append("image").attr("xlink:href", ladybug[0])
.attr("height", "80");
var pendown = true;
var bug_state = 0;
var d = [0,1];
var p = [0,0];
function Move() {
var theta = Math.atan2(d[0], d[1]) * 180 / Math.PI;
bug.attr("transform", `translate(${x(p[0])},${y(p[1])}) rotate(${theta}) translate(-25,-42)`);
}

Move();
function Forward(x) {
var seg = hyperbolic.SegmentDir(p, d, x);
if (pendown) {
arcs.push(drawer.AddHArc(p, seg.q));
}
p = seg.q;
d = seg.tq;
Move();
}
function Rotate(angle) {
var theta = -angle * Math.PI / 180;
d = euclid.Rotate(d, theta);
Move();
}
function Home() {
p = [0,0];
d = [0,1];
Move();
}
function PenUp() {
pendown = false;
}
function PenDown() {
pendown = true;
}
function Clear() {
Home();
drawer.ClearHArcs();
}
events.Register("Forward", Forward);
events.Register("Right", x => Rotate(x));
events.Register("Left", x => Rotate(-x));
events.Register("PenUp", PenUp);
events.Register("PenDown", PenDown);
events.Register("Clear", Clear);
events.Register("Home", Home);
return {
Move: Move,
Forward: Forward,
Rotate: Rotate,
};
}
var bug = Bug(svg);
return svg.node();
}
Insert cell
{
Clear();
var k = 100;
for (var i = 0; i < k; ++i) {
Forward(1.0986);
Right(60);
}
Home();
Right(180);
for (var i = 0; i < k; ++i) {
Left(60);
Forward(1.0986);
}
return 1;
}
Insert cell
{
Clear();
PenUp();
Forward(1);
Left(230);
PenDown();
var k = 100;
for (var i = 0; i < k; ++i) {
Forward(1.762747174039086);
Right(90);
}
Home();
PenUp();
Forward(1);
Left(230);
PenDown();
Right(180);
for (var i = 0; i < k; ++i) {
Left(90);
Forward(1.762747174039086);
}
return 1;
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
{
var width = 600;
var height = 600;
var svg = d3.select(DOM.svg(width, height))
.style("overflow", "visible");
// Let's define a grid and have the ladybug start in the center
// Reference: https://observablehq.com/@d3/margin-convention
// and this one https://observablehq.com/@d3/scatterplot-with-shapes
var margin = ({top: 10, right: 10, bottom: 10, left: 10})
var x = d3.scaleLinear()
.domain([-1, 1])
.range([margin.left, width - margin.right])
var y = d3.scaleLinear()
.domain([-1, 1])
.range([height - margin.bottom, margin.top])
var xAxis = g => g
.attr("transform", `translate(0,${(height + margin.top - margin.bottom)/2})`)
.call(d3.axisBottom(x))
var yAxis = g => g
.attr("transform", `translate(${(width + margin.left - margin.right)/2},0)`)
.call(d3.axisLeft(y))
var grid = g => g
.attr("stroke", "currentColor")
.attr("stroke-opacity", 0.1)
.call(g => g.append("g")
.selectAll("line")
.data(x.ticks())
.join("line")
.attr("x1", d => 0.5 + x(d))
.attr("x2", d => 0.5 + x(d))
.attr("y1", margin.top)
.attr("y2", height - margin.bottom))
.call(g => g.append("g")
.selectAll("line")
.data(y.ticks())
.join("line")
.attr("y1", d => 0.5 + y(d))
.attr("y2", d => 0.5 + y(d))
.attr("x1", margin.left)
.attr("x2", width - margin.right));
svg.append("g")
.call(xAxis);
svg.append("g")
.call(yAxis);
svg.append("g")
.call(grid);
var drawer = arc_drawer(svg, x, y);
drawer.AddArc([1,0], [-1,0], y(1)-y(0), "red");
drawer.AddArc([-1,0], [1,0], y(1)-y(0), "red");
// Apeirogon tiling initial edge
function ApeirogonSide(k) {
var x = Math.cos(Math.PI / k);
return Math.log((1+x)/(1-x));
}
var k = 3;
var side = ApeirogonSide(k);
var ap = [.1,.1];
var aq = hyperbolic.SegmentDir(ap, [0,1], side).q;
var drawing_threshold = 0.1;
var max_level = 20;
function DrawTiling(ap, aq) {
drawer.ClearHArcs();
drawer.AddHArc(ap, aq);
var Queue = [];
var aseg = hyperbolic.Line(ap, aq);
Queue.push({p:ap, d:euclid.Scale(aseg.tp, -1), l:1});
Queue.push({p:aq, d:euclid.Scale(aseg.tq, -1), l:1});
while (Queue.length > 0) {
var e = Queue.pop();
for (var i = 1; i < k; ++i) {
var d1 = euclid.Rotate(e.d, 2 * Math.PI * i/ k);
var seg = hyperbolic.SegmentDir(e.p, d1, side);
drawer.AddHArc(e.p, seg.q);
var dist = euclid.Distance(e.p, seg.q);
if (e.l <= max_level && dist > drawing_threshold) {
Queue.push({p:seg.q, d:euclid.Scale(seg.tq, -1), l:e.l+1});
}
}
}
}

DrawTiling(ap, aq);
//////////////////////
var ladybug = [await ladybug1.url(), await ladybug2.url()];
function Bug(svg) {
var bug = svg.append("image").attr("xlink:href", ladybug[0])
.attr("height", "80");
var bug_state = 0;
function MoveBug(p,d) {
var theta = Math.atan2(d[0], d[1]) * 180 / Math.PI;
bug.attr("transform", `translate(${x(p[0])},${y(p[1])}) rotate(${theta}) translate(-25,-42)`);
}
function MoveLegs() {
bug.attr("xlink:href", ladybug[bug_state]);
bug_state = (bug_state+1)%2;
}
function SetOnClick(f) {
bug.on("click", f);
}
return {
MoveBug: MoveBug,
MoveLegs: MoveLegs,
SetOnClick: SetOnClick,
};
}
function BugControl(bug, linear_speed, angular_speed) {
var p = [0,0];
var d = [0,1];
bug.MoveBug(p,d);
function Rotate(sign=1) {
var dtheta = sign * angular_speed * Math.PI / 180.0;
d = matrix.Prod(matrix.Rotation(dtheta), d);
bug.MoveBug(p,d);
}
function GoForward(draw=false) {
var seg = hyperbolic.SegmentDir(p, d, linear_speed);
if (draw) {
drawer.AddHArc(p, seg.q);
}
p = seg.q;
d = seg.tq;
bug.MoveBug(p,d);
}
return {
Rotate: Rotate,
GoForward: GoForward,
};
}
var bug = Bug(svg);
var bug_control = BugControl(bug, /*linear_speed*/ .02, /*angular_speed*/ 2);
var keys_pressed = {'d':false, 'f':false, 'j':false};
function UpdateKeys() {
keys_pressed[d3.event.key] = d3.event.type == 'keydown';
}
function RegisterKeyboardControls() {
d3.select("body")
.on("keydown", UpdateKeys)
.on("keyup", UpdateKeys);
}
RegisterKeyboardControls();
bug.SetOnClick(RegisterKeyboardControls);

function Move() {
if (keys_pressed['f']) {
bug_control.Rotate();
}
if (keys_pressed['d']) {
bug_control.Rotate(-1);
}
if (keys_pressed['j'] && keys_pressed['k']) {
bug_control.GoForward(true);
} else if (keys_pressed['j']) {
bug_control.GoForward(false);
}
if (keys_pressed['f'] || keys_pressed['d'] || keys_pressed['j']) {
bug.MoveLegs();
}
setTimeout(Move, 50);
}
Move();
return svg.node();
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
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