iod
iod

Reputation: 7592

Strange formatting of legend in ggplotly in R

I'm trying to turn a ggplot into a plotly. The ggplot renders fine, but when I put it through ggplotly, suddenly the legend adds parenthesis and ",1" after the label.

Here's a sample fake data:

sorted1<-data.frame(CommDate=c(as.Date("2017-09-12"), as.Date("2017-10-15")), CommName=c("Foo", "Bar"), PubB4=c(2,3))

And here's the code I'm trying to run on it:

ggplotly(ggplot(sorted1, aes(x=as.Date(CommDate), y=PubB4))+
           geom_smooth(level=0.0, aes(colour="Moving average"), se=FALSE)+
           geom_point(aes(fill=CommName), size=4)+
           expand_limits(y=c(0,4.5))+
           geom_line(mapping=aes(y=4),colour="orangered3",size=1)+
           geom_text(mapping=aes(y=4.2, x=min(sorted1$CommDate)+4), label="Target", size=3)+
           xlab("Committee Date")+
           guides(fill=guide_legend(title="Committee Names"), colour=guide_legend(title.theme=element_blank(),title=NULL))+
           scale_x_date(labels = date_format("%b-%y"))+
           theme_light()+
           theme(plot.title=element_text(hjust=0.5, size=12),panel.grid.major.x = (element_blank()), 
                 panel.grid.minor.x = (element_blank()), 
                 axis.title = element_text(size=8), legend.title = element_text(size=10),
                 legend.text = element_text(size=8), legend.box = 'vertical', legend.spacing.y = unit(-2,"mm"))+
           scale_colour_manual(name="",values="#0072B2"))

