1

I have the following plot, that I convert to Plotly that I am trying to add a stat_density_2d function to in order to show a 'heat map' of sorts within the graphs. When I simply add the stat_density_2d function it works fine, but not when I convert to plotly, does anyone know of a way to have a stat density that can convert to plotly

Graph <- ggplot(aes(text = paste("Var1:", round(Var1,1), "<br>Var2:", round(Var2,1), "<br>Var3:", round(Var3,1), "<br> Var4:", Var4,
                     "<br>Var5:", round(Var5,1), "<br>Var6:", round(Var6,1)))) +
    
    geom_point(size = .5, x = Var5, y = Var6) +
    scale_fill_gradientn(colours = r, trans = "log") +
    facet_grid(Var7 ~ Var1) +
    geom_path(aes(x, y), data = zone, lwd = 2, col = "black", alpha = .3) +
    stat_density_2d_filled(aes(fill = after_stat(level)), alpha = 0.3) +
    xlim(-3, 3) +
    ylim(-1.5, 6.5)

Graph_Plotly <- ggplotly(Graph)

In this I am trying the 'stat_density_2d_filled', but have had no luck.

1 Answers1

0

It looks like you're new to SO; welcome to the community! You've gotten off to a great start by including the code you used and the issue you ran into. If you want great answers quickly, it's best to make your question reproducible. This includes sample data like the output from dput() or reprex::reprex(), libraries you are using. Check it out: making R reproducible questions.

Because I don't have the data you're working with, I've used mtcars. You'll have to adjust the code to match the data you're working with.

