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

Reheating the simulation #25

Open
FrissAnalytics opened this issue May 3, 2019 · 15 comments
Open

Reheating the simulation #25

FrissAnalytics opened this issue May 3, 2019 · 15 comments
Assignees

Comments

@FrissAnalytics
Copy link

Hi Vasturiano!

Thanks for your amazing library, super useful!

In some use cases I want to reheat the force simulation after it has started or change some settings on the fly. Often this requires access to the main simulation object, e.g. by saying simulation.alpha(1).restart(), as per d3.

I guess in general it's very useful to be able to access the main simulation object in case you need it. Is this possible or is there a way this can be done or to expose it?

Any help is much appreciated :-)

@vasturiano
Copy link
Owner

Thanks for reaching out! Using the latest version (1.15.0) you can simply do myGraph.refresh() to reheat the force simulation engine.

As for the other configuration parameters, there are exposed properties that lets you manipulate this, such as d3AlphaDecay, d3VelocityDecay, d3Force, warmupTicks and cooldownTicks. If there's somehow an operation you're not able to control using these methods, please let me know and I'll consider adding additional support for.

@vasturiano vasturiano self-assigned this May 4, 2019
@FrissAnalytics
Copy link
Author

FrissAnalytics commented May 5, 2019

Hi,

thanks for the update, much appreciated! I had a few more questions if you don't mind :-)

With respect to the simulation, it would be nice to be able to manually control the tick function e.g. to say simulation.tick(20). Is there currently a good way to do that?

Furthermore, it would be handy to be able to manually- redraw - the canvas, e.g. because you've changed the color for a node. Currently I do this by using a function inside nodeCanvasObject that itself depends on a settings object. Of note, I need nodeCanvasObject because I draw more complex shapes as well. Hence if I change the settings object, the function changes the color. This is just an example of course, but in general it would be nice to be able to tell the graph object to re-render itself.

In that scenario, what would be the best way to redraw the canvas (without changing node positions etc.)?

Finally, it would also be handy to be able to have direct access to the ctx object, outside of nodeCanvasObject in order to control the canvas element by another process / function. For instance, I do some additional manipulations to ctx after the force engine has done its thing.

Clearly you could assign ctx to some variable from inside of nodeCanvasObject and then use that variable later, but it's a bit awkward to do it that way.

PS: I'm using your package in vue.js, which works quite well. No fancy binding, inside a component after mount I just create an instance of ForceGraph and define a few helper methods on the vue object that essentially call a few methods on a ForceGraph instance. For instance, now there is no zoom to fit function that zooms the canvas such that all nodes are inside the view port, so I created my own. A function like that would be useful to others as well I guess.

Anyways, thanks for you help, much appreciated! :-)

@havardl
Copy link

havardl commented Jun 6, 2019

PS: I'm using your package in vue.js, which works quite well. No fancy binding, inside a component after mount I just create an instance of ForceGraph and define a few helper methods on the vue object that essentially call a few methods on a ForceGraph instance. For instance, now there is no zoom to fit function that zooms the canvas such that all nodes are inside the view port, so I created my own. A function like that would be useful to others as well I guess.

Hi @FrissAnalytics, I'm looking into using this with vue as well, and was just wondering if you might be interested in sharing your own vue implementation for this?

@vasturiano
Copy link
Owner

@FrissAnalytics a few answers to your questions:

With respect to the simulation, it would be nice to be able to manually control the tick function e.g. to say simulation.tick(20). Is there currently a good way to do that?

You can control the dry-run ticks by doing .warmupTicks(20). The effect is exactly the same.

Furthermore, it would be handy to be able to manually- redraw - the canvas, e.g. because you've changed the color for a node. Currently I do this by using a function inside nodeCanvasObject that itself depends on a settings object. Of note, I need nodeCanvasObject because I draw more complex shapes as well. Hence if I change the settings object, the function changes the color. This is just an example of course, but in general it would be nice to be able to tell the graph object to re-render itself.

Changing the assignment or the behavior of nodeCanvasObject will not cause a reheating of the simulation. The nodeCanvasObject method is called at every canvas refresh tick using raf (requestAnimationFrame), therefore any behavioral changes you make to the to the nodeCanvasObject mode should reflect automatically in the following raf tick, you shouldn't need to do anything to cause an update.

Finally, it would also be handy to be able to have direct access to the ctx object, outside of nodeCanvasObject in order to control the canvas element by another process / function. For instance, I do some additional manipulations to ctx after the force engine has done its thing.

The issue with exposing the context is that you wouldn't be able to do much manipulation with it, since it is cleared at every raf tick. Anything you'd write to it would be wiped almost immediately in the following tick. This is why it's not exposed, and all the relevant draws need to happen in the nodeCanvasObject method. If you'd like to have some other persistent visual elements I'd suggest to have another canvas that you maintain externally that aligns exactly underneath (or above it) to this dom element. That way you're just layering the multiple canvases and you get better update performance by not repetitively drawing the same things.

As for vue.js I can't be of much help because I haven't attempted the integration, but feel free to post examples here for the benefit of other users that may want to do the same thing. :)

@FrissAnalytics
Copy link
Author

@vasturiano Thanks for your comments!

