Public
Edited
May 29
2 forks
Importers
31 stars
Also listed in…
Complex Dynamics
Math
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
function put_it_all_together() {
let container = d3
.create("div")
.style("width", `${total_width}px`)
.style("height", `${0.54 * total_width}px`);
container.append(() => mandel_canvas.node()).style("float", "left");
container.append(() => julia_svg);
container.append(() => c.node());
return container.node();
}
Insert cell
Insert cell
// A canvas to draw the Mandelbrot set
mandel_canvas = {
let xmin = -2;
let xmax = 0.6;
let ymin = -1.3;
let ymax = 1.3;
let w = 0.5 * total_width;
let canvas = d3.create("canvas").attr("width", w).attr("height", w);
draw_mandelbrot_set(canvas.node(), xmin, xmax, ymin, ymax);
let context = canvas.node().getContext("2d");
context.fillStyle = "#f30";
let ij = xy_to_ij([-1, 0], xmin, xmax, ymin, ymax, w);
context.fillRect(ij[0] + 1, ij[1] - 1, 3, 3);
const render = function (ij) {
let xy = ij_to_xy(ij, xmin, xmax, ymin, ymax, w);
// The lazy programmer's way to cover up the previous clicked point
draw_mandelbrot_set(canvas.node(), xmin, xmax, ymin, ymax);
context.fillRect(ij[0] + 1, ij[1] - 1, 3, 3);

d3.select(julia_svg)
.attr("c", ij_to_xy(ij, xmin, xmax, ymin, ymax, w))
.select("image")
.attr("xlink:href", generate_julia_im_url({ re: xy[0], im: xy[1] }));
c.html(`c = ${d3.format(".2f")(xy[0])} + ${d3.format(".2f")(xy[1])} i`);
};
let clicked = false;
canvas.on("mousedown", function () {
clicked = true;
render(d3.mouse(this));
});
canvas.on("mousemove", function () {
if (clicked) render(d3.mouse(this));
});
canvas.on("mouseup", function () {
clicked = false;
});

return canvas;
}
Insert cell
// A function to draw the mandelbrot set
function draw_mandelbrot_set(canvas, xmin, xmax, ymin, ymax) {
let bail = 100;
let w = total_width / 2;
let mandel_context = canvas.getContext("2d");
let canvasData = mandel_context.createImageData(canvas.width, canvas.height);
for (let i = 0; i < canvas.width; i++) {
for (let j = 0; j < canvas.height; j++) {
var c = ij_to_xy([i, j], xmin, xmax, ymin, ymax, w);
var it_cnt = mandelbrot_iteration_count(c[0], c[1], bail);
let scaled_it_cnt = 255 - (255 * it_cnt) / (bail + 1);
// let scaled_it_cnt =
// it_cnt == bail ? 0 : ((255 * it_cnt) / (bail + 1)) ** 1.5;
var idx = (i + j * canvas.width) * 4;
canvasData.data[idx + 0] = scaled_it_cnt;
canvasData.data[idx + 1] = scaled_it_cnt;
canvasData.data[idx + 2] = scaled_it_cnt;
canvasData.data[idx + 3] = 255;
}
}
mandel_context.putImageData(canvasData, 0, 0);
}
Insert cell
// Compute the iteration count from z=0 for a given z^2+c
function mandelbrot_iteration_count(cre, cim, bail = 100) {
let x = cre;
let y = cim;
let xtemp;
let ytemp;
let cnt = 0;
while (x * x + y * y <= 4 && ++cnt < bail) {
xtemp = x;
ytemp = y;
x = xtemp * xtemp - ytemp * ytemp + cre;
y = 2 * xtemp * ytemp + cim;
}
return cnt;
}
Insert cell
Insert cell
// A place to draw the Julia set
// The picture of the Julia set is drawn to a canvas which becomes the background of an SVG.
// The orbit is drawn as a path in the SVG on hover.
julia_svg = {
let w = total_width / 2;

const xScale = d3.scaleLinear().domain([-2, 2]).range([0, w]);
const yScale = d3.scaleLinear().domain([-2, 2]).range([w, 0]);
const rScale = d3.scaleLinear().domain([0, 4]).range([0, w]);
const pts_to_path = d3
.line()
.x(function (d) {
return xScale(d[0]);
})
.y(function (d) {
return yScale(d[1]);
});

let svg = d3.create("svg").attr("width", w).attr("height", w);
if (!svg.attr("c")) {
svg.attr("c", "-1,0");
}

// Set up the SVG background
const mapbg = DOM.uid("mapbg");

svg
.append("defs")
.append("pattern")
.attr("id", mapbg.id)
.attr("patternUnits", "userSpaceOnUse")
.attr("width", w)
.attr("height", w)
.append("image")
.attr("xlink:href", generate_julia_im_url({ re: -1, im: 0 }))
.attr("width", w)
.attr("height", w);

svg
.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("width", w)
.attr("height", w)
.attr("fill", mapbg);

svg
.on("mousemove", function () {
svg.selectAll(".orbit").remove();
let c = svg.attr("c").split(",").map(parseFloat);
let xy = ij_to_xy(d3.mouse(this), -2, 2, -2, 2, w);
let orbit = compute_orbit(c, xy);
for (let i = 0; i < orbit.length; i++) {
svg
.append("path")
.attr("class", "orbit")
.attr("d", pts_to_path(orbit.slice(i, i + 2)))
.attr("stroke", "#8888FF")
.attr("stroke-width", 2)
.attr("fill", "none")
.style("opacity", 0.5);
svg
.append("circle")
.attr("class", "orbit")
.attr("cx", xScale(orbit[i][0]))
.attr("cy", yScale(orbit[i][1]))
.attr("r", 4)
.attr("fill", "red")
.attr("stroke", "none")
.attr("fill-opacity", 0.03);
}
svg
.append("circle")
.attr("class", "orbit")
.attr("cx", xScale(orbit[0][0]))
.attr("cy", yScale(orbit[0][1]))
.attr("r", 4)
.attr("fill", "green")
.attr("stroke", "black")
.attr("stroke-width", 1);
})
.on("mouseleave", function () {
svg.selectAll(".orbit").remove();
});

return svg.node();
}
Insert cell
// Generate the dataURL that holds an image of the Julia set.
function generate_julia_im_url(c, bail = 150) {
let w = Math.floor(width / 2);
let context = DOM.context2d(w, w);
context.canvas.width = w;
context.canvas.height = w;
let xmin = -2;
let xmax = 2;
let ymin = -2;
let ymax = 2;
let canvasData = context.createImageData(w, w);

for (let i = 0; i < w; i = i + 1) {
for (let j = 0; j < w; j = j + 1) {
let xy = ij_to_xy([i, j], xmin, xmax, ymin, ymax, w);
let it_cnt = julia_iteration_count(c.re, c.im, xy[0], xy[1], bail);
let color = 255 - (255 * it_cnt) / (bail + 1);
// let color = it_cnt == bail ? 0 : ((255 * it_cnt) / (bail + 1)) ** 4;
let idx = (i + j * w) * 4;
canvasData.data[idx + 0] = color;
canvasData.data[idx + 1] = color;
canvasData.data[idx + 2] = color;
canvasData.data[idx + 3] = 255;
}
}
context.putImageData(canvasData, 0, 0);
return context.canvas.toDataURL();
}
Insert cell
// Compute the iteration count for a given z^2+c and given z0
function julia_iteration_count(cre, cim, x0, y0, bail) {
let x = x0;
let y = y0;
let xtemp;
let ytemp;
let cnt = 0;
while (x * x + y * y <= 4 && ++cnt < bail) {
xtemp = x;
ytemp = y;
x = xtemp * xtemp - ytemp * ytemp + cre;
y = 2 * xtemp * ytemp + cim;
}
return cnt;
}
Insert cell
Insert cell
function f(c, z) {
let cx = c[0];
let cy = c[1];
let x = z[0];
let y = z[1];
return [x * x - y * y + cx, 2 * x * y + cy];
}
Insert cell
function compute_orbit(c, z0) {
let z = z0;
let orbit = [z0];
let cnt = 0;
while (z[0] * z[0] + z[1] * z[1] < 20 && cnt < 500) {
z = f(c, z);
orbit.push(z);
cnt = cnt + 1;
}
return orbit;
}
Insert cell
Insert cell
// A place to display c
c = d3
.create("div")
.style("width", "100%")
.style("height", "30px")
.style("text-align", "center")
.text("c = -1")
Insert cell
Insert cell
function ij_to_xy(ij, xmin, xmax, ymin, ymax, w) {
return [
((xmax - xmin) / (w - 1)) * ij[0] + xmin,
((ymin - ymax) / (w - 1)) * ij[1] + ymax
];
}
Insert cell
function xy_to_ij(xy, xmin, xmax, ymin, ymax, w) {
return [
((w - 1) * (xy[0] - xmin)) / (xmax - xmin),
((1 - w) * (xy[1] - ymax)) / (ymax - ymin)
];
}
Insert cell
total_width = width < 850 ? width : 850
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