d3-force icon indicating copy to clipboard operation
d3-force copied to clipboard

Creating a new boundary force

Open john-guerra opened this issue 7 years ago • 11 comments
trafficstars

Many times when I'm doing network visualizations I want to guarantee that the nodes can't leave the canvas. Positioning forces can help on this for smaller networks, but when the number of nodes increase, it is easy to get nodes outside the view point. Moreover, given rectangular canvas, I want to use all the corners too for the visualization and centering forces usually display everything on a circle. Therefore I created a d3.forceBoundary that keeps nodes inside a boundary and decrementally reduces its strength towards the middle so it can play with other forces. Please let me know if you would consider adding this to d3 (and thanks for the hard work!)

john-guerra avatar Apr 15 '18 00:04 john-guerra

@john-guerra just chiming in to mention my experience is also that this force type would be super useful! It is so often needed that I think it earned its place within d3-force core. If not however, then it should definitely be published as an external force plugin (and listed on d3-force-registry).

Just a few small remarks:

  • The strength feature is great. In different cases you sometimes want nodes to bounce off the container walls or not. Was thinking maybe the option could be called elasticity, alluding to the coefficient of restitution in elastic collisions. [EDIT] Please ignore above. I understand the strength parameter has a different nature, to pull the outside nodes to the boundary lines. So, do you think an additional "collision elasticity" parameter would still make sense for this force type, or should perhaps be a separate force altogether?

  • Is it possible to specify a radius or similar on the nodes, so that the whole node is kept inside the container, and prevent the node's body from crossing the boundary line. Basically, the collision point would respect the node's surface, not its center coordinates.

And thanks for putting this together!

vasturiano avatar Apr 15 '18 20:04 vasturiano

@vasturiano glad you liked it, and thanks for the recommendations

john-guerra avatar Apr 15 '18 23:04 john-guerra

I took the code from this merge request and implemented it as a d3 plugin, but it seems like there's a bug in how it works. In my visualization I create the d3.force element, and add the boundary force, and add the nodes array - everything works fine.

Then later I add a new force.nodes(). With an active forceBoundary, all the nodes end up stuck at the center of the graph. This happens even if I do a .initialize(nodes) on the forceBoundary. To get them to continue to position properly, I have to completely readd the force like so:

linksForce.force("boundary", d3.forceBoundary(-(svgWidth/2), -(svgHeight/2), svgWidth/2, svgHeight/2).initialize(currentNodes));

From my understanding of the documentation, I should just be able to use the .initialize method, and not recreate the entire force. If you'd like I can try to create a code sample to show the issue.

@john-guerra

hornj avatar Jul 30 '18 15:07 hornj

@hornj Yes, can you please create an example of your problem?

You don't really need to call the initialize function by yourself, d3 would do that for you. I'll recommend using positive values on your boundary, the library should work with negative values too, but I always find it easier to wrap my head around positive values.

Here is an observable using a simple network with negative values like your https://beta.observablehq.com/@john-guerra/d3-force-directed-graph-with-force-boundary And here is the original observable I made to explain it https://beta.observablehq.com/@john-guerra/d3-force-boundary

john-guerra avatar Jul 30 '18 18:07 john-guerra

@john-guerra I'm working on creating an example, but you're right the initialize function doesn't have anything to do with it. It returns undefined, so the line of code I posted was effectively removing the boundary force.

I'll try to find a concrete example and let you know.

hornj avatar Aug 01 '18 17:08 hornj

+1 for this feature

kumavis avatar Apr 18 '19 08:04 kumavis

Small question: how this different from using forceX and forceY like here? Pro/cons?

      .force("x", d3.forceX())
      .force("y", d3.forceY())

Thanks for the plugin in any case, I'm already using it now 😄

nathanvogel avatar May 07 '19 19:05 nathanvogel

@nathanvogel positional forces are great, but when you have a larger disjoint graph, your nodes will tend to leave the viewport. See this notebook where increased the value of charge for the ManyBody force. Try turning on and off the forceBoundary (when off it will use just positional forces), and let me know if it makes sense https://observablehq.com/d/186feb840a7e5b06.

Also @kumavis @hornj @vasturiano, I published the library as an npm module https://www.npmjs.com/package/d3-force-boundary, I think it still has bugs, but it works nicely with observable

john-guerra avatar May 07 '19 20:05 john-guerra

@john-guerra Awesome! Thanks a lot for the Observable, the effects are super clear 👌 (in my first use case the difference was minimal, but this will get useful when I get more data into my project)

nathanvogel avatar May 07 '19 21:05 nathanvogel

@john-guerra nice! I've added it to the list at d3-force-registry.

vasturiano avatar May 08 '19 07:05 vasturiano

@john-guerra Thanks for publishing this. I'm planning to try this again soon. I ended up giving up when I tried to use this before as something in the code or how I implemented it was causing my graphs to just disappear at certain points.

hornj avatar May 08 '19 17:05 hornj