Reputation: 7592
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:
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
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
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") +
.
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
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
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
Upvotes: 14
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
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
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