3

I am working with ranked-choice voting data in R, and would like to plot it using a Sankey diagram (here is an example). Sankey diagrams default to color-coded nodes for each candidate, but I would like to place an image of each candidate within the nodes instead.

Data and Setup

I have 5 candidates: A, B, C, D, and E, and the ranked preferences of 27 voters (identified through their IDs: 1, 2, 3, ..., 27). This is my initial data:

rcv_data <- 

structure(list(pref_voter_id = 1:27, `1` = c("A", "B", "C", "C", 
"C", "D", "A", "C", "C", "C", "B", "C", "C", "E", "A", "B", "B", 
"A", "B", "C", "C", "C", "B", "E", "D", "B", "E"), `2` = c("C", 
"A", "A", "B", "A", "C", "E", "B", "A", "D", "C", "D", "D", "A", 
"C", "D", "C", "E", "C", "D", "B", "A", "A", "A", "B", "C", "B"
), `3` = c("B", "C", "D", "E", "B", "E", "B", "A", "B", "A", 
"E", "A", "E", "C", "E", "C", "D", "B", "D", "B", "A", "D", "C", 
"C", "E", "D", "D"), `4` = c("E", "E", "B", "A", "D", "B", "D", 
"D", "D", "B", "D", "B", "B", "D", "B", "E", "A", "D", "E", "A", 
"D", "B", "E", "B", "A", "E", "C"), `5` = c("D", "D", "E", "D", 
"E", "A", "C", "E", "E", "E", "A", "E", "A", "B", "D", "A", "E", 
"C", "A", "E", "E", "E", "D", "D", "C", "A", "A")), row.names = c(NA, 
-27L), class = c("tbl_df", "tbl", "data.frame"))

The columns 1, 2, 3, 4, 5 correspond to "first choice", "second choice", ..., "fifth choice" for each voter.

I am using the networkD3 package to create a Sankey diagram in an R Markdown HTML document. To get the data into an appropriate format for the networkD3::sankeyNetwork function (i.e. in terms of nodes and edges), some wrangling is needed using the rcv package and the rcv::makde_d3list function:

library(dplyr)
library(tidyr)
library(rcv)
library(networkD3)

rcv_data %>%
  pivot_longer(2:6, names_to = "vote_rank", values_to = "candidate") %>%
  rcv::rcv_tally(n_winners = 0) %>% # n_winners is 0 for the diagram to display an extra round
  filter(candidate != "NA") %>% # I don't want "NA" to appear in the diagram
  rcv::make_d3list() %>%
  {networkD3::sankeyNetwork(Links = .$values, Nodes = .$names, 
                            Source = "source", Target = "target",
                            Value = "value", NodeID = "candidate", 
                            units = "voters", fontSize = 12, nodeWidth = 50)}

sankey diagram

Help Needed

Now, I have some files A.jpg, B.jpg, C.jpg, D.jpg, and E.jpg, that contain photos of the 5 candidates. I would like the nodes to include these photos of the candidates, instead of simply being color coded, i.e. instead of dark blue nodes I want the image of candidate C, instead of dark orange nodes I want the image of candiate A etc.

There are two avenues I suspect might lead to a solution:

  1. The networkD3::sankeyNetwork only provides functionality for changing the colors of nodes, but there might be other approaches to the problem using some other R package? I know the visNetwork package enables one to add images as nodes, but it doesn't seem to support Sankey diagrams (?).
  2. The htmlwidgets::onRender function enables one to add extra JavaScript code to the graph by saving the graph into an object, e.g. graph <- [code above], and then writing JavaScript like htmlwidgets::onRender(graph, JS(...)). However, I'm not sure what should go in the ... part - how can I access the nodes and attach them to the appropriate images by group?
CJ Yetman
  • 8,373
  • 2
  • 24
  • 56
Count Orlok
  • 997
  • 4
  • 13
  • The vast majority of your question is totally irrelevant to the problem. I would suggest editing down your question so that it focuses precisely on the problem you’re trying to solve, that way it will be easier and faster for others to give an answer. – CJ Yetman Sep 13 '20 at 11:42

1 Answers1

0

Here is an example of adding an image to each node group in the SVG. There would still be many issues to work out, like appropriate sizing and positioning of the images, but this gives the basis for adding them...

library(networkD3)
library(htmlwidgets)
    
nodes <- data.frame(name = c("a", "b", "c", "d"))
links <- data.frame(source = c(0, 0, 1, 1),
                    target = c(2, 3, 2, 3),
                    value = c(1, 3, 2, 4)
                    )

sn <- sankeyNetwork(Links = links, Nodes = nodes, Source = "source",
                    Target = "target", Value = "value", NodeID = "name")
    
js <- 
  '
    function(el) { 
      d3.select(el)
        .selectAll(".node")
        .append("image")
        .attr("xlink:href", d => "https://pngimg.com/uploads/letter_" + d.name + "/letter_" + d.name + "_PNG4.png")
    }
  '

htmlwidgets::onRender(sn, js)
    
    
    
CJ Yetman
  • 8,373
  • 2
  • 24
  • 56