简体   繁体   中英

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 结果

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...

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

绘图结果

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

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))) 

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 :-)

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 (ie 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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM