Published
Edited
Apr 13, 2021
5 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
viewof render_button = Object.defineProperty(
html`<button>Render GIF`,
'value',
{ value: undefined }
)
Insert cell
{
render_button;
const draw = render_canvas_delay();
return renderGif(invalidation, draw, 3000, {preview: draw(0)})
} // render the gif
Insert cell
render_canvas_delay = () => {
const duration = .8; // proportion of time to render any given line
const delay = (1 - duration) / 2 / (field_data.length + 1); // proportion of time to wait between lines being drawn
const ctx = DOM.context2d(w, h);
ctx.globalAlpha = 1;
ctx.fillStyle = backgroundColor;
ctx.lineWidth = line_width;

ctx.fillRect(0, 0, w, h);
return t => {
ctx.fillRect(0, 0, w, h);
ctx.beginPath();
ctx.strokeStyle = lineColor;
ctx.lineWidth = line_width;
// Iterate through each starting point
field_data
.forEach((line, index) => {
const elapsed_time = t - index * delay;
const time_since_mid_point = t - .5 - index * delay;
let steps = d3.min([
Math.floor((elapsed_time / duration) * max_steps),
max_steps
]);
let start_step =
t > .5 ? Math.ceil((time_since_mid_point / duration) * max_steps) : 0;
if (steps < 1) return;
let v = PVector(line);
ctx.moveTo(v.x, v.y);

// Draw up to the halfway step
array(steps).forEach(d => {
const isInside = v.x >= 0 && v.x <= w && v.y >= 0 && v.y <= h;
if (!isInside) return;
const angle =
simplex.noise3D(v.x / noiseScale, v.y / noiseScale, seed) * TAU;

// Depending on if you've crossed the midpoint, move before drawing
if (d >= start_step) ctx.lineTo(v.x, v.y);
else ctx.moveTo(v.x, v.y);
v.add(PVector.fromAngle(angle).setMag(stepLength));
});
});
ctx.stroke();

return ctx.canvas;
};
}
Insert cell
field_data = array(n_start)
.map(d => {
let i = 0;
let y = Math.random() * h;
let x = Math.random() * w;
return { x, y };
})
.sort((a, b) => a.y - b.y)
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
d3 = require("d3")
Insert cell
Insert cell
Insert cell
import {slider} from "@jashkenas/inputs"
Insert cell
import {button} from "@jashkenas/inputs"
Insert cell
Insert cell
import {renderGif} from "@mootari/gif"
Insert cell
Insert cell
render_canvas = () => {
const ctx = DOM.context2d(w, h);
ctx.globalAlpha = 1;
ctx.fillStyle = backgroundColor;
ctx.fillRect(0, 0, w, h);
return t => {
ctx.fillRect(0, 0, w, h);
let current_step = Math.floor(t * max_steps);
let draw_step =
current_step > max_steps / 2 ? Math.floor(max_steps / 2) : current_step;
if (current_step > max_steps) return;
// Iterate through each starting point
field_data.forEach(line => {
let v = PVector(line);
ctx.moveTo(v.x, v.y);
ctx.strokeStyle = lineColor;

// Draw up to the halfway step
array(draw_step).forEach(d => {
const isInside = v.x >= 0 && v.x <= w && v.y >= 0 && v.y <= h;
if (!isInside) return;
const angle =
simplex.noise3D(v.x / noiseScale, v.y / noiseScale, seed) * TAU;

v.add(PVector.fromAngle(angle).setMag(stepLength));

ctx.lineTo(v.x, v.y);
});
});

// Draw over to create erasing effect
if (t > .5) {
ctx.stroke();
ctx.beginPath();
ctx.strokeStyle = backgroundColor;
field_data.forEach(line => {
let v = PVector(line);
ctx.moveTo(v.x, v.y);

const erase_step = Math.floor((t - .5) * max_steps);

array(erase_step).forEach(d => {
const isInside = v.x >= 0 && v.x <= w && v.y >= 0 && v.y <= h;
if (!isInside) return;
const angle =
simplex.noise3D(v.x / noiseScale, v.y / noiseScale, seed) * TAU;

v.add(PVector.fromAngle(angle).setMag(stepLength));

ctx.lineTo(v.x, v.y);
});
});
}
ctx.stroke();
return ctx.canvas;
};
}
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