Unable to vary linkDistance in network graph created using forceNetwork of networkD3

邮差的信 提交于 2020-06-17 04:07:20

问题


I am trying to create a force directed network graph in R using the networkD3 package. Everything works well ...

devtools::install_github('christophergandrud/networkD3')
library(networkD3)
links2 <- data.frame(
  Source = c(0, 0, 0, 1, 2, 3), 
  Target = c(1, 4, 5, 3, 4, 5), 
  Value = c(1, 9.9, 10, 8.8, 6.6, 7.2))
nodes2 <- data.frame(ID = 0:5, 
  Group = c(1L, 1L, 1L, 2L, 1L, 2L))

# this works
forceNetwork(Links=links2, Nodes=nodes2, 
  Source="Source", Target="Target", Value="Value", 
  NodeID="ID", Group="Group")

... until I start playing with the linkDistance argument. When I set it to what I want, a function of the Value, I get a network diagram that consists of what appears to be a single node in the far upper left corner of the device.

# this doesn't work
forceNetwork(Links=links2, Nodes=nodes2, 
  Source="Source", Target="Target", Value="Value", 
  NodeID="ID", Group="Group",
  linkDistance="function(d) { return d.value; }")

I would appreciate any suggestions as to how to get the length of the links to vary.

I'm using R version 3.1.3 for Windows in R Studio Version 0.98.1103 with package networkD3 version 0.1.2.2. (I originally experienced the problem using networkD3 version 0.1.2.1 from CRAN. So I installed the latest version from GitHub, and had the same problem.)


回答1:


If you want a value passed through from R to JavaScript to be evaluated (i.e. to resolve a string to a function) you enclose it in the htmlwidgets::JS function. For example:

library(htmlwidgets)
forceNetwork(Links=links2, Nodes=nodes2, 
             Source="Source", Target="Target", Value="Value", 
             NodeID="ID", Group="Group",
             linkDistance=JS('function(d) {', 'return d.value;', '}'))



回答2:


The networkD3 package is awesome!!

After an exhaustive investigation, I've tracked down the bug: The HTML/CSS/JavaScript code that is generated by networkD3 seems to depend on d3js, which provides the d3.min.js file that is central to the generated web code. It is inside d3.min.js that the linkDistance feature is coded, and it is there that the bug occurs.

Unfortunately, as the file name indicates, that JavaScript is minified, but you can access the non-minified source from the d3js site, specifically at https://github.com/mbostock/d3/blob/master/d3.js.

If you look at line 6307 (and surrounding context) of d3.js, you'll find this:

force.linkDistance = function(x) {
  if (!arguments.length) return linkDistance;
  linkDistance = typeof x === "function" ? x : +x;
  return force;
};

This function is called at some point during initialization with x being the string that you originally provided in your R call to forceNetwork() (incidentally, that string is embedded in the index.html file that is generated by networkD3 as JSON data, along with all other input data).

To repeat, x is a string. This means typeof(x) will return "string", not "function", as line 6307 is checking for. Hence, the second alternative of the ternary will be invoked, +x, which is JavaScript-style coercion to number, which will result in NaN. Thus, NaN gets propagated through all the link distances and coordinates and everything gets f*!%ed.

This can be fixed by changing it to:

linkDistance = typeof x === "string" ? eval('('+x+')') : +x;

Obviously I changed "function" to "string", but also I had to add an eval() call to actually pull the function definition out of the string so that all future code that works with linkDistance will work, because it all assumes that it's a function, not a string. Finally, I had to concatenate with parentheses to ensure that the function definition is expressionized, otherwise, if you used function as the leading token of your code string (which you did, and they did in the networkD3 documentation, and that's the most sensible thing to do), then it would be taken as a function statement, meaning it would not be parsed as an actual expression statement, and the eval() call would return undefined, and you'd end up getting NaN when the code would eventually try to use the undefined value in arithmetic expressions, leading to the same errors. (See What is the difference between a function expression vs declaration in JavaScript? and https://javascriptweblog.wordpress.com/2010/07/06/function-declarations-vs-function-expressions/ for a couple good sources on this distinction, and there's probably a million other sources out there as well.)

I've tested the above fix on my machine and it works. I suppose this should be reported as a bug to the d3js people, but I feel I've done enough work here and can't be bothered. Feel free to report this to them, using my post here as a reference.



来源:https://stackoverflow.com/questions/30446519/unable-to-vary-linkdistance-in-network-graph-created-using-forcenetwork-of-netwo

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!