How can I remove the duplicates in my legend when using plotly's subplots()?
Here is my MWE:
library(plotly)
library(ggplot2)
library(tidyr)
mpg %>%
group_by(class) %>%
do(p = plot_ly(., x = ~cyl, y = ~displ, color = ~trans, type = 'bar')) %>%
subplot(nrows = 2, shareX = TRUE, titleX = TRUE) %>%
layout(barmode = 'stack')
plotly does not have facet like ggplot2 so it will add legend for each subplot or you can turn it off for some of them.
Here we do not have a layer with all the ~class entries nor two plots with no intersection in class which their combination also covers all of them. In that case, we could set showlegend to TRUE for those specific plot(s) and set it to FALSE for the rest and also set the legendgroup to trans so we get a unique but also complete legend.
As I said, here we do not have that special case. So What I can think of are two possibilities:
Adding the whole data (duplicating whole dataframe) and assigning class of All to them. Then plotting that along with original data but keep the legend only for class == All.
Using ggplot::facet_wrap and then ggplotly to make a plotly object. However, this would cause some issues with x-axis (compare ggplot object to plotly ones).
library(plotly)
library(ggplot2)
library(dplyr)
ly_plot <- . %>%
plot_ly(x = ~cyl, y = ~displ, color = ~trans,
type = 'bar', showlegend = ~all(legendC)) %>%
add_annotations(
text = ~unique(class),
x = 0.5,
y = 1,
yref = "paper",
xref = "paper",
xanchor = "middle",
yanchor = "top",
showarrow = FALSE,
font = list(size = 15))
mpg %>%
mutate(class= "_All_") %>%
rbind(.,mpg) %>%
mutate(legendC = (class == "_All_")) %>%
group_by(class) %>%
do(p = ly_plot(.)) %>%
subplot(nrows = 2, shareX = TRUE, titleX = TRUE) %>%
layout(barmode = 'stack')
#> Warning in RColorBrewer::brewer.pal(N, "Set2"): n too large,
#> allowed maximum for palette Set2 is 8
#> Returning the palette you asked for with that many colors
p <- ggplot(data = mpg, aes(x=cyl, y=displ, fill=trans))+
geom_bar(stat="identity") +
facet_wrap(~class)
p
ggplotly(p) #seems for this we should also set "colour = trans"
Another workaround using the tidyverse. The following steps are added to the original MWE:
Convert the trans column to a factor.
Use tidyr's complete to fill (non-NA) dummy values for the missing factor levels in each class group.
Follow M-M's suggestion setting showlegend to TRUE for a single group and legendgroup to trans to link the legend entries between subplots.
library(plotly)
library(tidyverse)
mpg %>%
mutate_at("trans", as.factor) %>%
group_by(class) %>%
group_map(.f = ~{
## fill missing levels w/ displ = 0, cyl = first available value
complete(.x, trans, fill = list(displ = 0, cyl = head(.x$cyl, 1))) %>%
plot_ly(x = ~cyl, y = ~displ, color = ~trans, colors = "Paired", type = "bar",
showlegend = (.y == "2seater"), legendgroup = ~trans) %>%
layout(yaxis = list(title = as.character(.y)), barmode = "stack")
}) %>%
subplot(nrows = 2, shareX = TRUE, titleY = TRUE)
Related
I am trying to replicate the following stacked bar chart with plotly. I attach one screenshot for every hover text I get when hovering on a bar. As you will see there are 2 issues. First I cannot achieve 3 colors, besides the fact that I create them in the legend and secondly I cannot put First dose as top bar besides the fact that I use factor() based on the levels. Maybe there is an issue with the way I have created my dataset. I have no problem if you have to reform it instead of fix the plotly code to replicate the chart.
library(plotly)
Category<-c("First dose","Full vaccination")
`Uptake first dose`<-c(19.8,7.6)
`Uptake full vaccination`<-c(0,0)
`Not vaccinated`<-c(80.2,92.4)
ch5<-data.frame(Category,`Uptake first dose`,`Uptake full vaccination`,`Not vaccinated`)
ch5$Category <- factor(ch5$Category, levels = ch5[["Category"]])
ax <- list(
title = "",
showticklabels = FALSE,
showgrid = FALSE
)
fig <- plot_ly(ch5, y = ~Category, x = ~`Uptake first dose`,
type = 'bar', name = 'Uptake first dose',marker = list(color = 'lightgreen'))
fig <- fig %>% add_trace(x = ~`Uptake full vaccination`, name = 'Uptake full vaccination',marker = list(color = 'green'))
fig <- fig %>% add_trace(x = ~`Not vaccinated`, name = 'Not vaccinated',marker = list(color = 'gray'))
fig <- fig %>% layout(yaxis = ax,xaxis=list(title="",showgrid=F), barmode = 'stack')
fig
There might be a problem with your dataset. The 7.6% of full vaccination is listed under first doese. Therefore your coloring might not work.
Furthermore I transformed the data into a long format for an easy way to create hovertemplates.
library(plotly)
library(tidyverse)
# data
Category<-c("First dose","Full vaccination")
`Uptake first dose`<-c(19.8,0)
`Uptake full vaccination`<-c(0,7.6)
`Not vaccinated`<-c(80.2,92.4)
ch5<-data.frame(Category,`Uptake first dose`,`Uptake full vaccination`,`Not vaccinated`)
# transform data
data.long <- ch5 %>%
pivot_longer(cols = -Category,
names_to = "vac",
values_to = "percent") %>%
mutate(vac = str_replace_all(vac, "\\.", " "),
vac = fct_rev(factor(vac)))
# add plot
plot_ly(data.long) %>%
add_bars(y = ~Category,
x = ~percent,
color = ~vac,
text = ~vac,
colors = c("darkgreen", "green", "gray"),
hovertemplate = paste('<b>%{y}</b>',
'<br>%{text}: %{x} ',
'<extra></extra>')) %>%
layout(barmode = "stack",
yaxis = list(autorange="reversed"),
hoverlabel = list(bgcolor = "black",
bordercolor = "black",
font = list(color = "white")),
shapes = list(type = "line",
y0 = 0, y1 = 1, yref = "paper",
x0 = 70, x1 = 70),
annotations = list(text = "Target (70.0%)",
showarrow = FALSE,
x = 70,
y = 1.05,
yref = "paper"))
This is my code. Just a simple historgram. But what I wanted to do is to customize the hover text so that when I hover, it will display all species included in that histogram bar. Can you help me?
iris %>%
plot_ly(x=~Sepal.Length, color=~Sepal.Width, text=~Species) %>%
add_histogram()
Here's the output. But when I hover it seems the text is only displaying the first species in the table.
plotly_hist
I'm not sure whether this is possible. Probably you are demanding too much from plotly. After trying some options I think there are two ways to go if you want the different Species to show up in the tooltip:
First option is to use a stacked histogram using hovermode = "unified" like so:
library(plotly)
fig <- plot_ly()
fig <- fig %>% add_trace(data = filter(iris, Species == "setosa"),
x = ~Sepal.Length,
color = ~Species,
text = ~Species,
type='histogram',
bingroup=1, showlegend = FALSE)
fig <- fig %>% add_trace(data = filter(iris, Species == "versicolor"),
x = ~Sepal.Length,
color = ~Species,
text = ~Species,
type='histogram',
bingroup=1, showlegend = FALSE)
fig <- fig %>% add_trace(data = filter(iris, Species == "virginica"),
x = ~Sepal.Length,
color = ~Species,
text = ~Species,
type='histogram',
bingroup=1, showlegend = FALSE)
fig <- fig %>% layout(
hovermode="unified",
barmode="stack",
bargap=0.1)
fig
The second option would be to make the computations yourself, i.e. binning and summarising and to make a bar chart of the counts.
iris %>%
mutate(Sepal.Length.Cut = cut(Sepal.Length, breaks = seq(4, 8, .5), right = FALSE)) %>%
group_by(Sepal.Length.Cut, Species) %>%
summarise(n = n(), Sepal.Width = sum(Sepal.Width)) %>%
tidyr::unite("text", Species, n, sep = ": ", remove = FALSE) %>%
summarise(n = sum(n), Sepal.Width = sum(Sepal.Width) / n, text = paste(unique(text), collapse = "\n")) %>%
plot_ly(x = ~Sepal.Length.Cut, y = ~n, text = ~text) %>%
add_bars(marker = list(colorscale = "Rainbow"), hovertemplate = "%{y}<br>%{text}")
Edit A third option would be to use ggplotly(). This way it is an easy task to add annotations displayling the total numbers per bin. This way we can make use of the stats layers in ggplot2 which will do all the computations. To the best of my knowledge that couldn't be done that easily using "pure" plotly.
library(plotly)
ggplot(iris, aes(Sepal.Length, fill = Species)) +
stat_bin(breaks = seq(4, 8, .5), closed = "left") +
stat_bin(breaks = seq(4, 8, .5), closed = "left", geom = "text", mapping = aes(Sepal.Length, label = ..count..), inherit.aes = FALSE, vjust = -.5) +
theme_light()
ggplotly()
I am trying to split the attached grouped bar chart by the variable spec. Two thoughts on best way to do this are by adding facet_grid() or if a filter can be applied to the static output? Can either be done? Any advice appreciated.
a sample is below:
period <- c('201901', '201901', '201904', '201905')
spec <- c('alpha', 'bravo','bravo', 'charlie')
c <- c(5,6,3,8)
e <- c(1,2,4,5)
df <- data.frame(period, spec, c,e)
library(tidyverse)
library(plotly)
plot_ly(df, x =~period, y = ~c, type = 'bar', name = "C 1", marker = list(color = 'lightsteelblue3'))
%>%
add_trace(y = ~e, name = "E 1", marker = list(color = 'Gray')) %>%
layout(xaxis = list(title="", tickangle = -45),
yaxis = list(title=""),
margin= list(b=100),
barmode = 'group'
)
I am not sure if you are plotting what you actually want to achieve? My suggestion is to create your plot using standard ggplot and then use ggplotly.
For this, you also need to reshape your data and make it a bit longer.
library(tidyverse)
library(plotly)
period <- c('201901', '201901', '201904', '201905')
spec <- c('alpha', 'bravo','bravo', 'charlie')
c <- c(5,6,3,8)
e <- c(1,2,4,5)
df <- data.frame(period, spec, c,e) %>%
pivot_longer(cols = c(c,e), names_to = 'var', values_to = 'val')
p <- ggplot(df, aes(period, val, fill = var)) +
geom_col(position = position_dodge()) +
facet_grid(~spec)
ggplotly(p)
It's probably easier to use facets here, but a more "interactive" option would be to use a filter transforms which gives you a drop-down menu in the top left corner of your plot.
spec.val <- unique(df$spec)
plot_ly(
df %>% pivot_longer(-c(period, spec)),
x = ~period, y = ~value, color = ~name,
type = "bar",
transforms = list(
list(
type = "filter",
target = ~spec,
operation = "=",
value = spec.val[1]))) %>%
layout(
updatemenus = list(
list(
type = "drowdown",
active = 0,
buttons = map(spec.val, ~list(
method = "restyle",
args = list("transforms[0].value", .x),
label = .x)))))
Building up on another question (How to remove duplicate legend entries w/ plotly subplots()), I am facing a new problem. I want all plots in both rows to have the same Y-axis. However, If I turn "shareY = TRUE", the plots on the upper row share an axis, and the plots on the lower row do, but the axis differ from one another.
The code is basically the one from the answer by #Joris Chau, but added "shareY = TRUE" on the last line.
library(plotly)
library(tidyverse)
mpg %>%
mutate_at("trans", as.factor) %>%
group_by(class) %>%
group_map(.f = ~{
## fill missing levels w/ displ = 0, cyl = first available value
complete(.x, trans, fill = list(displ = 0, cyl = head(.x$cyl, 1))) %>%
plot_ly(x = ~cyl, y = ~displ, color = ~trans, colors = "Paired", type = "bar",
showlegend = (.y == "2seater"), legendgroup = ~trans) %>%
layout(yaxis = list(title = as.character(.y)), barmode = "stack")
}) %>%
subplot(nrows = 2, shareX = TRUE, shareY = TRUE, titleY = TRUE)
How can I tell plotly to use the same scale across all plots?
You should define range of yaxis manually. Here, I used c(0,ceiling(max(aggregate(displ ~ cyl+class, mpg, sum)$displ)/10)*10)).
aggregate(displ ~ cyl+class, mpg, sum)$displ gets the summation of displ grouped by cyl + class.
Then I get its maximum and at the end I round it up using ceiling.
library(plotly)
library(tidyverse)
mpg %>%
mutate_at("trans", as.factor) %>%
group_by(class) %>%
group_map(.f = ~{
complete(.x, trans, fill = list(displ = 0, cyl = head(.x$cyl, 1))) %>%
plot_ly(x = ~cyl, y = ~displ, color = ~trans, colors = "Paired", type = "bar",
showlegend = (.y == "2seater"), legendgroup = ~trans) %>%
layout(yaxis = list(title = as.character(.y),
range=c(0, ceiling(max(
aggregate(displ~cyl+class, mpg, sum)$displ)/10)*10)),
barmode = "stack")
}) %>%
subplot(nrows = 2, shareX = TRUE, shareY = FALSE, titleY = TRUE, margin = 0.05)
I am making a pie-chart in plotly in R.
I want my labels to be on the chart, so I use textposition = "inside", and for the very small slices those values are not visible.
I am trying to find a way to exclude those labels.
Ideally, I would like to like to not print any lables on my plot that are below 10%.
Setting textposition = "auto" doesn't work well, since there are a lot of small slices, and it makes the graph look very messy.
Is there a way to do it?
For example these piecharts from plotly website (https://plot.ly/r/pie-charts/)
library(plotly)
library(dplyr)
cut <- diamonds %>%
group_by(cut) %>%
summarize(count = n())
color <- diamonds %>%
group_by(color) %>%
summarize(count = n())
clarity <- diamonds %>%
group_by(clarity) %>%
summarize(count = n())
plot_ly(cut, labels = cut, values = count, type = "pie", domain = list(x = c(0, 0.4), y = c(0.4, 1)),
name = "Cut", showlegend = F) %>%
add_trace(data = color, labels = color, values = count, type = "pie", domain = list(x = c(0.6, 1), y = c(0.4, 1)),
name = "Color", showlegend = F) %>%
add_trace(data = clarity, labels = clarity, values = count, type = "pie", domain = list(x = c(0.25, 0.75), y = c(0, 0.6)),
name = "Clarity", showlegend = F) %>%
layout(title = "Pie Charts with Subplots")
In the plot for Clarity 1.37% are outside of the plot, while I would like them not to show at all.
You'll have to specify sector labels manually like so:
# Sample data
df <- data.frame(category = LETTERS[1:10],
value = sample(1:50, size = 10))
# Create sector labels
pct <- round(df$value/sum(df$value),2)
pct[pct<0.1] <- 0 # Anything less than 10% should be blank
pct <- paste0(pct*100, "%")
pct[grep("0%", pct)] <- ""
# Install devtools
install.packages("devtools")
# Install latest version of plotly from github
devtools::install_github("ropensci/plotly")
# Plot
library(plotly)
plot_ly(df,
labels = ~category, # Note formula since plotly 4.0
values = ~value, # Note formula since plotly 4.0
type = "pie",
text = pct, # Manually specify sector labels
textposition = "inside",
textinfo = "text" # Ensure plotly only shows our labels and nothing else
)
Check out https://plot.ly/r/reference/#pie for more information...