2

I created a plot using ggplot2 with two different aesthetics for colour and shape. When I convert the ggplot2 plot to plotly via plotly::ggplotly() the different aes seem to create problems.

library(ggplot2) # Version ‘3.4.4’
library(plotly) # Version ‘4.10.4’
library(dplyr)


test_data <- head(iris) |>
  mutate(ID = rank(Sepal.Length),
         category1 = sample(c("A", "B"), 6, replace = TRUE),
         category2 = sample(c("C", "D"), 6, replace = TRUE))
#ggplot
gg_plot <- ggplot(data = test_data) + 
  geom_line(mapping = aes(x = Sepal.Length,
                          y = Sepal.Width,
                          color = category1)) + 
  geom_point(mapping = aes(x = Sepal.Length, y = ID, shape = category2))
gg_plot

#plotly
plotly_plot <- plotly::ggplotly(gg_plot) 
plotly_plot

ggplot2 plot:

ggplot2_plot

plotly plot:

plotly_plot

The same or similar problems where already reported a few years ago:

https://github.com/plotly/plotly.R/issues/572

Convert ggplot2 graph to plotly - legend, labels and values

However, the workarounds either do not work for my case or seem very awkward to implement within a dynamic function.

Does anyone know if there is a somewhat easy way to convert a ggplot2 plot with multiple aesthetics to a plotly plot while keeping the legends?

Edit: I phrased my question wrong. I want to keep the legends and the displayed category values (e.g. "C" instead of "(C,1)").

0

1 Answer 1

1

plotly::ggplotly falsely groups legends and omits their titles. Best you just add the traces in "bare" R {plotly} as described here.

But if you don't want that, you can try this hacky approach:

  1. Splitting the legend title strsplit(plotly_plot$x$layout$legend$title$text, "<br />")[[1]] conveniently gives us all legend groups
  2. iterate over each trace and get the legend group title. For this I use the hovertext plotly_plot$x$data[[i]]$text[1] and see which category is inside
  3. if the group title is the same as before, omit it, if not set it as legendgrouptitle of that trace
library(ggplot2)
library(plotly)
library(dplyr)

gg <- head(iris, 12) |> 
  mutate(
    ID = rank(Sepal.Length),
    category1 = sample(c("A", "B"), 12, replace = TRUE),
    category2 = sample(c("C", "D"), 12, replace = TRUE),
    category3 = sample(c("E", "F", "G"), 12, replace = TRUE),
    category4 = sample(c("H", "I"), 12, replace = TRUE)
  ) |>
  ggplot(aes(x = Sepal.Length)) + 
  geom_line(aes(y = Sepal.Width, color = category1)) + 
  geom_point(aes(y = ID, shape = category2)) +
  geom_point(aes(y = Petal.Length, size = category3), alpha = 0.6) +
  geom_line(aes(y = Petal.Width, linetype = category4))


plotly_plot <- plotly::ggplotly(gg)

categories <- strsplit(plotly_plot$x$layout$legend$title$text, "<br />")[[1]]
group_title <- ""

for (i in seq_along(plotly_plot$x$data)) {
  if (!is.null(plotly_plot$x$data[[i]]$name)) {
     new_title <- categories[sapply(categories, \(x) grepl(x, plotly_plot$x$data[[i]]$text[1]))]
     group_title <- ifelse(new_title == group_title, "", new_title)
     plotly_plot$x$data[[i]]$legendgrouptitle = list(text = group_title)
  }
}

plotly_plot$x$layout$legend$title$text = "" # remove legend title
plotly_plot

This adds a title to each legend group, similar to your ggplot2.

res

Note: Since this is a hack, it could fail for some legends, I only tested some simple cases.

  • It could also break, if you remove the category from the hovertext should you pass custom hover texts to ggplotly
  • I removed the legend title, you might remove that line should you want to keep it
Sign up to request clarification or add additional context in comments.

2 Comments

Thank your for the detailed answer! Sadly i phrased the question wrong, I did not know that the term legend does not apply to the displayed category values which are my main concern. Your answer still gave me a workaround that maybe works: If you loop through the data names they can be changed. for(i in 1:length(plotly_plot$x$data)){ plotly_plot$x$data[[i]]$name <- stringr::str_extract(plotly_plot$x$data[[i]]$name, "(?<=\\()[^,]*(?=,)") }
Yes! It was my goal to show that one can loop through the traces and make changes to the plotly object afterwards :) - glad you found a solution

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.