(the geom_smooth doesn't render here, but it does with the full data.)

Here's what I get from this:

ggplotly result with misformatted legend

Why does the legend show up as "(foo,1)"?

I tried removing the geom_smooth which actually solved the problem, but I need it there - how can I keep it but fix the legend?

Thanks!

Update: OK, I started commenting out stuff to see what happens. If I remove the aes() from the geom_smooth, that also fixes the problem, as long as I keep the scale_colour_manual commented off as well. But I really would like to have control over the geom_smooth's aesthetics, and include it in the legend. So I'm making progress, but still not quite there...

Upvotes: 19

Views: 7713

Answers (7)

ecotopic
ecotopic

Reputation: 21

I had some luck with putting all the aes() statements (fill(), color() ...) in the first ggplot(aes()) rather than in the following geom_point() + geom_line() etc.

Just check this (silly) example:

p <- ggplot(mtcars, aes(x=hp, y=mpg)) + 
    geom_line(aes(colour = factor(am))) + 
    geom_point(aes(fill = factor(am)))

### this has the added ",1"
ggplotly(p)

p1 <- ggplot(mtcars, aes(x = hp, y = mpg,
                        colour = factor(am),
                        fill = factor(am))) + 
    geom_line() + 
    geom_point()

### Now it's gone!
ggplotly(p1)

Upvotes: 0

Juergen
Juergen

Reputation: 342

As of 2023-10-26 the easiest and cleanest way to address Plotly's known issue of adding parentheses on dummy scales is setting the respective guide/legend to none, using e.g. guides(color = "none") +.

enter image description here

Full reprex:

# set to FALSE to use original code
fix_plotly_legend = T

sorted1<-data.frame(CommDate=c(as.Date("2017-09-12"), as.Date("2017-10-15")), CommName=c("Foo", "Bar"), PubB4=c(2,3))
plotly::ggplotly(ggplot(sorted1, aes(x=as.Date(CommDate), y=PubB4))+
                   geom_smooth(level=0.0, aes(colour="Moving average"), se=FALSE)+
                   geom_point(aes(fill=CommName), size=4)+
                   expand_limits(y=c(0,4.5))+
                   geom_line(mapping=aes(y=4),colour="orangered3",size=1)+
                   geom_text(mapping=aes(y=4.2, x=min(sorted1$CommDate)+4), label="Target", size=3)+
                   xlab("Committee Date")+
                   guides(fill=guide_legend(title="Committee Names"), colour=guide_legend(title.theme=element_blank(),title=NULL))+
                   scale_x_date(labels = scales::date_format("%b-%y"))+
                   theme_light()+
                   theme(plot.title=element_text(hjust=0.5, size=12),panel.grid.major.x = (element_blank()), 
                         panel.grid.minor.x = (element_blank()), 
                         axis.title = element_text(size=8), legend.title = element_text(size=10),
                         legend.text = element_text(size=8), legend.box = 'vertical', legend.spacing.y = unit(-2,"mm"))+
                   scale_colour_manual(name="",values="#0072B2") +
                   {if (fix_plotly_legend) guides(color = "none") else NULL}
)

Upvotes: 2

Andrea Vargas
Andrea Vargas

Reputation: 13

In case someone is out there still having this problem: check that you aren't setting scale_fill_viridis_d or something similar, with fill when you should be using scale_color_viridis_d (i.e. check you aes() to see if you are using color of fill or both).

Another check to make is if the dataset is grouped or not, if it is grouped, you should ungroup() before doing the graph

Upvotes: 0

Isaac Zhao
Isaac Zhao

Reputation: 429

Here's yet another elegant solution. Under the hood it detects if a plotly legend name option is available and if so, removes the "(" and ",1)".

library(ggplot2)
library(plotly)
library(stringr)
library(dplyr)

data = data.frame(Date=as.Date(c("2017-09-12","2017-10-15")), PubB4=c(2,3), category=c("Foo", "Bar"))

myplot = ggplotly(ggplot(data, aes(x=Date, y=PubB4))+
    geom_hline(aes(yintercept=2.5, color="my line label"))+
    geom_point(aes(fill=category), size=4))

for (i in 1:length(myplot$x$data)){
    if (!is.null(myplot$x$data[[i]]$name)){
        myplot$x$data[[i]]$name =  gsub("\\(","",str_split(myplot$x$data[[i]]$name,",")[[1]][1])
    }
}

myplot

plot result

Upvotes: 14

James N
James N

Reputation: 325

Here's an approach that worked for me. It involves diving into the resulting plotly object and cleaning up the legend names.

The first part creates a function to detect if the plotly list element has a legend specified and then cleans it up.

clean_plotly_leg <- function(.plotly_x, .extract_str) {
  # Inpects an x$data list in a plotly object, cleans up legend values where appropriate
  if ("legendgroup" %in% names(.plotly_x)) {
    # The list includes a legend group

    .plotly_x$legendgroup <- stringr::str_extract(.plotly_x$legendgroup, .extract_str)
    .plotly_x$name <- stringr::str_extract(.plotly_x$name, .extract_str)

  }
  .plotly_x


}

The second part applies it to your example, but with an intermediate step.

sorted_plotly <-
  ggplotly(ggplot(sorted1, aes(x=as.Date(CommDate), y=PubB4))+
           geom_smooth(level=0.0, aes(colour="Moving average"), se=FALSE)+
           geom_point(aes(fill=CommName), size=4)+
           expand_limits(y=c(0,4.5))+
           geom_line(mapping=aes(y=4),colour="orangered3",size=1)+
           geom_text(mapping=aes(y=4.2, x=min(sorted1$CommDate)+4), label="Target", size=3)+
           xlab("Committee Date")+
           guides(fill=guide_legend(title="Committee Names"), colour=guide_legend(title.theme=element_blank(),title=NULL))+
           scale_x_date(labels = date_format("%b-%y"))+
           theme_light()+
           theme(plot.title=element_text(hjust=0.5, size=12),panel.grid.major.x = (element_blank()), 
                 panel.grid.minor.x = (element_blank()), 
                 axis.title = element_text(size=8), legend.title = element_text(size=10),
                 legend.text = element_text(size=8), legend.box = 'vertical', legend.spacing.y = unit(-2,"mm"))+
           scale_colour_manual(name="",values="#0072B2"))

sorted_plotly$x$data <-
  sorted_plotly$x$data %>% 
  map(clean_plotly_leg, "[^\\(][^,]*") # ie remove the opening bracket, if one exists, and extract each character up to the first comma

sorted_plotly 

I'd welcome any suggestions for making this code more efficient, but at least it works

Upvotes: 3

Hovav Dror
Hovav Dror

Reputation: 1

Here's a workaround that actually works:

# First, repeating your code, noting the plot as p1
# --------------------------------------------------
sorted1<-data.frame(CommDate=c(as.Date("2017-09-12"), as.Date("2017-10-15")), CommName=c("Foo", "Bar"), PubB4=c(2,3))
p1 <- ggplotly(ggplot(sorted1, aes(x=as.Date(CommDate), y=PubB4))+
           geom_smooth(level=0.0, aes(colour="Moving average"), se=FALSE)+
           geom_point(aes(fill=CommName), size=4)+
           expand_limits(y=c(0,4.5))+
           geom_line(mapping=aes(y=4),colour="orangered3",size=1)+
           geom_text(mapping=aes(y=4.2, x=min(sorted1$CommDate)+4), label="Target", size=3)+
           xlab("Committee Date")+
           guides(fill=guide_legend(title="Committee Names"), colour=guide_legend(title.theme=element_blank(),title=NULL))+
           scale_x_date(labels = date_format("%b-%y"))+
           theme_light()+
           theme(plot.title=element_text(hjust=0.5, size=12),panel.grid.major.x = (element_blank()), 
                 panel.grid.minor.x = (element_blank()), 
                 axis.title = element_text(size=8), legend.title = element_text(size=10),
                 legend.text = element_text(size=8), legend.box = 'vertical', legend.spacing.y = unit(-2,"mm"))+
           scale_colour_manual(name="",values="#0072B2"))

Now we can proceed to the workaround:

# Now, the workaround:
# ------------------------------------------------------
p1Names <- unique(sorted1$CommName) # we need to know the "true" legend values
for (i in 1:length(p1$x$data)) { # this goes over all places where legend values are stored
  n1 <- p1$x$data[[i]]$name # and this is how the value is stored in plotly
  n2 <- " "
  for (j in 1:length(p1Names)) {
    if (grepl(x = n1, pattern = p1Names[j])) {n2 = p1Names[j]} # if the plotly legend name contains the original value, replace it with the original value
  }
  p1$x$data[[i]]$name <- n2 # now is the time for actual replacement
  if (n2 == " ") {p1$x$data[[i]]$showlegend = FALSE}  # sometimes plotly adds to the legend values that we don't want, this is how to get rid of them, too
}
p1   # now we can see the result :-)

Upvotes: 0

iod
iod

Reputation: 7592

Since nothing I did worked, I took the dive and taught myself how to create the chart directly in plotly. For the benefit of possible future viewers, here's how the chart is recreated in plotly (sans some beautification that I'll get around to another time):

plot_ly(sorted, x=~CommDate) %>%
  add_markers(y=~PubB4, color=~factor(CommName), size=I(15)) %>% 
  add_lines(x=loess.smooth(sorted$CommDate,sorted$PubB4)$x,         
    y=loess.smooth(sorted$CommDate,sorted$PubB4)$y, name = "Rolling Average", 
    showlegend = TRUE, size=I(3)) %>%
  add_lines(x=c(min(sorted$CommDate),max(sorted$CommDate)),y=4, 
    color=I("red"), name="target", size=I(3)) %>%
  layout(yaxis=(list(range=c(0,max(c(4.5,sorted$PubB4))))),xaxis=list(range=c(min(sorted$CommDate)-10, max(sorted$CommDate)+5))) 

Upvotes: 1

Related Questions