I want to point out a few things that might be useful.

  • some ggplot2 layers aren't built into ggplotly; one such layer is stat_density_2d_filled (when you run ggplotly here, you should have seen a warning in the console, if you enter warnings() you would see a message that tells you why it didn't do what you expected and

  • Plotly has something similar which can be called with the argument `type = "histogram2dcontour"; however, I don't feel that this is a very accurate representation of density (no matter how I set the available parameters)

  • Plotly area charts can be used along with the data that was created when you made your ggplot2 plot to recreate this in Plotly; however, it's important to note that whenever you're using fill in Plotly it gets overly helpful and makes extremely poor guesses as to what you're trying to accomplish...so baby steps for Plotly here (I do want to mention that in most aspects of plotting, Plotly is a phenomenal tool...)

Using ggplot2 to get density data

First, I've made a simplified ggplot2 density plot similar to the plot in your question.

library(tidyverse)
library(plotly)

data("mtcars")

ggplot(mtcars, aes(x = mpg, y = cyl)) + geom_point() + 
  stat_density_2d_filled(aes(fill = after_stat(level)))

Next, you'll collect the data from this plot. Currently, you have two layers: geom_point and stat_density_2d_filled. That means when we collect the data from this plot, we'll have two data frames in the order you've plotted them. So, in this case, the first will be the scatter plot, and the latter will be the density plot.

You could add as many layers as you want, just keep in mind that we will be using the density plot data.

BTW: last_plot() is in Plotly and ggplot2's libraries, so it's important to keep the package name so you've captured the right plot.

# using ggplotly here won't work; stat_density.. is not implemented
         # workaround:
gg <- ggplot2::last_plot() %>% ggplot_build() # capture plot

ggd <- gg$data                                         # capture list of data frames
# [[1]] data is the scatter plot; [[2]] data is the density plot

Create the contour plot

Next, create the contours using the data taken from ggplot2.

# create contour/shaded area plot using ggplot2 data 
plt <- plot_ly(type = "scatter", mode = "lines", 
               split = ~subgroup,                  # keeps shading correct
               legendgroup = ~level,               # consolidate legend
               name = ~level,                      # legend name
               fill = "tonext",                    # connect to next coordinate for "fill"
               data = ggd[[2]], x = ~x, y = ~y, 
               color = ~level, colors = ~fill,     # legend: level, literal colors: fill
               line = list(dash = "3px", width = .5)) # matches ggplot2 a bit better

Modify the contour plot

The colors, legend, and scatter points will be taken care of next. I've written a function that goes through all of the data traces (a trace in Plotly is equivalent to a layer in ggplot2). I've added a lot of comments to explain what's happening and why. If you really wanted to understand what this function does, look at plt before and after this function is used.

# since area plots are pretty finicky in Plotly, and Plotly loves to have the same
#   legend entry over, and over, and over... and over again, the following function
#   consolidates the legend, sets the `fillcolor`, 
#   and makes the contour lines light gray (as in ggplot2)

fixer <- function(pltr) {
  pltr <- plotly_build(pltr)                       # build plot to view/mod data
  lgs <- unique(ggd[[2]]$level) %>% as.character() # fix legend: 1 each name
  invisible(lapply(1:length(pltr$x$data), function(i) { # each trace
    nm <- as.character(pltr$x$data[[i]]$name)           # get name (level)
    if(any(lgs %in% nm)) {                              # if new name, keep in legend
      pltr$x$data[[i]]$showlegend <<- T
      lgs <<- lgs[!lgs %in% nm]                         # rem name from list
    } else {
      pltr$x$data[[i]]$showlegend <<- F                 # rem from legend
    }                                                   # collect proper fill color
    col <- filter(ggd[[2]], level == nm) %>% select(fill) %>% unique() %>% as.character()
    pltr$x$data[[i]]$fillcolor <<- col                  # update fill
    pltr$x$data[[i]]$line$color <<- "lightgray"         # to match ggplot
  }))
  pltr                                                  # return updated plot
}
plt1 <- plt %>% fixer() # fixer to mod plot BEFORE adding markers

Lastly, add the scatter points

It's time to create and add the markers...and that's it.

# add markers (so lines, fill, markers don't collide--- plotly tries to "help" --- UGH)
p <- plot_ly(type = "scatter", mode = "markers", color = I("black"),
             data = ggd[[1]], x = ~x, y = ~y, showlegend = F) %>% plotly_build()

plt1$x$data <- append(plt1$x$data, p$x$data[1], after = 0) # combine areas/markers
plt1 # check it out!

enter image description here

Summary

Here's all that same code you've already seen, but altogether (easier copy + paste)

library(tidyverse)
library(plotly)

data("mtcars")

ggplot(mtcars, aes(x = mpg, y = cyl)) + geom_point() + 
  stat_density_2d_filled(aes(fill = after_stat(level)))

# using ggplotly here won't work; stat_density.. is not implemented
         # workaround:
gg <- ggplot2::last_plot() %>% ggplot_build() # capture plot

ggd <- gg$data                                         # capture list of data frames
# [[1]] data is the scatter plot; [[2]] data is the density plot

# create contour/shaded area plot using ggplot2 data 
plt <- plot_ly(type = "scatter", mode = "lines", 
               split = ~subgroup,                  # keeps shading correct
               legendgroup = ~level,               # consolidate legend
               name = ~level,                      # legend name
               fill = "tonext",                    # connect to next coordinate for "fill"
               data = ggd[[2]], x = ~x, y = ~y, 
               color = ~level, colors = ~fill,     # legend: level, literal colors: fill
               line = list(dash = "3px", width = .5)) # matches ggplot2 a bit better

# since area plots are pretty finicky in Plotly, and Plotly loves to have the same
#   legend entry over, and over, and over... and over again, the following function
#   consolidates the legend, sets the `fillcolor`, 
#   and makes the contour lines light gray (as in ggplot2)

fixer <- function(pltr) {
  pltr <- plotly_build(pltr)                       # build plot to view/mod data
  lgs <- unique(ggd[[2]]$level) %>% as.character() # fix legend: 1 each name
  invisible(lapply(1:length(pltr$x$data), function(i) { # each trace
    nm <- as.character(pltr$x$data[[i]]$name)           # get name (level)
    if(any(lgs %in% nm)) {                              # if new name, keep in legend
      pltr$x$data[[i]]$showlegend <<- T
      lgs <<- lgs[!lgs %in% nm]                         # rem name from list
    } else {
      pltr$x$data[[i]]$showlegend <<- F                 # rem from legend
    }                                                   # collect proper fill color
    col <- filter(ggd[[2]], level == nm) %>% select(fill) %>% unique() %>% as.character()
    pltr$x$data[[i]]$fillcolor <<- col                  # update fill
    pltr$x$data[[i]]$line$color <<- "lightgray"         # to match ggplot
  }))
  pltr                                                  # return updated plot
}
plt1 <- plt %>% fixer() # instantiate fixer to mod plot BEFORE adding markers

# add markers (so lines, fill, markers don't collide--- plotly tries to "help" --- UGH)
p <- plot_ly(type = "scatter", mode = "markers", color = I("black"),
             data = ggd[[1]], x = ~x, y = ~y, showlegend = F) %>% plotly_build()

plt1$x$data <- append(plt1$x$data, p$x$data[1], after = 0) # combine areas/markers
plt1 # check it out!

If you run into any problems or have any questions, let me know.

Kat
  • 15,669
  • 3
  • 18
  • 51