I'm happy to offer some bigger, more worked out examples for others, that also include vue and vuetify so others can see how your awesome package can be applied in those contexts. One nice thing of the vuetify docs is that they have a codepen for all the examples, which is especially nice if you want to play around with the code and experiment. If you want I can help in setting up a bunch of them for your package based on your existing code examples.

With respect to the ctx canvas context, previously I was not aware the requestAnimationFrame loop keeps running at all times unless you explicitly tell it not to via pauseAnimation.

Especially if you don't use the edge particles and you have largely unchanging data there is little need to keep the event loop running after the simulation freezes. For instance, currently on a graph of e.g. 200 nodes, even after the simulation freezes, the nodeCanvasObject callback will be executed approximately 200 * 60 = 12.000 per second, even when the data and the layout, i.e. the visual, do not change.

It's a bit unfortunate that currently when you apply pauseAnimation all event propagation always stops. I can see that in the generic scenario that needs to be done. However, in the above scenario, instead of wiping the canvas, it would be nice to be able to keep it alive after the layout freezes. That way you can still get the colors from the canvas to be able to generate hover events. When applying a drag move one could then quickly call resumeAnimation to facilitate the drag move and automatically call pauseAnimation after the layout stabilizes. Doing so will be less taxing for the browser, which helps in making additional transitions less choppy, especially when there are a lot of nodes in the network.

@vasturiano
Copy link
Owner

@FrissAnalytics good points. It's a little difficult to keep track of the variety of scenarios that the simulation could go dormant, even though there is certainly room for performance improvement.

Currently the decision is to let the consumer handle that state by choosing when to call pauseAnimation and resumeAnimation. A possible improvement is to decouple that from the pointer interaction events, so that one could still detect hover interactions and resume/pause the animation engine to permit node dragging. Actually the fact that dragging is not possible when the animation engine is off is the main reason why the pointer interactions are also disabled on pauseAnimation.

As for vue.js I would love to see a bindings repo, similar to what there is for React at react-force-graph.

@FrissAnalytics
Copy link
Author

FrissAnalytics commented Jun 20, 2019

@vasturiano I think it would be really helpful to decouple the pointer interaction events from the pauseAnimation and resumeAnimation functionality. I was thinking, wouldn't it be possible to use a mousedown event or say an invisible svg element at the location of the mouse cursor to signal the start of a drag move in case the rendering cycle of the component is paused?

@klaraseitz
Copy link

@vasturiano I would also be interested in decoupling the pointer events from pause and resumeAnimation functions.
Is there any (planned) progress on this?

Mainly I would like to render the graph only once it's done with positioning, allow the user to add nodes through click (triggering a rerender) and only ever show the result of the finished positioning and not showing the steps to reach the new position.

@vasturiano
Copy link
Owner

@klaraseitz you can achieve this by setting cooldownTicks(0) and warmupTicks(80) (adjust value to your case). This will essentially compute the final node positions in the warmup phase, and bypass the cooldown phase where the graph build-up is normally being animated.

@klaraseitz
Copy link

@vasturiano great thanks!

And as for the pointer events. I would like to pauseAnimations onEngineStop. Such that neither the linkCanvasObject nor the nodeCanvasObject get called after the enginestop but click/hover interactions can still be caught. (on clicking/dragging a node animations should resume)

I tried
this.graph.onEngineStop(() => { this.graph.pauseAnimation() })
but the linkCanvasObject and nodeCanvasObject callbacks keep being called and interactions were not deactivated.

@vasturiano
Copy link
Owner

@klaraseitz if your overall intent is to improve the graph performance by bypassing rerenders after the simulation engine has stopped, note that there's been a flag added on v1.38.0 that addresses exactly this.

The option is autoPauseRedraw and it's enabled by default, so you should be automatically getting the performance improvement, if you upgrade your component version.

More details about it in this issue.

@klaraseitz
Copy link

@vasturiano Oh perfect!! I saw that option but thought my current version already had that. Now that I upgraded it works really smoothly. Thanks so much, Great library!

@kohlerpop1
Copy link

@vasturiano Sorry for bringing this back up but I am running into a refresh issue with the graph.
image

This is occurred using the latest version!
image

@pebubblemaps
Copy link

Hi,
First of all, thanks for your amazing job @vasturiano ! It's a great lib and I'm having fun using it.
I have some trouble with the interactions and the forces/reheat:
I would like to have hover and be able to pan/zoom, but without it triggering a re-heat; all while keeping the ability to drag (and that would of course trigger the reheat).
The end goal is to recreate the example of d3-force but with your great library !
I'm pulling my hair over this, but don't seem to be able to figure it out.
Is it possible to do that ?

@FrissAnalytics
Copy link
Author

@pebubblemaps don't know if I understand your question completely, but you can just fix nodes into place by setting a fx and fy value.

For instance, after a given number of ticks, when the layout engine has done its thing, loop over all the nodes and fix all the node x, y positions. See this. example for details.

The difference with the example is that in the example only the node that gets dragged is fixed, but if you fix all the nodes, the graph does not move.

Assuming gData has you nodes, you can say

gData.nodes.map(node => {
       node.fx = node.x;
       node.fy = node.y;
})

You can set the node fx or fy position to null to unfreeze it. See the example on how to move a node with a drag move.

After the nodes have a fixed position, you can pan and zoom the graph with node hovers without a reheat, meaning that the nodes do not change position anymore.

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

No branches or pull requests

6 participants