I'm making some figures with ggplotly() and have noticed that facet_wrap and facet_grid causes each item in the legend to be repeated by the number of facets. Is there a way to stop this?
For example:
library("ggplot2")
library("plotly")
diamonds = diamonds[diamonds$cut %in% c("Fair", "Good"),]
dia = ggplot(diamonds, aes(x = cut)) +
geom_bar(aes(stat = "identity", fill = cut)) +
facet_grid(.~color)
ggplotly(dia)
The ?plotly documentation isn't very elaborate, and none of these have legends.
Here's what comes up when I just type ggplotly if that gives any insight:
function (p = ggplot2::last_plot(), filename, fileopt, world_readable = TRUE)
{
l <- gg2list(p)
if (!missing(filename))
l$filename <- filename
if (!missing(fileopt))
l$fileopt <- fileopt
l$world_readable <- world_readable
hash_plot(p$data, l)
}
UPDATE
Issues appear fixed with Plotly 3.6.0 -- 16 May 2016
Due to the ggplotly bug for geom_bar, which distorts the data for the bars, there may not be a good way to do this. For this particular case, facet is not needed. You can use plot_ly() to build an effective plot.
Plot_ly
require(plotly)
require(dplyr)
d <- diamonds[diamonds$cut %in% c("Fair", "Good"),] %>%
count(cut, color)
plot_ly(d, x = color, y = n, type = "bar", group = cut)
Use Plotly subplot()
If this plot type is a must, you can build a facet-like plot using Plotly's subplot. It's not pretty.
d2 <- diamonds[diamonds$cut %in% c("Fair", "Good"),] %>%
count(cut, color) %>%
transform(color = factor(color, levels=rev(levels(color)))) %>%
mutate(id = as.integer(color))
p <- plot_ly(d2, x = cut, y = n, type = "bar", group = color, xaxis = paste0("x", id), marker = list(color = c("#0000FF","#FF0000"))) %>%
layout(yaxis = list(range = range(n), linewidth = 0, showticklabels = F, showgrid = T, title = ""),
xaxis = list(title = ""))
subplot(p) %>%
layout(showlegend = F,
margin = list(r = 100),
yaxis = list(showticklabels = T),
annotations = list(list(text = "Fair", showarrow = F, x = 1.1, y = 1, xref = "paper", yref = "paper"),
list(text = "Good", showarrow = F, x = 1.1, y = 0.96, xref = "paper", yref = "paper")),
shapes = list(list(type = "rect", x0 = 1.1, x1 = 1.13, y0 = 1, y1 = 0.97, line = list(width = 0), fillcolor = "#0000FF", xref = "paper", yref = "paper"),
list(type = "rect", x0 = 1.1, x1 = 1.13, y0 = 0.96, y1 = 0.93, line = list(width = 0), fillcolor = "#FF0000", xref = "paper", yref = "paper")))
You could just turn off the guide/legend in this case as you don't really need it.
library("ggplot2")
library("plotly")
diamonds = diamonds[diamonds$cut %in% c("Fair", "Good"),]
dia = ggplot(diamonds, aes(x = cut)) +
geom_bar(aes(stat = "identity", fill = cut)) +
guides(fill=FALSE) +
facet_grid(.~color)
ggplotly(dia)
Related
I'm trying to create a 2x2 subplot, with both plots in each column having the same y-axis title, like this :
i.e. one 'title' (here called annotations, cf. later) for the left column (blue+green) and one for the right column (yellow+red).
I can easily have a yaxis title for each plot but I'm stumped as to making shared ones.
I tried using annotations, like this (this is the code used to render the plot shown above) :
if (!require("plotly")) install.packages("plotly")
library(plotly)
group <- c("a", "b", "c")
values <- c(0, 5, 10)
df <- data.frame(group, values)
plot <- df %>%
plot_ly() %>%
add_trace(x = ~group, y = ~values, type = "scatter", mode = "line") %>%
layout(yaxis = list(ticks = "outside"), xaxis = list(showline = TRUE))
plot
subdf1 <- subplot(plot, plot, nrows = 1, margin = 0.06)
subdf2 <- subplot(plot, plot, nrows = 1, margin = 0.06)
subdf <- subplot(subdf1, subdf2, nrows = 2, margin = 0.06) %>%
layout(annotations = list(list(x = -0.1, y = 0.5, text = "<b>First annotation</b>", xref = "paper", yref = "paper", xanchor = "center", yanchor = "center", showarrow = FALSE, textangle = -90, font = list(color = "black", size = 16)),
list(x = 0.48, y = 0.5, text = "<b>Second annotation</b>", xref = "paper", yref = "paper", xanchor = "center", yanchor = "center", showarrow = FALSE, textangle = -90, font = list(color = "black", size = 16))))
subdf
My main gripe with this method is that when the plot is resized, the annotations (mainly the first one, in the negative range for x-axis placement) move around the x-axis.
Same plot but wider :
I used xref = "paper" as I thought it meant the whole plot area i.e. the whole white background, but in such case, my annotation wouldn't disappear (and wouldn't be in negatives, but I'm possibly not thinking about this the right way). I did try using xref = x but it won't go into negatives and instead just push the data to the right.
So all in all, two questions :
Is there a native way to have a shared axis title for subplots?
If not, is there a way to make sure that my annotations stay in the same relative place as the plots and axes when resizing the subplot?
If you aren't tied to using plotly, this can be done in a straightforward way using faceting in ggplot. It may require some rearranging of your data into tidy format but gives some serious flexibility while plotting!
library(ggplot2)
group <- c("a", "b", "c")
values <- c(0, 5, 10)
df <- data.frame(group, values)
df <- data.frame(group = rep(c('a','b','c'), 4),
values = rep(c(0,5,10), 4),
facet = rep(c('W','X','Y','Z'), each = 3))
ggplot(df, aes(x = group, y = values, colour = facet, group = 1)) +
geom_line(size = 1.1) +
geom_point(size = 2) +
facet_wrap(~facet) +
theme_bw() +
labs(x = 'Shared X axis title', y = 'Shared Y axis title', colour = 'Traces') +
theme(
strip.background = element_blank(),
strip.text.x = element_blank()
)
You could create a separate title in each layout of both subplots and combine them using titleY like this:
library(plotly)
library(dplyr)
group <- c("a", "b", "c")
values <- c(0, 5, 10)
df <- data.frame(group, values)
plot <- df %>%
plot_ly() %>%
add_trace(x = ~group, y = ~values, type = "scatter", mode = "line") %>%
layout(yaxis = list(ticks = "outside"), xaxis = list(showline = TRUE))
subdf1 <- subplot(plot, plot, nrows = 1) %>%
layout(yaxis = list(title = "First annotation"))
subdf2 <- subplot(plot, plot, nrows = 1) %>%
layout(yaxis = list(title = "Second annotation"))
subdf <- subplot(subdf1, subdf2, nrows = 2, titleY = TRUE)
subdf
Created on 2023-01-23 with reprex v2.0.2
Edit
Change margin in layout:
library(plotly)
library(dplyr)
group <- c("a", "b", "c")
values <- c(0, 5, 10)
df <- data.frame(group, values)
plot <- df %>%
plot_ly() %>%
add_trace(x = ~group, y = ~values, type = "scatter", mode = "line") %>%
layout(yaxis = list(ticks = "outside"), xaxis = list(showline = TRUE))
subdf1 <- subplot(plot, plot, nrows = 1, margin = 0.06)
subdf2 <- subplot(plot, plot, nrows = 1, margin = 0.06)
subdf <- subplot(subdf1, subdf2, nrows = 2, margin = 0.06) %>%
layout(margin = 0.01,
annotations = list(list(x = -0.1, y = 0.5, text = "<b>First annotation</b>", xref = "paper", yref = "paper", xanchor = "center", yanchor = "center", showarrow = FALSE, textangle = -90, font = list(color = "black", size = 16)),
list(x = 0.48, y = 0.5, text = "<b>Second annotation</b>", xref = "paper", yref = "paper", xanchor = "center", yanchor = "center", showarrow = FALSE, textangle = -90, font = list(color = "black", size = 16))))
subdf
Created on 2023-01-23 with reprex v2.0.2
I have a data.table, and I would like to create an histogram (or barplot) by 2 groups in plotly
library(data.table)
library(plotly)
library(ggplot2)
n = 7200
n1 = 4/3*n
n2 = 2*n
dt = data.table(x = sample(rep(c("0-20", "21-40", "41-60", "61-80"), n)),
group1 = sample(rep(c("A", "B", "C"), n1)),
group2 = sample(rep(c(0, 1), n2))
)
setorder(dt, x, group1, group2)
dt[, x := factor(x)]
dt[, group1 := factor(group1)]
dt[, group2 := factor(group2)]
ggplot(dt) + geom_bar(aes(x = x, fill = factor(group2)), width = 1) +
scale_fill_manual(values = c("#9c868b", "#038073"), guide = 'none') + guides(legend = 'none') +
scale_y_continuous(position = 'right') +
facet_grid(rows = vars(forcats::fct_rev(group1)), switch = 'y') +
coord_flip(clip = "off")
Here is the result I want to have (made with ggplot) and I don't want to use ggplotly(...)
I do not know if I have to handle data like below to create barplot instead of histogram
dt = dt[, .N, by = .(x, group1, group2)]
dt = dcast(dt,
group1 ~ x + group2,
value.var = c("N"))
You could make something similar in a few lines of code. If you want all the details lined up as you've depicted, it's a 'few more'.
By the way, I used set.seed(34) if you wanted to see the exact same plot.
# not really what you're looking for
plot_ly(subset(dt, group2 == "0"), type = 'histogram', name = 'group 0',
y = ~list(rev(group1), x), orientation = 'h') %>%
add_histogram(subset(dt, group2 == "1"), name = 'group 1',
y = ~list(rev(group1), x), orientation = 'h') %>%
layout(barmode = 'stack')
(I didn't include the axis title or legend in the image; I'm just trying to highlight the lack of gap)
You can always continue to mod this graph toward the desired plot. However, you won't get the gaps you're looking for between the bars.
Alternatively, you could use subplot and make a separate plot for each of the unique values used in faceting in your original plot.
lapply(1:length(unique(dt$group1)), # for each facet...
function(k) {
dt <- subset(dt, group1 == unique(dt$group1)[k]) # find facet data
p <- plot_ly(dt, type = "histogram", color = ~group2,
y = ~x, orientation = 'h', showlegend = F) %>% # no legend
layout(barmode = 'stack', bargap = 0)
assign(paste0('p', k), p, envir = .GlobalEnv) # put in global env
})
subplot(p1, p2, p3, nrows = 3, titleX = T, shareX = T) %>% # assemble facets
layout(xaxis = list(side = 'top', title = 'count', anchor = 'y1')) # anchor top plot
With a few more lines of code, you can add the labeling as you see in ggplot faceting.
lapply(1:length(unique(dt$group1)), # for each facet...
function(k) {
message(print(k))
dt <- subset(dt, group1 == unique(dt$group1)[k]) # find facet data
p <- plot_ly(dt, type = "histogram", color = ~group2,
y = ~x, orientation = 'h', showlegend = F) %>% # no legend
layout(barmode = 'stack', bargap = 0,
shapes = list( # like facet plot this is the gray bar behind label
type = "rect", xref = 'x', yref = 'paper', # set plot 'space'
y0 = 0, y1 = 1, x0 = -250, x1 = -50, # rect limits
fillcolor = 'lightgrey',
line = list(linewidth = 0.0001, color = 'lightgrey') # remove border
),
annotations = list( # like facet plot, this is the facet label
showarrow = F, text = unique(dt$group1), # no arrow; label
xref = 'x', yref = 'paper', x = -150, y = .5, # center of 'rect'
xanchor = 'center', yanchor = 'center', textangle = -90 # rotate text
))
assign(paste0('p', k), p, envir = .GlobalEnv) # put in global env
})
subplot(p1, p2, p3, nrows = 3, titleX = T, shareX = T) %>% # assemble facets
layout(xaxis = list(side = 'top', title = 'count', anchor = 'y1')) # anchor top plot
How can I reproduce the following graph in plotly
library(dplyr)
library(ggplot2)
tibble(x =1:10, y = 1:10) %>%
ggplot(aes(x,y)) +
geom_line() +
scale_y_continuous(sec.axis = ~.*2)
I tried the following code based on this answer here
library(dplyr)
library(plotly)
tibble(x =1:10, y = 1:10) %>%
mutate(y2 = y*2) %>%
plot_ly() %>%
add_lines(x =~x, y =~y) %>%
add_lines(x= ~x, y=~y2,
yaxis = "y2", color = I("transparent"),
hoverinfo='skip', showlegend=FALSE) %>%
layout(yaxis2 = list(side = "right", overlaying = "y", showgrid = FALSE),
margin = list(r = 50))
While at first glance it appears to work, it only provides a partial solution, since if I interactively try to change the scale of the main (left) y axis on the produced graph (by dragging it up or down), the right axis does not move with the graph (because it is linked only to the second invisible graph). This of course is not acceptable as it does not allow using any of interactive features of plotly reliably which is the reason I wanted to use it to begin with instead of ggplot.
Edit: Just realized that this plotly solution does not seem to work at all in the case of non linear transformation between the axes (while ggplot handles it beautifully).
You just need to set up dtick and tick0 for plotly to have the same graph as ggplot2 one. See below;
library(plotly)
library(dplyr)
tibble(x =1:10, y = 1:10) %>%
mutate(y2 = y*2) -> df1
n0 <- 4
y0 <- max(df1$y)/n0
x0 <- max(df1$x)/n0
df1 %>%
plot_ly(data = . , x = ~x, y = ~y,
type = "scatter", mode = "lines", width = 800,
color = I("red"), name = "") %>%
add_trace(x = ~x, y = ~y2, yaxis = "y2",
color = I("red"), name = "") %>%
layout(yaxis = list(showline = FALSE, side = "left",
title = "", color = "red",
dtick = y0, tick0 = y0, fixedrange=TRUE),
yaxis2 = list(showline = FALSE, side = "right", overlaying = "y", position = 1,
title = "", anchor = "free", color = "blue",
dtick = 2*y0, tick0 = 2*y0, fixedrange=TRUE),
xaxis = list(showline = FALSE, zeroline = FALSE, title = "",
dtick = x0, tick0 = x0),
showlegend = FALSE,
margin = list(l = 50, r = 50, b = 50, t = 50, pad = 4)
)
Created on 2020-06-19 by the reprex package (v0.3.0)
I have a list of figures created with R's plotly, just as an example:
library(plotly)
library(dplyr)
set.seed(1)
data.df <- data.frame(val = c(rnorm(100,0,1),rnorm(100,1,1)), group = c(rep("A",100),rep("B",100)))
density.df <- do.call(rbind,lapply(levels(data.df$group),function(g){
dens <- density(dplyr::filter(data.df,group == g)$val)
data.frame(x = dens$x, y = dens$y, group = g)
}))
plot.list <- lapply(1:5,function(x)
plot_ly(x = density.df$x, y = density.df$y, type = 'scatter', mode = 'lines',color = density.df$group, showlegend = (x == 5)) %>%
layout(xaxis = list(title= "Value", zeroline = F), yaxis = list(title = "Density", zeroline = F))
)
Which I'd like to put together horizontally, where there will be only a single shared x-axis label and a single shared y-axis label.
I'm using:
plotly::subplot(plot.list, nrows = 1, shareX = T, shareY = T, titleX = T, titleY = T)
And getting:
Is it not possible to get a single x-axis label in a horizontal plot?
The same occurs for the y-axis label if I change the nrows argument value from 1 to 5.
You can manually add an annotation
https://community.plot.ly/t/subplots-how-to-add-master-axis-titles/13927/5
myplotly(df) %>%
add_annotations(
text = "my x title",
x = 0.5,
y = 0,
yref = "paper",
xref = "paper",
xanchor = "center",
yanchor = "bottom",
yshift = -35,
showarrow = FALSE,
font = list(size = 15)
)
I am wondering how to give difference subtitles for the subplots using plot_ly. Any hint please. I got one title BB in this case. Thanks.
p <- subplot(
plot_ly(economics, x = date, y = uempmed)%>%layout(showlegend = FALSE, title="AA"),
plot_ly(economics, x = date, y = unemploy)%>%layout(showlegend = FALSE, title="BB"),
margin = 0.05
)
Instead of positioning "by hand" (i.e., #d-roy's answer), you can now leverage subplot()'s ability to reposition paper referenced things like annotations (as well as shapes, images, etc).
library(plotly)
library(dplyr)
my_plot <- . %>%
plot_ly(x = ~date, y = ~value) %>%
add_annotations(
text = ~unique(variable),
x = 0.5,
y = 1,
yref = "paper",
xref = "paper",
xanchor = "middle",
yanchor = "top",
showarrow = FALSE,
font = list(size = 15)
)
economics_long %>%
group_by(variable) %>%
do(p = my_plot(.)) %>%
subplot(nrows = NROW(.), shareX = TRUE)
The title attribute in layout refers to the title for the entire plotting surface, so there can only be one. However, we can use text annotations to create "titles" for your subplots, for example:
p <- subplot(
plot_ly(economics, x = date, y = uempmed)%>%layout(showlegend = FALSE),
plot_ly(economics, x = date, y = unemploy)%>%layout(showlegend = FALSE),
margin = 0.05
)
p %>% layout(annotations = list(
list(x = 0.2 , y = 1.05, text = "AA", showarrow = F, xref='paper', yref='paper'),
list(x = 0.8 , y = 1.05, text = "BB", showarrow = F, xref='paper', yref='paper'))
)
I was able to use layout(annotations()) scheme not on the subplot() but on the plot_ly objects themselves. This gives a slightly better placement for dynamic visualization. So to rework #d-roy's answer:
p <- subplot(
plot_ly(economics, x = date, y = uempmed) %>%
layout(annotations = list(x = 0.2 , y = 1.05, text = "AA", showarrow = F,
xref='paper', yref='paper'),
showlegend = FALSE),
plot_ly(economics, x = date, y = unemploy) %>%
layout(annotations = list(x = 0.2 , y = 1.05, text = "AA", showarrow = F,
xref='paper', yref='paper'),
showlegend = FALSE),showlegend = FALSE))`.
Please note that in this case coordinates of the annotations are the same for each annotation because they are referring to each subplot and not the combined plot as a whole.