rich-iannone / DiagrammeR

Graph and network visualization using tabular data in R
https://rich-iannone.github.io/DiagrammeR/
Other
1.7k stars 248 forks source link

Illustrating nodes with emoji/image/icon? #295

Open maelle opened 6 years ago

maelle commented 6 years ago

Thanks for a great package!

I'm trying to illustrate the nodes with emoji/image/icon (any would do) and encounter a few issues. I'm writing a .gv that I then render using the package. Maybe I'm trying to do things that aren't supported (yet) by the package, or maybe they are but by using R syntax directly, not a .gv file?

Thanks in advance for any guidance.

rich-iannone commented 6 years ago

Hi Maëlle! Glad you like the package and thanks for the good words!

The short answer to your question is that currently (and I do hope to change this!) images or icons as nodes do not work.

Fontawesome icons and local images used to work (way back...) but because a dependency that enabled those icons was determined by CRAN to be difficult to install for Linux users, I had to remove support for FontAwesome and local images. Graphviz generally has support for images in nodes but my version of viz.js does not have that functionality.

I do think that this should all come back (along with much-needed documentation updates, since they are very stale), and some new packages and new ideas could make this possible. I'm going to work on this next.

maelle commented 6 years ago

Thanks for your answer and good luck with the updates! 🙂

rich-iannone commented 6 years ago

Going to add updates here, every now and then, and I believe I made some progress toward a usable method for getting images back into nodes. It'll again involve use of the DiagrammeRsvg pkg (functionality which was spun off into a secondary package due to its dependencies on V8) to extract the SVG from the render process and insert images from an image attribute.

I've been playing around with the output SVG (which is now much faster to generate since I recently upgraded viz.js to a very recent version) and I think this minimal work needs to be done:

Here is an image showing that this approach is possible:

node-image-filter

The nice thing with the approach is that the sizing and centering of the image should work well. I also believe that local images can undergo Base64 encoding and included as an image URI. I'm pretty excited about getting this working well.

maelle commented 6 years ago

🎉👏

RSchwinn commented 6 years ago

Hi Rich, Is it possible to use different fonts for different nodes? In this way, could a font-awesome code be passed as the node name for users with FA installed? Thanks! (also) Rich

AnaBVA commented 5 years ago

Hello Rich, I didn't get it all, I'm sorry. Can you re-explain how DiagrammeRsvg can help to import an image when using DiagrammeR::grViz(".dot")?? Thanks in advance :)

sb3rg commented 4 years ago

Are there any updates to this capability? I'd love to substitute graphs of probability distributions as node images for Bayesian networks.

akshaytuptewar19 commented 1 year ago

library(shiny) library(DiagrammeR) library(visNetwork)

ui <- fluidPage( textInput("node_A", " A:",width = '50px'), textInput("node_B", " B:",width = '50px'),

textInput("node_d", " d:",width = '5px'), grVizOutput("my_graphviz"),

visNetworkOutput("mygraph")

)

