I'm trying to get a sensible X-axis in a ggplotly with class POSIXct, and maintain sensibility when zooming.
Calling ggplotly
directly on the ggplot object results in a plot in which the x-axis does NOT rescale on zoom, so after two zooms, you've zoomed in past the tick marks which makes the plot worthless.
Over here I found that we could autoscale the x-axis and it becomes sensible with each zoom... for numerics.
How do I make a sensible x-axis when it is class POSIXct? Or conversely, format it in a way that is sensible? And of course zooming should switch the date format to date-time format at the appropriate zoom level.
I've tried multiple combinations of tickmode
, type
, and tickformat
after piping to layout
. I get bizarre results in all combinations. E.g., the combo below results in a plot in which the hover dates are correct over the 30-year span, but the x-axis shows only about 12 days of scale (see screenshot below).
Here is an MRE to create the data and plot it.
library(ggplot2)
library(plotly)
# create dataframe with random POSIXct x-axis and sort
df <- data.frame(mydate = sort(as.POSIXct(round(runif(1000, max = 10e8), 0))),
value = sort(sample(1:1000)))
gg <- ggplot(df, aes(mydate, value)) +
geom_point()
ggplotly(gg) %>%
layout(xaxis = list(tickmode = "auto",
type = "date"
#tickformat = "%Y-%m"
),
yaxis = list(tickmode = "auto"))
Primarily 4 things have to happen, 2 of which you already addressed. The type
, tickmode
and autorange
for the axis and the data used for the axis.
It gets a bit trickier with dates, depending on how you build your plot before using ggplotly
, sometimes the data is converted to character text... (why, oh why Plotly???!!) That didn't happen here. However, the values' class is now numeric, not dates or POSIXct.
(BTW -- Plotly would recognize millisecond-formatted numeric dates - if designated in the layout, but R primarily uses seconds-based dates.)
You could multiply the values of x by 103 or you can just change the formatting back to dates after plotting.
Here's how you can do that:
fixer <- function(plt) { # make them dates again
plt <- plotly_build(plt) # build data
plt$x$data[[1]]$x <- as.POSIXct(plt$x$data[[1]]$x) # make them dates
plt # return plot
}
ggplotly(gg) %>%
layout(xaxis = list(tickmode = 'auto', type = "date", autorange = T),
yaxis = list(tickmode = "auto")) %>% fixer()
If you had more than one trace or split traces (typically due to colors' assignment) you could use lapply
to go through all of the x-axes' data, where the fixer()
has plt$x$data[[1]]$x
, the second trace would be plt$x$data[[2]]...
and so on.
Here's an example of what that might look like. There are a lot of different ways to plot and combine different data, so this method may require adjustments for different plots.
set.seed(234)
df <- data.frame(mydate = sort(as.POSIXct(round(runif(1000, max = 10e8), 0))),
value = sort(sample(1:1000)))
set.seed(394)
df <- mutate(df, value2 = sort(sample(1:1000), decreasing = T))
fixer2 <- function(plt) { # this could be used for 1 or more traces
plt <- plotly_build(plt) # prepare plot
lapply(1:length(plt$x$data), \(k) { # for each trace
plt$x$data[[k]]$x <<- as.POSIXct(plt$x$data[[k]]$x) # change the x back to date format
})
plt
}
ggplot(df, aes(x = mydate)) + geom_point(aes(y = value, color = "value")) +
geom_point(aes(y = value2, color = "value2"))
ggplotly(ggplot2::last_plot()) %>%
layout(xaxis = list(tickmode = 'auto', type = 'date', autorange = T)) %>%
fixer2()