R ggplotly() and colour annotations - How do you do it? - r

I am trying to replicate a plot from ggplot with the added functionality from Plotly with hover points, but it strips the annotations out and have tried everything to achieve the same view with no success .
library("ggplot2")
library("plotly")
test_data <- data.frame(A = c(1,5,7,4,2),
B = c(3,3,6,8,4))
my_days <- as.Date(c("2010-01-01", "2010-02-01",
"2010-03-01", "2010- 4-01",
"2010-05-01"))
df <- data.frame(test_data, my_days)
# Anotate Box
s_1 <- unique(min(df$my_days))
s_2 <- unique(max(df$my_days))
target <- 1
plot_out <- df %>%
group_by(my_days) %>%
summarise(prop = sum(A / B)) %>%
ggplot(aes(x =my_days, y = prop)) +
geom_line(color = "purple") +
annotate("rect", xmin = s_1, xmax = s_2, ymin = -Inf, ymax = target, alpha = .2, fill = "red") +
annotate("rect", xmin = s_1, xmax = s_2, ymin = target, ymax = Inf, alpha = .2, fill = "green")
plot_out # Plot with Colour
ggplotly(plot_out) # This gives the hover info points , but removes the annotates

Not a ggplotly solution but a plotly solution. (; At least in my opinion ggplotly is nice if you want to make a quick interactive version of a ggplot. However, ggplotly still has a lot of issues and is not able to convert every ggplot. Try this:
library("ggplot2")
library("plotly")
test_data <- data.frame(
A = c(1, 5, 7, 4, 2),
B = c(3, 3, 6, 8, 4)
)
my_days <- as.Date(c(
"2010-01-01", "2010-02-01",
"2010-03-01", "2010- 4-01",
"2010-05-01"
))
df <- data.frame(test_data, my_days)
# Anotate Box
s_1 <- unique(min(df$my_days))
s_2 <- unique(max(df$my_days))
target <- 1
p <- df %>%
group_by(my_days) %>%
summarise(prop = sum(A / B)) %>%
plot_ly(x = ~my_days, y = ~prop) %>%
add_lines(
line = list(color = "purple"),
hoverinfo = "text",
text = ~ paste0(
"mydays: ", my_days,
"\n", "prop: ", round(prop, 7)
)) %>%
# Add the rectangles and set x-axis as in ggplot
layout(
xaxis = list(
type = "date",
tickformat = "%b",
nticks = 5
),
shapes = list(
list(
type = "rect",
fillcolor = "red", opacity = 0.2,
x0 = s_1, x1 = s_2, xref = "x",
y0 = -Inf, y1 = target, yref = "y"
),
list(
type = "rect",
fillcolor = "green", opacity = 0.2,
x0 = s_1, x1 = s_2, xref = "x",
# Setting y1 to Inf results in a yaxis which spans up to 2.5. So I chose 1.8 to mimic the ggplot
y0 = target, y1 = 1.8, yref = "y"
)
)
)
p
Created on 2020-04-05 by the reprex package (v0.3.0)

Related

Is there a way to add a shared axis title on a subplot?

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

Overlaying 2 histograms by 2 groups in plotly

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

Converting ggplot object to plotly object creates axis title that overlaps tick values

I had the same issue described in this question:
R: ggplot and plotly axis margin won't change
but when I implemented the solution, I got the following error:
Warning: Ignoring unknown aesthetics: text We recommend that you use the dev version of ggplot2 with ggplotly() Install it with: devtools::install_github('hadley/ggplot2') Error in tmp[[2]] : subscript out of bounds
This code will produce this error on my machine:
library(gapminder)
library(plotly)
library(ggplot2)
lead <- rep("Fred Smith", 30)
lead <- append(lead, rep("Terry Jones", 30))
lead <- append(lead, rep("Henry Sarduci", 30))
proj_date <- seq(as.Date('2017-11-01'), as.Date('2017-11-30'), by = 'day')
proj_date <- append(proj_date, rep(proj_date, 2))
set.seed(1237)
actHrs <- runif(90, 1, 100)
cummActHrs <- cumsum(actHrs)
forHrs <- runif(90, 1, 100)
cummForHrs <- cumsum(forHrs)
df <- data.frame(Lead = lead, date_seq = proj_date,
cActHrs = cummActHrs,
cForHrs = cummForHrs)
makePlot <- function(dat=df, man_level = 'Lead') {
p <- ggplot(dat, aes_string(x='date_seq', y='cActHrs',
group = man_level,
color = man_level),
linetype = 1) +
geom_line() +
geom_line(data=df,
aes_string(x='date_seq', y = 'cForHrs',
group = man_level,
color = man_level),
linetype = 2)
p <- p + geom_point(aes(text=sprintf('%s\nManager: %s\n MTD Actual Hrs: %s\nMTD Forecasted Hrs: %s',
date_seq, Lead, round(cActHrs, 2), round(cForHrs, 2))))
p <- p + theme_classic() + ylab('Hours') + xlab('Date')
gp <- ggplotly(p, tooltip = "text") %>% layout(hovermode = "compare")
### FIX IMPLEMENTED HERE ###
gp[['x']][['layout']][['annotations']][[2]][['x']] <- -0.1
gp %>% layout(margin = list(l = 75))
return(gp)
}
## run the example
p1 <- makePlot()
Try this:
makePlot <- function(dat=df, man_level = "Lead") {
dat$var <- dat[,man_level]
dat$grp <- ""
p <- ggplot(dat, aes(x=date_seq, y=cActHrs,
group = var, color = var,
text=paste0("Manager:", date_seq,"<br>MTD Actual Hrs:", round(cActHrs, 2),
"<br>MTD Forecasted Hrs:", round(cForHrs, 2))),
linetype = 1) +
geom_line() +
geom_line(data=dat,
aes(x=date_seq, y = cForHrs,
group = var, color = var),
linetype = 2) +
geom_point() +
theme_classic() + ylab("Hours") + xlab("Date") +
scale_color_discrete(name=man_level) +
facet_wrap(~grp)
gp <- ggplotly(p, tooltip = "text")
# Set y-axis label position
gp[["x"]][["layout"]][["annotations"]][[2]][["x"]] <- -0.06
# Set legend label position
gp[["x"]][["layout"]][["annotations"]][[3]][["y"]] <- 0.93
gp <- gp %>% layout(margin = list(l = 120, b=70), hovermode = "compare")
return(gp)
}
The problem in your case is the opposite of the linked question. Your axis title is a real axis title, not an annotation. Currently there is no solution to move axis titles in any direction (see https://github.com/lleslie84/plotly.js/pull/1).
Workarounds like adding line breaks to the axis title or adding spaces to the tick labels don't work in your case.
One possible workaround would be to add an annotation with your axis title. The annotation can then be freely moved.
gp <- layout(gp, yaxis = list(title = ""),
margin = list(l = 100),
annotations = c(list(text = "Hours",
x = -0.15,
xref = "paper",
showarrow = F,
textangle = -90))
)
Complete code
library(gapminder)
library(plotly)
library(ggplot2)
lead <- rep("Fred Smith", 30)
lead <- append(lead, rep("Terry Jones", 30))
lead <- append(lead, rep("Henry Sarduci", 30))
proj_date <- seq(as.Date('2017-11-01'), as.Date('2017-11-30'), by = 'day')
proj_date <- append(proj_date, rep(proj_date, 2))
set.seed(1237)
actHrs <- runif(90, 1, 100)
cummActHrs <- cumsum(actHrs)
forHrs <- runif(90, 1, 100)
cummForHrs <- cumsum(forHrs)
df <- data.frame(Lead = lead, date_seq = proj_date,
cActHrs = cummActHrs,
cForHrs = cummForHrs)
makePlot <- function(dat=df, man_level = 'Lead') {
p <- ggplot(dat, aes_string(x='date_seq', y='cActHrs',
group = man_level,
color = man_level),
linetype = 1) +
geom_line() +
geom_line(data=df,
aes_string(x='date_seq', y = 'cForHrs',
group = man_level,
color = man_level),
linetype = 2)
p <- p + geom_point(aes(text=sprintf('%s\nManager: %s\n MTD Actual Hrs: %s\nMTD Forecasted Hrs: %s',
date_seq, Lead, round(cActHrs, 2), round(cForHrs, 2))))
p <- p + theme_classic() + ylab('Hours') + xlab('Date')
gp <- ggplotly(p, tooltip = "text") %>% layout(hovermode = "compare")
### FIX IMPLEMENTED HERE ###
gp <- layout(gp,
yaxis = list(title = ""),
margin = list(l = 100),
annotations = c(list(text = "Hours",
x = -0.15,
xref = "paper",
showarrow = F,
textangle = -90))
)
return(gp)
}
## run the example
p1 <- makePlot()
p1

R: Plot_ly 3d graph with trace line

I am using plotly 4.7.0. I am trying to add a 3d line to a 3d plot_ly surface plot. When I don't add the add_lines() function inside of the plot_ly call it looks fine. As soon as I do add the add_lines, the graph gets all messed up and doesn't add the 3d line graph.
library(plotly)
m_x = seq(-2, 2, .01)
m_y = seq(-2, 2, .01)
df = expand.grid(m_x, m_y)
df['matrix'] = exp(-(df$Var1^2+df$Var2^2))
m_z = matrix(df$matrix, nrow = length(m_x), ncol = length(m_y))
m_df = list(m_x, m_y, m_z)
x1 = seq(-2, 0, by=0.0202)
y1 = runif(100, min=-0.03,max=0.03)
z1 = exp(-(x1^2+y1^2))
df = data.frame(x1, y2, z2)
names(df) <- c("df_x", "df_y", "df_z")
colors = c( "Blue", "Cyan", "Green", "Yellow", "Orange", "Red")
p1 <- plot_ly(x = m_df[[1]], y = m_df[[2]], z = m_df[[3]],
colors = colors, color = m_df[[3]]) %>% add_surface() %>%
add_lines(x = df$df_x, y = df$df_y, z = df$df_z, data = df,
line = list(color = 'red', width = 1)) %>%
layout(title = "Hike_Example",
scene = list(aspectratio = list(x = 4, y = 4, z = 1)))
p1
Here is the code of the surface plot with the 3D line.
I plotted the 3D line using add_trace in place of add_lines.
library(plotly)
m_x = seq(-2, 2, .01)
m_y = seq(-2, 2, .01)
df = expand.grid(m_x, m_y)
df['matrix'] = exp(-(df$Var1^2+df$Var2^2))
m_z = matrix(df$matrix, nrow = length(m_x), ncol = length(m_y))
m_df = list(m_x, m_y, m_z)
x1 = seq(-2, 0, by=0.0202)
y1 = runif(100, min=-0.03,max=0.03)
z1 = exp(-(x1^2+y1^2))
df = data.frame(x1, y1, z1)
names(df) <- c("df_x", "df_y", "df_z")
colors = c( "Blue", "Cyan", "Green", "Yellow", "Orange", "Red")
p1 <- plot_ly(x = m_df[[1]], y = m_df[[2]], z = m_df[[3]],
colors = colors, color = m_df[[3]]) %>% add_surface() %>%
add_trace(x = ~df_x, y = ~df_y, z = ~df_z, data = df,
type="scatter3d", mode="lines",
line = list(color = "red", width = 4)) %>%
layout(title = "Hike_Example",
scene = list(aspectratio = list(x = 4, y = 4, z = 1)))
p1

Duplicated legends when faceting in ggplotly

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)

Resources