javascriptrhtmlwidgetsnetworkd3

networkD3 package: forceNetwork() - setting the linkDistance


I was trying to use the forceNetwork() function for my node plots. However, I can't seem to set the linkDistance. For example, in the attached image, I want to set all the green nodes equidistant from the center. Currently, they appear to be farther away depending on how many pink nodes are attached to them. I followed the suggestion given here, but couldn't get it to work.

Here is my code (sorry for the ramshackle code blocks):

library(networkD3)
library(htmlwidgets)

NUM_PNODES <- 100
NUM_DNODES <- 8
LINKDIST1 <- 1
LINKDIST2 <- 5
N_SIZE1 <- 2

### Parent edges and nodes
nodes.ID <- (1:NUM_PNODES)-1
nodes <- paste0('p',1:NUM_PNODES)

source1 <- rep(nodes[1],max(nodes.ID))
target1 <- nodes[-1]
source_idx1 <- nodes.ID[1]
target_idx1 <- nodes.ID[-1]
linkdist1 <- LINKDIST1
links.df <- data.frame(source1,target1,source_idx1,target_idx1,linkdist1)

type <- rep('parent',NUM_PNODES)
group <- 0
nodesize <- N_SIZE1
node.df <- data.frame(nodes,group,type,nodesize)

source1 <- target1 <- source_idx1 <- target_idx1 <- linkdist1 <- tdat <- c()

for(i in 1:NUM_DNODES){
  if(i==1){
    NCOUNT <- max(links.df$target_idx1) + 1
  }

  ### clone edges and nodes
  source1 <- 'p1'
  target1 <- paste0('d',i)
  source_idx1 <- 0
  target_idx1 <- NCOUNT
  linkdist1 <- LINKDIST2
  tdat <- data.frame(source1,target1,source_idx1,target_idx1,linkdist1)
  ndat <- data.frame(target1,i,'clone',N_SIZE1)
  colnames(ndat) <- c('nodes','group','type','nodesize')
  links.df <- rbind(links.df,tdat)
  node.df <- rbind(node.df,ndat)

  ### Daughter edges and nodes
  source1 <- rep(paste0('d',i),i)
  target1 <- paste0('d',i,'.',1:i)
  source_idx1 <- rep(NCOUNT,i)
  target_idx1 <- (NCOUNT+1):(NCOUNT+i)
  NCOUNT <- NCOUNT+i+1
  linkdist1 <- LINKDIST1
  tdat <- data.frame(source1,target1,source_idx1,target_idx1,linkdist1)
  ndat <- data.frame(target1,i,'daughter',N_SIZE1)
  colnames(ndat) <- c('nodes','group','type','nodesize')
  links.df <- rbind(links.df,tdat)
  node.df <- rbind(node.df,ndat)

}



ColourScale <- 'd3.scaleOrdinal()
    .domain(["parent", "clone", "daughter"])
   .range(["blue", "green", "red"]);'

fn <- forceNetwork(Links = links.df, Nodes = node.df, 
             Source = 'source_idx1', Target = 'target_idx1', 
             NodeID = 'nodes', Group = 'type',
             bounded = TRUE, opacityNoHover = TRUE, zoom = TRUE,
             colourScale = JS(ColourScale),
             linkDistance=JS('function(d) {', 'return d.linkdist1;', '}'))

fn$x$links$linkdist1 <- links.df$linkdist1
fn

Thanks for your help!

forceNetwork plot


Solution

  • This took me a bit to figure out. This is a force diagram. So the forces acting on the nodes have a lot to do with their placement. I get the impression that you're not using this graph for the force aspect, specifically.

    If you were using the JS version, you would just need to add the parameter linkStrength. However, it doesn't appear that that was written into the R version. I couldn't figure out how to add it to another call, onRender, or anything else I could think of. I tried to reduce the energy, too.

    I did make it work, but you may not want to go this route. If you would like to have the parameter linkStrength, you use the repo I made to answer this question. It will look, feel, and act just like the Cran version. The only difference is that this package has one more parameter in the function forceNetwork. (In the package, I really only made changes to the forceNetwork.js and forceNetwork.R.)

    To use this package:

    devtools::install_github("fraupflaume/networkD3")
    

    When you call the library, you do it the same way you did before.

    library(networkD3)
    

    If you want to use the Cran version later, just install it again. Installing it will write over the previous version, regardless of whether it was from Cran or Github.

    Using the data code exactly as you've provided in your question, here are some examples of the difference between having no control over link strength and using that parameter.

    Essentially, your original graph; no control over strength established.

    forceNetwork(Links = links.df, Nodes = node.df, 
                 Source = 'source_idx1', Target = 'target_idx1', 
                 NodeID = 'nodes', Group = 'type',
                 bounded = TRUE, opacityNoHover = TRUE, zoom = TRUE,
                 colourScale = JS(ColourScale))
    

    enter image description here




    In this graph, strength is defined.

    forceNetwork(Links = links.df, Nodes = node.df, 
                 Source = 'source_idx1', Target = 'target_idx1', 
                 NodeID = 'nodes', Group = 'type',
                 bounded = TRUE, opacityNoHover = TRUE, zoom = TRUE,
                 colourScale = JS(ColourScale),
                 linkStrength = 1)
    

    enter image description here




    In this graph, the strength and length are specified.

    This final example reflects the variation of link distances that you were trying to achieve. However, I didn't use 1 and 5. I know the description states the values are relative...but they appear to be quite literal.

    forceNetwork(Links = links.df, Nodes = node.df, 
                 Source = 'source_idx1', Target = 'target_idx1', 
                 NodeID = 'nodes', Group = 'type',
                 bounded = TRUE, opacityNoHover = TRUE, zoom = TRUE,
                 colourScale = JS(ColourScale),
                 linkStrength = 1,
                 linkDistance = JS("
                 function(d){
                   return ((d.source.group === 'parent' && d.target.group === 'clone') ? 75 : 50 );
                 }"))
    

    enter image description here