d3 / d3-force

Force-directed graph layout using velocity Verlet integration.
https://d3js.org/d3-force
ISC License
1.82k stars 377 forks source link

forceSimulation: number too small: cx="-3.2056569513393925e-168", Safari 15.4 #207

Closed mthiebaux closed 2 years ago

mthiebaux commented 2 years ago

I created the simplest possible program that reproduces the following endlessly streaming error messages in Safari 15.4:

Error: Invalid value for attribute cx="-3.2056569513393925e-168" Error: Invalid value for attribute cy="1.64703629808156e-168"

The program simply creates 2 nodes, with no links, and runs the simulation. After 1 second, I delete one of the nodes. After 10 seconds pass, the errors start streaming. The message stops only when I click on the node with the drag function, which apparently rectifies the values.

I discovered I can fix it in my code like this in the "tick" callback function:

sim.nodes .attr( "cx", d => ( d.x + 0.0 ) ) .attr( "cy", d => ( d.y + 0.0 ) ) // .attr( "cx", d => ( d.x + 0.000000001 ) ) // fixed! // .attr( "cy", d => ( d.y + 0.000000001 ) ) ;

This looks like a JavaScript number parsing problem, where some calculation result is converted to a string, but can't be converted back. The error does not appear in Firefox.

These are the files:

https://github.com/mthiebaux/D3JSdemo/blob/main/isolate_bug.html https://github.com/mthiebaux/D3JSdemo/blob/main/isolate_bug.js

mbostock commented 2 years ago

This is not a bug with d3-force but with using the values directly as SVG attributes. If there’s a fix here, it’s when you set the attributes (in your code, e.g. by calling number.toFixed instead of relying on the implicit number.toString), but it’s surprising to me that Safari errors here and I’d probably just recommend ignoring the errors.

mthiebaux commented 2 years ago

I appreciate the prompt followup, from Mike Bostock himself! I see that it's a browser problem, but the error streams. The reason I brought it here is that the value d.x is generated internally.

In this boilerplate chaining expression, the value d.x is generated within d3-force, not the app: .attr( "cx", d => d.x )

The Safari patch replaces it with this: .attr( "cx", d => d.x.toFixed( 100 ) )

But this suggests that this patch need apply to all other internal dynamic variables which the app doesn't explicitly set, but passes through.

In context:

d3.forceSimulation() .on( "tick", () => { nodes.attr( "cx", d => d.x ) } );