Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

p5 #1093

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open

p5 #1093

wants to merge 1 commit into from

Conversation

mbostock
Copy link
Member

Adds p5.js to the set of built-in libraries (so it just works).

@Fil
Copy link
Contributor

Fil commented Mar 18, 2024

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);
    };
  })
);
```

@mbostock
Copy link
Member Author

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.

@Fil
Copy link
Contributor

Fil commented Mar 18, 2024

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.

@patrixr
Copy link

patrixr commented Apr 20, 2024

@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 !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants