the sankeyNetwork from networkD3 package is pretty clever most of the times at positioning the nodes, but there are occasions that I want to place the node to another location horizontally.
There are also other occasion that I want to vertically move the start point of an edge. Take the following screen shot for example:
For the node, I want to move the B3 on the 2nd column from the right to the 4th column from the left. Similarly, I want to move the B4 from the 2nd column from the right to the 5th column from the left.
For the edge, I want to move the start point of the very first edge (B1->B11) to the low end of B1.
My guess is that I need to make some changes to the source code and manually put in the specific positions. I saw this post for js How to tune horizontal node position in d3 sankeyjs, but I'm not sure what to do in R, beside I did not find any post talking about changing the edge positions.
Here is the replicable data and code:
load(url("https://github.com/bossaround/question/raw/master/sankeyexample.RData"))
# nn is the node, ee is the edge, now create the link (ll)
ll <- inner_join(ee, nn, by = c("N1"="name")) %>%
rename(source_ID = ID) %>%
inner_join(nn, by = c("N2"="name")) %>%
rename(target_ID = ID)
# Create Sankey Plot
sankeyNetwork(
Links = ll,
Nodes = nn,
Source = "source_ID",
Target = "target_ID",
Value = "Value",
NodeID = "newname",
fontSize = 12,
nodeWidth = 40,
nodePadding = 20,
NodeGroup = "newname"
)
Thank you in advance!
You can manually adjust the vertical position of nodes, so if you drag the top-right B11 node to the bottom, the link line from the top-left B1 node will automatically adjust to the bottom of that B1 node.
To achieve what's described in the answer to How to tune horizontal node position in d3 sankey.js?, all you have to do is add the sinksRight = FALSE parameter to your sankeyNetwork() call, though I don't think that achieves what you actually want.
Manually adjusting the horizontal position of nodes is not currently possible. Adjusting them individually in code would be very tedious, but you could add additional JS to run after it loads to individually adjust the positioning of specific nodes with some convoluted JS code like d3.selectAll(".node").filter(function(d) { return d.name == "B3" }).attr("transform", function(d) { return "translate(" + (d.x - 300) + "," + d.y + ")"; })
Related
I would like to create a net-graph, that will have 2 nodes in the middle and the rest of the nodes surround them. The edges go from nodes "A" and "B" to the rest of nodes but they are not connected with each other.
I found that layout "star" from igraph package will suit me probably the most.
It is theoretically possible to add more nodes in the center of "star" (manual page) but it does not work for me, as regardless specifying two nodes in center parameter there is still only one.
#data
set.seed(1)
name <- LETTERS
a <- data.frame(from = "A", to = name)
b <- data.frame(from = "B", to = name)
sample <- rbind(a,b)
sample <- sample[-c(1,2,27,28), ] #please note removed edges between A-A, A-B, B-B, and B-A
#plot
g <- graph_from_data_frame(sample)
plot(g, layout = layout_as_star(g, center = V(g)[c("A", "B")]) )
I do not think that layout_as_star allows multiple centers.
The center argument on the help page that you refer to only
allows you to specify which one node is the center, not multiple
centers. So to get what you want, you need to do more of the
layout yourself. Here is one way to get a graph in the form that
you wanted. I use layout_as_star to lay out all of the nodes
except for one of the centers. But we do not want both "centers"
at the exact center of the circle or they would overlap. So I move
the center and make a spot for the second "center".
library(igraph)
s1 = make_star(25, mode="out")
V(s1)$name = LETTERS[-2]
s2 = make_star(25, mode="out")
V(s2)$name = LETTERS[-1]
LO1 = layout_as_star(s1)
TwoCenters = union(s1, s2)
LO2 = LO1
LO2[1,] = c(-0.2, 0)
LO2 = rbind(LO2, c(0.2,0))
plot(TwoCenters, layout= LO2)
I have the following dataframe:
df<-data.frame(consumed= c("level1_plt1", "level1_plt2", "level1_plt3", "level1_plt3","level1_plt2","level1_plt4","level1_plt5","level1_plt5","level1_plt6","level1_plt7","level1_plt8","level1_plt9","level1_plt10","level1_plt10","level1_plt1","level1_plt1","level1_plt6","level1_plt6","level1_plt9","level1_plt9","level1_plt11","level1_plt11","level1_plt11","level2_lep1","level2_lep4","level2_lep3"),consumer=c("level2_lep1","level2_lep2","level2_lep3","level2_lep2","level2_lep4", "level2_lep4","level2_lep5","level2_lep5","level2_lep6","level2_lep7","level2_lep8","level2_lep9","level2_lep10","level2_lep10","level2_lep8","level2_lep8","level2_lep1","level2_lep1","level2_lep3","level2_lep11","level2_lep12","level2_lep13","level2_lep13", "level3_pst1","level3_pst3","level3_pst4"))
And have preformed the following steps to get an igraph tripartite output:
links<-
df%>%
group_by(consumed, consumer) %>%
summarize(freq=n())
g<- graph_from_data_frame(d=links,directed=FALSE)
layer <- rep(2, length(V(g)$name))
layer[grepl("level1_",V(g)$name)]=1
layer[grepl("level3_",V(g)$name)]=3
names<- V(g)$name
names<-sub("level2_","", names)
names<-sub("level3_","", names)
names<-sub("level1_","", names)
V(g)$name = names
layout = layout_with_sugiyama(g, layers=layer)
E(g)$width <- E(g)$freq
V(g)$vertex_degree <- degree(g)*7
plot(g,
layout=cbind(layer,layout$layout[,1]),edge.curved=0,
vertex.shape=c("square","circle","square")[layer],
vertex.frame.color = c("darkolivegreen","darkgoldenrod","orange3")
[layer],
vertex.color=c("olivedrab","goldenrod1","orange1")[layer],
vertex.label.color="white",
vertex.label.font=2,
vertex.size=V(g)$vertex_degree,
vertex.label.dist=c(0,0,0)[layer],
vertex.label.degree=0, vertex.label.cex=0.5)
And I would like to do two things to adjust the picture, if possible:
Order the layers from the largest shape (highest degree) to smallest shape (smallest degree). For example, in the green layer the order could be as follows: plt9, plt3,plt2,plt11,plt6,plt1,plt7,plt5,plt4,plt10,plt8.
Create space between the shapes so that there is no overlap (e.g. lep3 and lep4). I like the current sizes/proportions so I am opposed to making shapes smaller to create space between shapes.
Flip the graph and vertex font 90 degrees counter-clockwise so that from bottom to top it would be in the order green layer-->yellow layer-->orange layer. (I guess it is always an option to rotate vertex text and I can rotate the image in word or ppt.)
I know this question is old, but I hope that the answer will help someone.
Rather than using layout_with_sugiyama, It may be easiest to do this with
a custom layout. It is not very hard to do so. You already constructed the
horizontal position with your layer variable. To get the vertical positions,
we need to order the vertices by size (vertex_degree) and then allow shape proportional to the size, so we will set the height using cumsum on the vertex_degrees within each layer. After I make the layout the complex call to plot is the same as yours except
that I swap my custom layout for your call to sugiyama.
MyLO = matrix(0, nrow=vcount(g), ncol=2)
## Horizontal position is determined by layer
MyLO[,1] = layer
## Vertical position is determined by sum of sorted vertex_degree
for(i in 1:3) {
L = which(layer ==i)
OL = order(V(g)$vertex_degree[L], decreasing=TRUE)
MyLO[L[OL],2] = cumsum(V(g)$vertex_degree[L][OL])
}
plot(g,
layout=MyLO, edge.curved=0,
vertex.shape=c("square","circle","square")[layer],
vertex.frame.color = c("darkolivegreen","darkgoldenrod","orange3")[layer],
vertex.color=c("olivedrab","goldenrod1","orange1")[layer],
vertex.label.color="white",
vertex.label.font=2,
vertex.size=V(g)$vertex_degree,
vertex.label.dist=0,
vertex.label.degree=0, vertex.label.cex=0.5)
I am using the leaflet package for R to plot circles in sizes and colors that are determined by a set of variables. It works well, but not all circles can be selected with the cursor. I cannot show the mouseover tooltip or popup window for circles that are hidden underneath larger circles.
How can I fix that? I already have on/off toggles in my legend to simplify the map and hide categories, but the issue persist when overlapping circles belong to the same category.
A drag-n-drop feature, or a way to cycle through circles under the cursor using the mouse wheel would be perfect, but I am not sure this can be achieved in R.
Thank you.
[Edit] Here is an example:
library(leaflet)
n <- c(50, 100)
coord <- matrix(c(-72.3, -70.3, -72, -70), nrow=2, byrow=T)
map <- leaflet() %>%
addTiles() %>%
setView(lng = -72, lat = -70, zoom = 5)
for (i in 1:length(n)) {
map <- map %>% addCircles(lng = coord[i, 1], lat = coord[i, 2], radius = n[i]*1000, # Radius is proportional to sample size
label=paste0("Mouseover tooltip ", i),
popup=paste0("Popup window ", i),
highlightOptions=highlightOptions(color="black", weight=2))
}
map
Output with circle 2 preventing selection of circle 1 because it is on top:
Radius is a function of sample size in the data set, so I cannot freely manipulate it to avoid overlapping, and cannot reduce it an all circles either because I already have very small samples in the data set. Blue would be one category here (other categories/colors exist in the data set but can toggled off from the legend so it's not an issue when they hide other categories). The circle 1 is smaller than the circle 2 due to a smaller sample.
Of course, here I drew the only two circles using addCircles twice, so I could just decide to plot the smaller circle after the larger one so it's on the top layer. But in my data, there are tens of circles plotted automatically according to the variables and I cannot sort manually the order in which circles from a single category are plotted.
[Edit 2] Just found out that adding sendToBack=T to the highlightOptions=highlightOptions()line helps. It allows cycling through overlapping circles by sending those on the front to the back after they have been hovered by the cursor. However it's not perfect when there are more than 2 circles, because the user would hardly cycle voluntarily using this method, the cursor has to be brought in and out several times until the correct circle is highlighted. Better than nothing though. If anyone has another method to suggest, please don't hesitate.
I am using R to visualize relation between, say, 5-6 different nodes. Now, a graph is probably the best way to do it. The issue is, the edges are way too many. There can be a hundred edges between two vertexes. That makes the graph look very clumsy. I want the edge name to be displayed. With a hundred edge name being displayed, they overlap over each other and hence not comprehensible.
So, I have two questions-
Is there any other way in which I can represent the data? Some kind of chart probably?
I want to export the final output to HTML, which uses d3.js or any other similar library, keeping the edge name and a few other similar information intact. What will be the best plugin to use in that case?
I am using the igraph library to create the graph in R.
I also tried using the networkD3 library to export it to an HTML and make it interactive.
graph <- graph.data.frame(edges, directed = TRUE, vertices = vertex)
plot(graph, edge.label = E(graph)$name)
wc <- cluster_walktrap(graph)
members <- membership(wc)
graph_d3 <- igraph_to_networkD3(graph, group = members)
graph_forceNetwork <- forceNetwork(Links = graph_d3$links, Nodes = graph_d3$nodes,
Source = 'source',
Target = 'target',
NodeID = 'name',
Group = 'group',
zoom = TRUE,
fontSize = 20)
Right now, it is a graph with only two vertex and about 60-70 edges between them. So, I did not use any particular layout.
In a web app, I would like to let the user select a region of interest in a plotted image using the nice box/lasso selection tools of bokeh. I would the like to receive the selected pixels for further operations in python.
For scatter plots, this is easy to do in analogy with the gallery,
import bokeh.plotting
import numpy as np
# data
X = np.linspace(0, 10, 20)
def f(x): return np.random.random(len(x))
# plot and add to document
fig = bokeh.plotting.figure(x_range=(0, 10), y_range=(0, 10),
tools="pan,wheel_zoom,box_select,lasso_select,reset")
plot = fig.scatter(X, f(X))
#plot = fig.image([np.random.random((10,10))*255], dw=[10], dh=[10])
bokeh.plotting.curdoc().add_root(fig)
# callback
def callback(attr, old, new):
# easily access selected points:
print sorted(new['1d']['indices'])
print sorted(plot.data_source.selected['1d']['indices'])
plot.data_source.data = {'x':X, 'y':f(X)}
plot.data_source.on_change('selected', callback)
however if I replace the scatter plot with
plot = fig.image([np.random.random((10,10))*255], dw=[10], dh=[10])
then using the selection tools on the image does not change anything in plot.data_source.selected.
I'm sure this is the intended behavior (and it makes sense too), but what if I want to select pixels of an image? I could of course put a grid of invisible scatter points on top of the image, but is there some more elegant way to accomplish this?
It sounds like the tool you're looking for is actually the BoxEditTool. Note that the BoxEditTool requires a list of glyphs (normally these will be Rect instances) that will render the ROIs, and that listening to changes should be set using:
rect_glyph_source.on_change('data', callback)
This will trigger the callback function any time you make any changes to your ROIs.
The relevant ColumnDataSource instance (rect_glyph_source in this example) will be updated so that the 'x' and 'y' keys list the center of each ROI in the image's coordinates space, and of course 'width' and 'height' describe its size. As far as I know there isn't currently a built-in method for extracting the data itself, so you will have to do something like:
rois = rect_glyph_source.data
roi_index = 0 # x, y, width and height are lists, and each ROI has its own index
x_center = rois['x'][roi_index]
width = rois['width'][roi_index]
y_center = rois['y'][roi_index]
height = rois['height'][roi_index]
x_start = int(x_center - 0.5 * width)
x_end = int(x_center + 0.5 * width)
y_start = int(y_center - 0.5 * height)
y_end = int(y_center + 0.5 * height)
roi_data = image_plot.source.data['image'][0][y_start:y_end, x_start:x_end]
IMPORTANT: In the current version of Bokeh (0.13.0) there is a problem with the synchronization of the BoxEditTool at the server and it isn't functional. This should be fixed in the next official Bokeh release. For more information and a temporary solution see this answer or this discussion.