warship
warship

Reputation: 3024

R networkD3 package: forceNetwork() coloring

I really like how this network looks:

enter image description here

Source: https://christophergandrud.github.io/networkD3/

There are multiple colors for each sector, where each color represents some distinct portion of nodes in the network that are far enough separated from the other nodes.

However, within any one specific sector, I don't like how the src nodes are the same color as the target nodes, and so I asked a question (https://stackoverflow.com/a/35371131/3878253) for how to distinguish the two colors.

Now is there a way that I can expand on this code to get the multiple color scheme back (to distinguish the various sectors from each other) yet retain the nice src/target difference in color? Here is the code:

# Load package
library(networkD3)
library(dplyr) # to make the joins easier

# Create fake data
src <- c("A", "A", "A", "A",
         "B", "B", "C", "C", "D")
target <- c("B", "C", "D", "J",
            "E", "F", "G", "H", "I")
networkData <- data.frame(src, target, stringsAsFactors = FALSE)

nodes <- data.frame(name = unique(c(src, target)), stringsAsFactors = FALSE)
nodes$id <- 0:(nrow(nodes) - 1)


# create a data frame of the edges that uses id 0:9 instead of their names
edges <- networkData %>%
   left_join(nodes, by = c("src" = "name")) %>%
   select(-src) %>%
   rename(source = id) %>%
   left_join(nodes, by = c("target" = "name")) %>%
   select(-target) %>%
   rename(target = id)

edges$width <- 1

# make a grouping variable that will match to colours
nodes$group <- ifelse(nodes$name %in% src, "lions", "tigers")


ColourScale <- 'd3.scaleOrdinal()
            .domain(["lions", "tigers"])
           .range(["#FF6900", "#694489"]);'

forceNetwork(Links = edges, Nodes = nodes, 
             Source = "source",
             Target = "target",
             NodeID ="name",
             Group = "group",
             Value = "width",
             opacity = 0.9,
             zoom = TRUE,
             colourScale = JS(ColourScale))

Upvotes: 4

Views: 5011

Answers (2)

CJ Yetman
CJ Yetman

Reputation: 8848

After re-reading your question and response, I think what you really want to know is... how to cluster your data into groups. Once you've defined the clusters, you can set the group value for each node in the nodes data frame as the name/id of its cluster, and then networkD3 will color them accordingly.

Further into your question, it's not exactly clear how you want to combine 'color source and target nodes differently' and 'color nodes according to their cluster' simultaneously... those goals obviously conflict because clusters contain both source and target nodes. An additional problem with your question is that in these types of networks, it's possible for a node to be both a source and a target node, so you would have to be more clear about how you want those to be colored. I could make an assumption that you define a 'target' node as one that is only linked to one other node (technically, whether a node is a source or a target is determined by the direction of the link between nodes), but that's not necessarily the case (I can imagine other interpretations you might have).

To do the clustering, you want to look into the igraph package and its numerous clustering functions. for example...

using the Les Misérables data included in the networkD3, let's backtrack till we have just a two column data frame with source and target characters...

library(igraph)
library(networkD3)

data(MisLinks)
data(MisNodes)

lesmis <- data.frame(source = MisNodes$name[MisLinks$source + 1], 
                     target = MisNodes$name[MisLinks$target + 1],
                     stringsAsFactors = F)

now, using that you can use igraph to calculate the clusters, and then put them back together to plot in networkD3...

lesmis <- graph_from_data_frame(lesmis)
wc <- cluster_walktrap(lesmis)
members <- membership(wc)

lesmis <- igraph_to_networkD3(lesmis, group = members)

forceNetwork(Links = lesmis$links, Nodes = lesmis$nodes, 
             Source = 'source', Target = 'target', 
             NodeID = 'name', Group = 'group', opacity = 1)

now if you want to make 'target' nodes (those that have only one link) a different color also, you could figure out which nodes only appear once in the list of sources and targets, and set their group value in the nodes data frame to something unique...

allnodes <- c(lesmis$links$source, lesmis$links$target)
targetids <- allnodes[!duplicated(allnodes) & ! duplicated(allnodes, fromLast = T)]
lesmis$nodes$group[targetids + 1] <- 'target'

forceNetwork(Links = lesmis$links, Nodes = lesmis$nodes, 
             Source = 'source', Target = 'target', 
             NodeID = 'name', Group = 'group', opacity = 1)

Upvotes: 2

CJ Yetman
CJ Yetman

Reputation: 8848

The group column in the Nodes data frame is what determines the color. You can set the group value for each node (manually or otherwise), and networkD3 will color each group of nodes according to a palette of colors.

For instance...

edges <- read.table(header = T, text = '
source target width
0      1     1
0      2     1
0      3     1
0      4     1
1      5     1
1      6     1
2      7     1
2      8     1
3      9     1
')

nodes <- read.table(header = T, text = '
name id  group
A    0   panther
B    1   leopard
C    2   tiger
D    3   lion
J    4   panthercub
E    5   leopardcub
F    6   leopardcub
G    7   tigercub
H    8   tigercub
I    9   lioncub
')

forceNetwork(Links = edges, Nodes = nodes, 
             Source = "source",
             Target = "target",
             NodeID ="name",
             Group = "group",
             Value = "width",
             opacity = 0.9,
             zoom = TRUE,
             colourScale = JS("d3.scaleOrdinal(d3.schemeCategory10);"))

Upvotes: 1

Related Questions