Related
I am trying to produce a stack of plots with plotly R but the plotted lines relative to the zero axis are drifting so that what is zero in the top plot is not zero in the bottom plot. Any ideas how to fix this?
Here is an image of the problem:
Assume that the top plot is correct (it is: Gaussian random walk around zero). Then notice that in plots 2:4 from the top down the xaxis drifts upwards but the exact same data is used on all plots. Notice on the y-axis that the number 2 is drifting upwards. I feel that this is a simple issue (pretty fundamental - keeping values aligned to data!) so I think I am missing something obvious so would appreciate it if someone could point out my obvious mistake.
I have tried setting anchor and scaleanchor but this seems to have no effect on the positioning, even when position = 0 and anchor = 'free'.
I have also tried variations on fixedrange and autorange but again no joy.
Here is a reproducible example:
set.seed(2244)
cols <- c('black','red','green','cyan','blue','magenta','yellow','gray')
outlist <- list()
nplots <- c(1,2,3,4)
bounds <- 0
trials <- 0
M <- 1
N <- 50
i <- 1
y <- rnorm(N)
chleaf <- rbinom(N, 1, 0.5)
outmat <- matrix(0, nrow=N, ncol=6)
outmat[,c(1,3)] <- rnorm(dim(outmat)[1]*2, 0, 1)
outmat[,c(2,4)] <- outmat[,c(1,3)]^2
outmat[,c(5,6)] <- y - outmat[,c(1,3)]
for (j in nplots) {
mgrid <- NULL
if ( bounds == 1 ) {
mgrid <- c(min(outmat[,3]-2*sqrt(outmat[,4]))-0.5,
max(outmat[,3]+2*sqrt(outmat[,4]))+0.5)
} else {
mgrid <- c(min(min(outmat[,3]), min(outmat[,5]), min(y))-0.5,
max(max(outmat[,3]), max(outmat[,5]), max(y))+0.5)
}
outlist[[i]] <- plotly::plot_ly() %>%
plotly::add_trace(x = 1:(M*N), y = outmat[,3], type = 'scatter', mode='lines',
showlegend=ifelse(i==1, TRUE, FALSE), name=TeX('\\mu_{t|t-1}'),
line=list(color=cols[1], width=0.5)) %>%
plotly::add_trace(x = 1:(M*N), y = outmat[,3]+2*sqrt(outmat[,4]),
type='scatter', mode='markers', color=I(cols[1]), size=0.5,
showlegend=ifelse(i==1, TRUE, FALSE), name=TeX('\\Sigma_{t|t-1}'),
marker=list(symbol='cross-thin'), visible=ifelse(bounds==1, TRUE, FALSE)) %>%
plotly::add_trace(x = 1:(M*N), y = outmat[,3]-2*sqrt(outmat[,4]),
type='scatter', mode='markers', color=I(cols[1]), size=0.5,
showlegend=FALSE, marker=list(symbol='cross-thin'),
visible=ifelse(bounds==1, TRUE, FALSE)) %>%
plotly::add_trace(x = 1:(M*N), y = outmat[,1], type = 'scatter', mode='lines',
showlegend=ifelse(i==1, TRUE, FALSE), name=TeX('\\mu_{t|t}'),
line=list(color=cols[2], width=0.5)) %>%
plotly::add_trace(x = 1:(M*N), y = outmat[,1]+2*sqrt(outmat[,2]),
type='scatter', mode='markers', color=I(cols[2]), size=0.5,
showlegend=ifelse(i==1, TRUE, FALSE), name=TeX('\\Sigma_{t|t}'),
marker=list(symbol='cross-thin'), visible=ifelse(bounds==1, TRUE, FALSE)) %>%
plotly::add_trace(x = 1:(M*N), y = outmat[,1]-2*sqrt(outmat[,2]),
type='scatter', mode='markers', color=I(cols[2]), size=I(5),
showlegend=FALSE, marker=list(symbol='cross-thin'),
visible=ifelse(bounds==1, TRUE, FALSE)) %>%
plotly::add_trace(x = 1:(M*N), y = outmat[,5], type = 'scatter', mode='lines',
showlegend=ifelse(i==1, TRUE, FALSE), name=TeX('\\hat{y}_{t}'),
line=list(color=cols[3], width=0.5)) %>%
plotly::add_trace(x = 1:(M*N), y = outmat[,6], type = 'scatter', mode='lines',
showlegend=ifelse(i==1, TRUE, FALSE), name=TeX('\\tilde{y}_{t}'),
line=list(color=cols[4], width=0.5)) %>%
plotly::config(mathjax='cdn') %>%
plotly::layout(
xaxis=list(title=list(text='Iterations', standoff=0),
showline=T, showgrid=F, range = c(0, ifelse(N==1, M+0.25, N*M+0.5)),
anchor='y', scaleanchor='x'),
yaxis=list(showline=T, showgrid=F, range=mgrid))
rMe <- 0
for (n in 1:N) {
rMe <- rMe+M
nchleaf <- (M*(n-1)+1):(n*M)*chleaf[(M*(n-1)+1):(n*M)]
xupdates <- nchleaf[which(nchleaf!=0)]
yupdates <- as.vector(sapply(y[n], function(x){rep(x,length(xupdates))}))
outlist[[i]] <- outlist[[i]] %>%
plotly::add_trace(x = c(M*(n-1), n*M), y = c(y[n], y[n]),
type = 'scatter', mode='lines',
showlegend=ifelse((i==1 && n==1), TRUE, FALSE), name='y',
line=list(color = cols[6], dash = ifelse(M==1, 'solid', 'dash'),
width=0.5)) %>%
plotly::add_trace(x = xupdates, y = yupdates,
type='scatter', mode='markers',
showlegend=ifelse((i==2&&n==1), TRUE, FALSE), name='Update',
color=I(cols[5]), size=0.5) %>%
plotly::add_trace(x = rMe, y = mgrid,
type = 'scatter', mode='lines', visible=ifelse(trials==1, TRUE, FALSE),
showlegend=ifelse((trials==1 && i==1 && n==1), TRUE, FALSE), name='Trial',
line=list(color = cols[8], dash = 'dash', width=0.5))
}
i <- i+1
}
fig <- plotly::subplot(outlist, nrows=length(nplots), shareX=TRUE,
which_layout=c(1)) %>%
plotly::config(staticPlot=T, mathjax='cdn', displayModeBar = F)
fig <- fig %>% plotly::layout(
showlegend=TRUE,
legend=list(itemsizing='trace', orientation='h', xanchor='center', x=0.5),
margin=list(b=70, l=45, r=30, t=80),
title=list(text="Test Title"))
# yaxis=list(autorange=TRUE, fixedrange=FALSE))
# xaxis=list(anchor='y', scaleanchor='x'),
# yaxis=list(anchor='x', scaleanchor='y'))
# yaxis=list(range=mgrid))#,
# xaxis=list(title=list(text='Iterations', standoff=0),
# showline=T, showgrid=F, range = c(0, ifelse(N==1, M+0.25, N*M+2))),
# yaxis=list(showline=T, showgrid=F))
Its a bug in the backend of the graphics device ( or similar) . if you touch the scale knob in the export window the scales jump to the right position.
This is a workaround, i think plotly is not the ideal choice for static images since it translates to python using reticulate and its also more focused on dynamic web stuff. So i would not expect a quick bugfix.
I am creating a plot with several time series, plotting the mean value and two quantiles in the process and highlighting the region between the quantiles. Then I have a unified hoverinfo showing me all points at one point in time. I do not want this hoverinfo to display the dummy trace that is only there for the highlighting. I tried both hoverinfo = skip and hoverinfo = none, but that only hides any text info, not the trace itself in the hoverinfo.
Here is the code I have (without most "beautification parameters" such as axis width and the likes...):
###creating dummy data
test_data <- data.frame(matrix(0,nrow = 27,ncol = 4))
colnames(test_data) <- c("x","y","value","date")
test_data$x <- c(-1,-1,-1,0,0,0,1,1,1,-1,-1,-1,0,0,0,1,1,1,-1,-1,-1,0,0,0,1,1,1)
test_data$y <- c(-1,0,1,-1,0,1,-1,0,1,-1,0,1,-1,0,1,-1,0,1,-1,0,1,-1,0,1,-1,0,1)
test_data$value <- rnorm(27,mean = 5,sd = 1)
date_vec <- c(rep(as.Date("2022-01-01"),9),rep(as.Date("2022-01-02"),9),rep(as.Date("2022-01-03"),9))
test_data$date <- date_vec
x_levels <- unique(test_data$x)
y_levels <- unique(test_data$y)
avg <- data.frame(matrix(0,nrow = 3,ncol = 1))
colnames(avg) <- c("value")
avg$date <- unique(date_vec)
Q75 <- data.frame(matrix(0,nrow = 3,ncol = 1))
colnames(Q75) <- c("value")
Q75$date <- unique(date_vec)
Q25 <- data.frame(matrix(0,nrow = 3,ncol = 1))
colnames(Q25) <- c("value")
Q25$date <- unique(date_vec)
for (i in 1:length(unique(date_vec))){
avg$value[i] <- mean(test_data$value[test_data$date == unique(date_vec)[i]])
Q75$value[i] <- quantile(test_data$value[test_data$date == unique(date_vec)[i]],0.75)
Q25$value[i] <- quantile(test_data$value[test_data$date == unique(date_vec)[i]],0.25)
}
##creating plot
fig <- plot_ly()
fig <- fig %>% layout(hovermode = 'x unified')
for(i in 1:length(x_levels)){
for(j in 1:length(y_levels)){
fig <- fig %>% add_trace(data = test_data, type = 'scatter', mode = 'lines+markers',
x = test_data$date[test_data$x == x_levels[i] & test_data$y == y_levels[j]],
y = test_data$value[test_data$x == x_levels[i] & test_data$y == y_levels[j]],
showlegend = FALSE, line = list(color = 'grey'), marker = list(color = 'grey'),
hoverinfo = 'text', text = paste0("some text dependent on x and y"))
}}
fig <- fig %>% add_trace(data = Q25, type = 'scatter', mode = 'lines', name = '25%-quantile', x = Q25$date,
y = Q25$value, showlegend = TRUE, line = list(color = 'darkred', dash = 'dot'),
hoverinfo = 'text', text = paste0("some text dependent on Q25"))
fig <- fig %>% add_trace(data = avg, type = 'scatter', mode = 'lines', name = 'mean value', x = avg$date,
y = avg$value, showlegend = TRUE, line = list(color = 'darkred', dahs = 'dash'),
hoverinfo = 'text', text = paste0("some text depenent on avg"))
fig <- fig %>% add_trace(data = Q75, type = 'scatter', mode = 'lines', name = '75%-quantile', x = Q75$date,
y = Q75$value, showlegend = TRUE, line = list(color = 'darkred', dash = 'dot'),
hoverinfo = 'text', text = paste0("some text dependent on Q75"))
#### This is the dummy trace in question:
fig <- fig %>% add_trace(x = Q25$date, y = Q25$value, type = 'scatter', mode = 'lines', fill = 'tonexty',
fillcolor = 'rgba(139,0,0,0.2)', line = list(color = 'darkred', width = 0.1), showlegend = FALSE,
hoverinfo = 'skip')
print(fig)
Is this a bug? Am I missing something obvious? Is there a better way to make the highlighting that does not create an additional hoverinfo? Thanks in advance!
Alright, this answer is not ideal—but it works.
There is good news, though. Plotly developers are already working on a solution. They're in the testing phase, which is where I got the idea for this answer. You can read about what Plotly's doing here.
First, I set a seed because you have random data. I used set.seed(3509). There were no changes to the data or fig.
When you hover over any point on your graph, your fill color will disappear. The tooltip does not show the trace that shouldn't be there anyway.
The fill color will return as soon as you move on or when the tooltip goes away.
This is the Javascript to call this hover event.
# the notation [12] is the curvenumber; it represnts a single trace
hoverer = "function(el, x) {
el.on('plotly_hover', function(d){
dt = {'fill': 'none'};
Plotly.restyle(el.id, dt, [12]);
});
el.on('plotly_unhover', function(d){
ft = {'fill':'tonexty'};
Plotly.restyle(el.id, ft, [12]);
});
}"
To visualize your plot with this event:
fig %>% htmlwidgets::onRender(hoverer)
I am trying to visualize multiple periods in a time-series/candlestick chart, highlighting certain dates by vertical lines. Similar to the sinus wave example here: https://plotly.com/r/sliders/
aval <- list(setNames(xts(matrix(data = rnorm(1:28), ncol = 4), as.Date(1:7)), c("Open","High","Low","Close")),
setNames(xts(matrix(data = rnorm(1:52), ncol = 4), as.Date(1:13)), c("Open","High","Low","Close")),
setNames(xts(matrix(data = rnorm(1:20), ncol = 4), as.Date(1:5)), c("Open","High","Low","Close")))
Looping through the list, I can achieve my desired result with respect to the vertical lines:
for (i in 1:length(aval)) {
fig <- plot_ly()
b <- data.frame(Date=index(aval[[i]]), coredata(aval[[i]]))
line <- list(type = "line", line = list(color = 'magenta'), # , dash = "dot", width = 0.5
xref = "x", yref = "paper", x0 = NA, x1 = NA, y0 = 0, y1 = 1)
l <- list(line, line)
l[[1]][['x0']] <- l[[1]][['x1']] <- b[1+1,1]
l[[2]][['x0']] <- l[[2]][['x1']] <- b[nrow(b)-1,1]
fig <- add_trace(fig, type = "candlestick", x = b[,1], open = b[,2], close = b[,5], high = b[,3], low = b[,4],
showlegend = FALSE) %>%
layout(xaxis = list(rangeslider = list(visible = F), type = "category"), shapes = l, font = list(size = 10))
fig <- fig %>% config(displayModeBar = FALSE)
print(fig)
}
However, using the slider layout, I am only able to use one vertical line, see:
n <- length(aval)
steps <- list()
fig <- plot_ly()
for (i in 1:length(aval)) {
b <- data.frame(Date=index(aval[[i]]), coredata(aval[[i]])[,c('Open','High','Low','Close')])
toggle <- ifelse(i == 1, TRUE, FALSE)
line <- list(type = "line", line = list(color = 'magenta', dash = "dot", width = 0.5),
xref = "x", yref = "paper", x0 = b[1+1,1], x1 = b[1+1,1], y0 = 0, y1 = 1)
fig <- add_trace(fig, type = "candlestick", x = b[,1], open = b[,2], close = b[,5], high = b[,3], low = b[,4],
visible = toggle, showlegend = FALSE) %>%
layout(xaxis = list(rangeslider = list(visible = F), type = "category"), shapes = line, font = list(size = 10))
step <- list(args = list('visible', rep(FALSE, length(aval))), label = i, method = 'restyle')
step$args[[2]][i] = TRUE
steps[[i]] = step
}
fig <- fig %>% layout(sliders = list(list(active = 0, steps = steps))) %>% config(displayModeBar = FALSE)
fig
Using a list of shapes, analog to my first example, does not seem to work:
steps <- list()
fig <- plot_ly()
for (i in 1:length(aval)) {
b <- data.frame(Date=index(aval[[i]]), coredata(aval[[i]])[,c('Open','High','Low','Close')])
toggle <- ifelse(i == 1, TRUE, FALSE)
line <- list(type = "line", line = list(color = 'magenta', dash = "dot", width = 0.5),
xref = "x", yref = "paper", x0 = NA, x1 = NA, y0 = 0, y1 = 1)
l <- list(line, line)
l[[1]][['x0']] <- l[[1]][['x1']] <- b[1+1,1] # as.character
l[[2]][['x0']] <- l[[2]][['x1']] <- b[nrow(b)-1,1]
fig <- add_trace(fig, type = "candlestick", x = b[,1], open = b[,2], close = b[,5], high = b[,3], low = b[,4],
visible = toggle, showlegend = FALSE) %>%
layout(xaxis = list(rangeslider = list(visible = F), type = "category"), shapes = l, font = list(size = 10))
step <- list(args = list('visible', rep(FALSE, length(aval))), label = i, method = 'restyle')
step$args[[2]][i] = TRUE
steps[[i]] = step
}
fig <- fig %>% layout(sliders = list(list(active = 0, steps = steps))) %>% config(displayModeBar = FALSE)
fig
Can anybody help me? Is this related to the visibility argument in steps, perhaps?
I've created a graph that lets you pick which group's data to plot. I'd like to change the title when you pick the group, but I'm not sure how or if its possible. I'm having trouble learning which way to structure lists for certain plotly parameters. Even if I could add custom text to graph would probably work.
#Working Example so Far
library(plotly)
x <- c(1:100)
random_y <- rnorm(100, mean = 0)
random_y_prim <- rnorm(100, mean = 50)
mydata <- data.frame(x, random_y, random_y_prim, group = rep(letters[1:4], 25))
# Make Group List Button
groupList <- unique(mydata$group)
groupLoop <- list()
for (iter in 1:length(groupList)) {
groupLoop[[iter]] <- list(method = "restyle",
args = list("transforms[0].value", groupList[iter]),
label = groupList[iter])
}
# Set up Axis labeling
f <- list(
family = "Verdana",
size = 18,
color = "#7f7f7f"
)
xLab <- list(
title = "x Axis",
titlefont = f
)
yLab <- list(
title = "y Axis",
titlefont = f
)
fig <- plot_ly(mydata, x = ~x, y = ~random_y
, type = 'scatter', mode = 'lines',
transforms = list(
list(
type = 'filter',
target = ~mydata$group,
operation = '=',
value = groupList[1]
)
)
)
fig <- fig %>%
layout(
title = "Updating Practice",
xaxis = xLab,
yaxis = yLab,
updatemenus = list(
list(
type = 'dropdown',xanchor = 'center',
yanchor = "top",
active = 1,
buttons = groupLoop
)
)
)
fig
I am trying to generate multiple graphs in Plotly for 30 different sales offices. Each graph would have 3 lines: sales, COGS, and inventory. I would like to keep this on one graph with 30 buttons for the different offices. This is the closest solution I could find on SO:
## Create random data. cols holds the parameter that should be switched
l <- lapply(1:100, function(i) rnorm(100))
df <- as.data.frame(l)
cols <- paste0(letters, 1:100)
colnames(df) <- cols
df[["c"]] <- 1:100
## Add trace directly here, since plotly adds a blank trace otherwise
p <- plot_ly(df,
type = "scatter",
mode = "lines",
x = ~c,
y= ~df[[cols[[1]]]],
name = cols[[1]])
## Add arbitrary number of traces
## Ignore first col as it has already been added
for (col in cols[-1]) {
p <- p %>% add_lines(x = ~c, y = df[[col]], name = col, visible = FALSE)
}
p <- p %>%
layout(
title = "Dropdown line plot",
xaxis = list(title = "x"),
yaxis = list(title = "y"),
updatemenus = list(
list(
y = 0.7,
## Add all buttons at once
buttons = lapply(cols, function(col) {
list(method="restyle",
args = list("visible", cols == col),
label = col)
})
)
)
)
print(p)
It works but only on graphs with single lines/traces. How can I modify this code to do the same thing but with graphs with 2 or more traces? or is there a better solution? Any help would be appreciated!
### EXAMPLE 2
#create fake time series data
library(plotly)
set.seed(1)
df <- data.frame(replicate(31,sample(200:500,24,rep=TRUE)))
cols <- paste0(letters, 1:31)
colnames(df) <- cols
#create time series
timeseries <- ts(df[[1]], start = c(2018,1), end = c(2019,12), frequency = 12)
fit <- auto.arima(timeseries, d=1, D=1, stepwise =FALSE, approximation = FALSE)
fore <- forecast(fit, h = 12, level = c(80, 95))
## Add trace directly here, since plotly adds a blank trace otherwise
p <- plot_ly() %>%
add_lines(x = time(timeseries), y = timeseries,
color = I("black"), name = "observed") %>%
add_ribbons(x = time(fore$mean), ymin = fore$lower[, 2], ymax = fore$upper[, 2],
color = I("gray95"), name = "95% confidence") %>%
add_ribbons(x = time(fore$mean), ymin = fore$lower[, 1], ymax = fore$upper[, 1],
color = I("gray80"), name = "80% confidence") %>%
add_lines(x = time(fore$mean), y = fore$mean, color = I("blue"), name = "prediction")
## Add arbitrary number of traces
## Ignore first col as it has already been added
for (col in cols[2:31]) {
timeseries <- ts(df[[col]], start = c(2018,1), end = c(2019,12), frequency = 12)
fit <- auto.arima(timeseries, d=1, D=1, stepwise =FALSE, approximation = FALSE)
fore <- forecast(fit, h = 12, level = c(80, 95))
p <- p %>%
add_lines(x = time(timeseries), y = timeseries,
color = I("black"), name = "observed", visible = FALSE) %>%
add_ribbons(x = time(fore$mean), ymin = fore$lower[, 2], ymax = fore$upper[, 2],
color = I("gray95"), name = "95% confidence", visible = FALSE) %>%
add_ribbons(x = time(fore$mean), ymin = fore$lower[, 1], ymax = fore$upper[, 1],
color = I("gray80"), name = "80% confidence", visible = FALSE) %>%
add_lines(x = time(fore$mean), y = fore$mean, color = I("blue"), name = "prediction", visible = FALSE)
}
p <- p %>%
layout(
title = "Dropdown line plot",
xaxis = list(title = "x"),
yaxis = list(title = "y"),
updatemenus = list(
list(
y = 0.7,
## Add all buttons at once
buttons = lapply(cols, function(col) {
list(method="restyle",
args = list("visible", cols == col),
label = col)
})
)
)
)
p
You were very close!
If for example you want graphs with 3 traces,
You only need to tweak two things:
Set visible the three first traces,
Modify buttons to show traces in groups of three.
My code:
## Create random data. cols holds the parameter that should be switched
library(plotly)
l <- lapply(1:99, function(i) rnorm(100))
df <- as.data.frame(l)
cols <- paste0(letters, 1:99)
colnames(df) <- cols
df[["c"]] <- 1:100
## Add trace directly here, since plotly adds a blank trace otherwise
p <- plot_ly(df,
type = "scatter",
mode = "lines",
x = ~c,
y= ~df[[cols[[1]]]],
name = cols[[1]])
p <- p %>% add_lines(x = ~c, y = df[[2]], name = cols[[2]], visible = T)
p <- p %>% add_lines(x = ~c, y = df[[3]], name = cols[[3]], visible = T)
## Add arbitrary number of traces
## Ignore first col as it has already been added
for (col in cols[4:99]) {
print(col)
p <- p %>% add_lines(x = ~c, y = df[[col]], name = col, visible = F)
}
p <- p %>%
layout(
title = "Dropdown line plot",
xaxis = list(title = "x"),
yaxis = list(title = "y"),
updatemenus = list(
list(
y = 0.7,
## Add all buttons at once
buttons = lapply(0:32, function(col) {
list(method="restyle",
args = list("visible", cols == c(cols[col*3+1],cols[col*3+2],cols[col*3+3])),
label = paste0(cols[col*3+1], " ",cols[col*3+2], " ",cols[col*3+3] ))
})
)
)
)
print(p)
PD: I only use 99 cols because I want 33 groups of 3 graphs