server <- function(input, output) { output$my_graphviz <- renderDiagrammeR({

node_A <- as.integer(input$node_A) node_B <- as.integer(input$node_B) node_d<-(input$node_d)

node_c <- node_A + node_B

my_graphviz <- grViz(paste0("digraph {

             graph[rankdir = LR]
              node[shape = ellipse]

d[image ='D:/desktop/images/Screenshot (918).png', label = '", node_d, "',width=5,height=1]

             node[shape = ellipse, style = filled]
             A[label = '", node_A, "']
             B[label = '", node_B, "']
             c[label = '", node_c, "']

             edge[color = black]
             A->d
             B->d
             d->c

             }"))

my_graphviz }) }

shinyApp(ui, server) why image is not visible when i executed this code

akshaytuptewar19 commented 1 year ago

Hi Maëlle! Glad you like the package and thanks for the good words!

The short answer to your question is that currently (and I do hope to change this!) images or icons as nodes do not work.

Fontawesome icons and local images used to work (way back...) but because a dependency that enabled those icons was determined by CRAN to be difficult to install for Linux users, I had to remove support for FontAwesome and local images. Graphviz generally has support for images in nodes but my version of viz.js does not have that functionality.

I do think that this should all come back (along with much-needed documentation updates, since they are very stale), and some new packages and new ideas could make this possible. I'm going to work on this next.

i have added my code at bottom why it is not working?

nickbeeton commented 1 year ago

For those interested, I've written a workaround using JavaScript to plot posterior distributions for each node image (as requested by @sb3rg - though I did it for my own selfish purposes!). This works for my complicated Bayes net, but no promises for anyone else's! The variable networkModel here is a HydeNetwork from the HydeNet package, and posts is a data frame of posteriors with column names equal to the node names (in my case, generated using HydeNet's bindSim function).

The general idea is to let DiagrammeR and htmltools do the work of generating a HTML file, then to write a script to modify the HTML with images after the fact. I've used SVGs here but nothing stopping you from using <img src="file.jpg"> etc.

@rich-iannone if you don't think you'll get to the image capability anytime soon (and given the age of this thread I'm guessing you won't 😆), and you think this workaround is worth using, I can put something more general together and make a branch or something?

# set individual posterior plots to be 7 inches wide, 4 inches high
HydePlotOptions(variable = list(width = "7", height = "4", shape = "rectangle", fontcolor = "black", color = "black"), 
                determ = list(width = "7", height = "4", shape = "rectangle", fontcolor = "black", color = "black"), 
                decision = list(width = "7", height = "4", shape = "rectangle", fontcolor = "black", color = "black"), 
                utility = list(width = "7", height = "4", shape = "rectangle", fontcolor = "black", color = "black"))
# Draw an initial plot with nodes as correct-sized rectangles (but not to screen)
test = plot(networkModel)
# Extract the node names from the plot
pattern = 'label[ ]*=[ ]*"([^"]*)"'
labels = gsub(pattern, "\\1", regmatches(test$x$diagram, gregexpr(pattern, test$x$diagram))[[1]], perl = TRUE)
# create a temporary file to store each SVG output temporarily
tmp = tempfile(fileext = ".svg");
# create a character array to store SVGs before adding to HTML
svgs = character(length(labels))
# Loop over each node name
for (i in 1:length(labels)){
  # create 7 inch x 4 inch SVG file with suitable plotting parameters
  svg(tmp, 7, 4); par(lwd = 2, cex = 1.5, mar = c(3,4,2,1)+0.1); 
  # plot density of posterior
  plot(density(posts[[labels[i]]]), xlab = "", main = labels[i]); dev.off()
  # Read created SVG file, remove first line with xml tag, make into one char string
  X = readLines(tmp); X = paste0(X[-1], collapse = "\n"); 
  # SVG uses pts, remove these to make consistent with network plot which uses px (the default unit)
  svgs[i] = gsub("([0-9]+)pt", "\\1", X, perl = TRUE)
  # Give labels for "glyph" references in SVG files unique names for each SVG
  # as they'll all be lumped together in the same HTML file
  svgs[i] = gsub("glyph", paste0("node", i, "-glyph"), svgs[i])
}
# Convert to HTML tags
tags = htmltools::as.tags(test, standalone = TRUE)
# Create JavaScript script to add to HTML, to be run after all page elements are loaded
script = '<script>document.addEventListener("DOMContentLoaded", function(event) {'
# Loop over each node name,
# adding a script to extract the coordinates of the rectangle in the network
# and replace the HTML node element with the SVG plot of the posterior
# at those coordinates
for (i in 1:length(labels))
  script = paste0(script, 
                  'xmin = document.getElementById("node', i, '").getElementsByTagName("polygon")[0].points[1].x;
xmax = document.getElementById("node', i, '").getElementsByTagName("polygon")[0].points[0].x;
ymin = document.getElementById("node', i, '").getElementsByTagName("polygon")[0].points[0].y;
ymax = document.getElementById("node', i, '").getElementsByTagName("polygon")[0].points[2].y;
document.getElementById("node', i, '").innerHTML = 
`<svg x = "\` + xmin + `" y = "` + ymin + `" width = "` + (xmax - xmin) + 
`" height = "` + (ymax - ymin) + `" viewbox = "0 0 504 288">
', svgs[i], '</svg>`\n')

# Add closing parentheses, braces and tags to script
script = paste0(script, ';});</script>')
# Add script to HTML tags
tags[[4]] = HTML(script)
# Run HTML in RStudio viewer (or browser if desired)
filename = htmltools::html_print(tags)