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

Run force layout synchronously. #419

Closed
mbostock opened this issue Dec 30, 2011 · 10 comments
Closed

Run force layout synchronously. #419

mbostock opened this issue Dec 30, 2011 · 10 comments
Milestone

Comments

@mbostock
Copy link
Member

It might be nice to have the ability to run the force layout synchronously, so that you could (say) run it a fixed number of iterations, or until the alpha dropped below a certain value. Then you could initialize a force layout synchronously on page layout, or externalize the computation to a webworker.

@txominpelu
Copy link

Any thought about the specific implementation?

Cause right now a worker like this could already work and returned the positioned nodes (not tested):

var force = ...

self.addEventListener('message', function(e) {
d3.timer(force.tick);
self.postMessage({nodes:nodes, links:links});
self.close();
}, false);

@mbostock
Copy link
Member Author

mbostock commented Jan 4, 2012

Yeah, I think it's just a question of making the internal tick function publicly-accessible as force.tick. Then you can call it however you like—say until it returns true, or a fixed number of times in a loop.

@txominpelu
Copy link

Yep, that would work.

So far when I wanted to do something similar I had a function listening to tick events that had a condition to check when alpha was smaller than a predefined value.

@krid
Copy link
Contributor

krid commented Jan 24, 2012

I'm not sure this entirely fixes the problem. Seems to me that before you can call force.tick() you still need to call force.start() once to initialize everything. However, start() calls resume() which calls d3.timer(force.tick()), which means you're going to have both your synchronous method and the timer-driven async method both poking at the layout.

@mbostock
Copy link
Member Author

Well, assuming you run the force layout synchronously to completion, the timer will get called back once after that, and will only run once because (alpha *= .99) < .005. And, if it's a static layout, you probably don't have a tick listener.

Also, you can cancel the timer by calling force.stop() when you're done calling force.tick(). So, you'd start(), tick() a fixed number of times, and then stop().

I could see adding an optional argument so that you could force.start(false) for synchronous starting, which wouldn't start the timer. But I'm not sure if that's better than just calling stop()?

@krid
Copy link
Contributor

krid commented Jan 24, 2012

What bothers me is that by calling force.start(); force.stop(); force.tick(); you're intentionally getting into a race condition with the timer. In theory the call to stop should happen before the timer fires, but it feels wrong to depend on that. Or am I missing something about the architecture?

@mbostock
Copy link
Member Author

There's no race condition, because JavaScript is single-threaded. It's impossible to receive a callback while your code is still running.

@krid
Copy link
Contributor

krid commented Jan 24, 2012

Ahh, yes, that would be the forest I was missing while examining the tree in front of me. Never mind...

@krid
Copy link
Contributor

krid commented Feb 8, 2012

I finally got out from under my other projects and got back to this one. Turns out that force.start(); force.stop(); force.tick(); doesn't work cleanly. The call to stop() sets alpha to zero, so that subsequent calls to tick() don't accomplish anything. You can get around this by calling start(), looping over tick() a few hundred times, then calling stop(), but you still end up with at least one call to tick() from the timer (because it has to run through the method and get a false to cancel the timer). This is a problem if you need to have a tick callback that does "interesting" stuff.

I need a way to call start() without getting the call to resume(). Doing this cleanly will require at least one more API change. I can see three solutions:

  1. Remove the call to resume() from the end of start().
    Requires that everyone update their code, so probably a non-starter.

  2. Add a flag/option when creating the layout or when calling start().

  3. Rename start() to initialize() and make a new start() that just calls initialize() and resume().
    Adding a public method might seem less clean, but on the other hand having initialize() separated from start() seems clearer to me.

I was going to send you pull request for #3, but I figured you might prefer #2, or maybe something else. Let me know if you want me to code something up.

@mbostock
Copy link
Member Author

The "one call to tick" was fixed in 2.7.5. See #510.

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

No branches or pull requests

3 participants