p5
Adds p5.js to the set of built-in libraries (so it just works).
Nice that you overload the draw function and thus don't need setInterval as we had previously.
Beyond the (efficient) plumbing, I feel that we should give a nicer example—something that makes people want to use P5. Here's a possibility (although maybe a bit too much code?
The example below is slightly adapted from R. Luke DuBois’s origin [Spirograph P5 example](https://p5js.org/examples/simulate-spirograph.html). (Click to switch modes.)
```js
const NUMSINES = view(Inputs.range([2, 50], {step: 1, value: 20, label: "number of wheels"}));
```
```js echo
display(
p5((p) => {
let sines = new Array(NUMSINES); // an array to hold all the current angles
let rad; // an initial radius value for the central sine
let i; // a counter variable
// play with these to get a sense of what's going on:
let fund = 0.005; // the speed of the central sine
let ratio = 1; // what multiplier for speed is each additional sine?
let alpha = 50; // how opaque is the tracing system
let trace = true; // are we tracing?
p.setup = () => {
p.createCanvas(710, 400);
rad = p.height / 4; // compute radius for central circle
p.background(dark ? 51 : 204); // clear the screen
for (let i = 0; i < sines.length; i++) {
sines[i] = p.PI; // start EVERYBODY facing NORTH
}
};
p.draw = () => {
if (!trace) {
p.background(dark ? 51 : 204); // clear screen if showing geometry
p.stroke(dark ? 255 : 0, 255); // pen color
p.noFill(); // don't fill
}
// MAIN ACTION
p.push(); // start a transformation matrix
p.translate(p.width / 2, p.height / 2); // move to middle of screen
for (let i = 0; i < sines.length; i++) {
let erad = 0; // radius for small "point" within circle... this is the 'pen' when tracing
// setup for tracing
if (trace) {
p.stroke(dark ? 255 : 0, dark ? 255 : 0, 255 * (p.float(i) / sines.length), alpha);
p.fill(dark ? 255 : 0, dark ? 255 : 0, 255, alpha / 2);
erad = 5.0 * (1.0 - p.float(i) / sines.length); // pen width will be related to which sine
}
let radius = rad / (i + 1); // radius for circle itself
p.rotate(sines[i]); // rotate circle
if (!trace) p.ellipse(0, 0, radius * 2, radius * 2); // if we're simulating, draw the sine
p.push(); // go up one level
p.translate(0, radius); // move to sine edge
if (!trace) p.ellipse(0, 0, 5, 5); // draw a little circle
if (trace) p.ellipse(0, 0, erad, erad); // draw with erad if tracing
p.pop(); // go down one level
p.translate(0, radius); // move into position for next sine
sines[i] = (sines[i] + (fund + fund * i * ratio)) % p.TWO_PI; // update angle based on fundamental
}
p.pop(); // pop down final transformation
};
p.mouseClicked = () => {
trace = !trace;
p.background(dark ? 0 : 255);
};
})
);
```
Sure, I can tinker on some better examples. Does the implementation look okay though otherwise? And are you supportive of adding p5 to built-ins? I’m thinking we can be pretty liberal on adding built-ins given that it has minimal overhead and helps Observable Framework to feel batteries included; without it folks spend a lot of time struggling with require/import/etc.
Yeah I'm fine with it, especially because it solves the "plumbing" issue. But I can't think of a real use case. It's nice as a stand-alone app but it has its specific syntax, which is probably a bit obsolete now; and with p5-in-a-function needs you to prefix all the keywords (like p.TWO_PI), which kills a bit of the spontaneity.
@mbostock I've gone down a similar path as you have and implemented my own (opinionated) version (code here)
Just dropping in a few notes on what I've done in case it's helpful:
- Had some edge cases where the
node.isConnected ? ...ternary wouldn't trigger (haven't managed to pinpoint why I had issues). I'm using a mutation observer now to remove it, which seems to work better (no duplicate sketch running in background) - I'm starting sketches only when they come into view, longer pages would cause the viewer to miss the beginning of animations
- Added replay and save functionalities (using custom buttons), I don't know if that could have been achieved directly from markdown since we don't have access to the p5 reference
- Created a helper "draw" for times when a looping sketch is not required and we just want static drawing, e.g.
draw(400, 400, (p5) => {
p5.rect(...)
})
Overall I felt like I needed a bit of tweaking for it to be more user friendly. Was planning on making a PR into Observable with the basics, but saw you already had one up. Cheers !
Maybe in the future we could have a p5 fenced code block, and it somehow rewrites the code so that you can use the “global mode” syntax but it evaluates in “instance mode”. Then you could use verbatim syntax from the p5.js web editor in Framework and it would work, and even have multiple sketches on the same page. But… unless we hear more demand for p5.js in Framework, I am not planning to prioritize this work.