2

I have a Sankey network from networkd3 rendering inside a Shiny app showing transitions within a year, with various options for users to filter data. I encountered an issue with the links not connecting fully with the nodes and running into other links, specifically when users select a small subset of groups causing the plot to be longer horizontally than it is vertically (i.e., showing more years than groups in each year - see example below).

Example of rendering issue

I could not locate a fix on Stack Overflow. I believe the issue may be in the underlying Javascript, so I am out of my depth. Any help would be super appreciated. Thanks! Reprex below.

## Create links dataframe 
# Note that the 0 count links are for displaying nodes in the proper year
links <- data.frame(
  from = c(
    "A 2015", "A 2016", "A 2017", "A 2018", "A 2019",
    "A 2015", "A 2016", "A 2017", "A 2018"
  ),
  to = c(
    "B 2016", "B 2017", "B 2018", "B 2019", "B 2020",
    "A 2016", "A 2017", "A 2018", "A 2019"
  ),
  count = c(48, 36, 31, 46, 24, 0, 0, 0, 0)
)

## Create node dataframe
nodes <- data.frame(
  name = unique(
    c(
      as.character(links$from), 
      as.character(links$to)
    )
  )
)

## Add node indices to link data (zero-indexed)
links$source = match(links$from, nodes$name)-1
links$target = match(links$to, nodes$name)-1

## Render Sankey
library(networkD3)
sankeyNetwork(
  Links = links,
  Nodes = nodes,
  Source = "source",
  Target = "target",
  Value = "count",
  NodeID = "name",
  iterations = 0, 
  sinksRight = FALSE
)
CJ Yetman
  • 8,373
  • 2
  • 24
  • 56
Josh C
  • 43
  • 7

1 Answers1

1

The links that D3's sankey module makes are SVG paths with a Bézier curve. In your example, the browser's SVG engine is trying to make a curved path within a very tight horizontal space with a very thick (stroke-width) path. Compare for example the following two SVGs (where the only difference is the stroke-width)...

<svg style="width: 100%; height: 100%;">

    <path d="M 0 50 C 25,50 25,100 50,100" style="stroke-width: 10px; fill: none; stroke: rgb(0, 0, 0); stroke-opacity: 0.2;"></path>
    
    <g transform="translate(100,0)">
    
        <path d="M 0 50 C 25,50 25,100 50,100" style="stroke-width: 80px; fill: none; stroke: rgb(0, 0, 0); stroke-opacity: 0.2;"></path>
    
    </g>
    
</svg>

By default, networkD3's sankey plot tries to use as much horizontal and vertical space as its container allows, which in your case resulted in a less than optimal height to width ratio, which makes the horizontal distance between the nodes relatively small, and the height of the nodes (and therefore the thickness of the links) relatively large.

You can "fix" this by changing the size of the container and refreshing (i.e. resizing your browser window, or changing the HTML/CSS attributes of the container), or by specifying a width and height so that the ratio works for the data you have, for instance...

sankeyNetwork(
  Links = links,
  Nodes = nodes,
  Source = "source",
  Target = "target",
  Value = "count",
  NodeID = "name",
  iterations = 0, 
  sinksRight = FALSE,
  height = 250,
  width = 1000
)

enter image description here

CJ Yetman
  • 8,373
  • 2
  • 24
  • 56
  • Thank you, very clear and helpful answer. So I am thinking the best way to implement this solution in an interactive Shiny app (where the defaults usually work well except in this situation when the user displays a lot of years and filters out most of the groups shown in the nodes) would be to program the width and height to be a function of the years (width) by groups (height). Does that make sense or is there a better way? – Josh C Aug 11 '20 at 19:46
  • Yeah, if that’s do-able, that sounds like a good strategy to me. – CJ Yetman Aug 12 '20 at 15:12