I'm creating a barplot in plotly where I want to add a vertical line. In the plot we initially only show one selected item from the legend. Now I want to have the vertical be the maximum height of the max bar shown in the plot. Unfortunately, I'm not able to make the vertical line reactive to the max value shown in the barplot. Here I tried to create a simple reproducible example:
library(plotly)
library(dplyr)
library(purrr)
set.seed(7)
df <- data.frame(
group = rep(LETTERS[1:4], each = 4),
subgroup = letters[1:16],
value = sample.int(16)
)
df_mtcars <- mtcars %>%
mutate(gear = as.factor(gear),
carb = as.factor(carb))
p <- plotly::plot_ly()
purrr::walk(unique(mtcars$carb), function(carb) {
p <<- p %>%
add_trace(data = df_mtcars[df_mtcars$carb == carb,],
x = ~gear,
y = ~mpg,
color = ~carb, type = "bar",
visible = if (carb == 3) {TRUE} else {"legendonly"}) %>%
# add vertical line
add_trace(type = "scatter", mode = "lines",
x = c(factor(3), factor(3)),
y = c(0, sum(df_mtcars %>% filter(carb == carb) %>% pull(mpg))),
showlegend = FALSE)
})
p
Created on 2025-12-17 with reprex v2.1.0
In the graph we have carb 3 as initially selected. This means that the height of the vertical line should be 48.9 because this is the max bar height. But if the user adds another item of the legend, the vertical line should be adjusted. How can we achieve this?
Now I want to have the vertical be the maximum height of the max bar shown in the plot
1.
You can add a vertical line vline that scales to the plot's y-range automatically like shown here. This is much simpler than adding it as a trace and works, since you don't want to toggle it via legend anyways.
library(plotly)
library(purrr)
p <- plotly::plot_ly()
purrr::walk(unique(mtcars$carb), function(carb) {
p <<- p |>
add_trace(data = df_mtcars[df_mtcars$carb == carb,],
x = ~gear,
y = ~mpg,
color = ~carb, type = "bar",
visible = if (carb == 3) {TRUE} else {"legendonly"})
})
p |>
layout(shapes = list(
list(
type = "line",
y0 = 0,
y1 = 0.95, # will always scale to the max because 95% of the
# y-scale is always the max value
yref = "paper",
x0 = 0,
x1 = 0,
line = list(color = "red", dash = "dot")
)
))
2. If you want to retain the hover functionality, you can
el.layout.yaxis.range[1]* 0.95p <- plotly::plot_ly()
purrr::walk(unique(mtcars$carb), function(carb) {
p <<- p |>
add_trace(data = df_mtcars[df_mtcars$carb == carb,],
x = ~gear,
y = ~mpg,
color = ~carb, type = "bar",
visible = if (carb == 3) {TRUE} else {"legendonly"})
})
p |>
add_trace(type = "scatter", mode = "lines",
x = c(factor(3), factor(3)),
y = c(0, sum(df_mtcars[df_mtcars$carb == 3,]$mpg)),
line = list(color = "red", width = 2, dash = "dash"),
showlegend = FALSE, hovertemplate = '<i>Max-Y-Value</i>: %{y}') |>
htmlwidgets::onRender("
function(el) {
el.on('plotly_restyle', function(data) {
Plotly.deleteTraces(el, [-1]);
// help from here: https://plotly.com/javascript/hover-text-and-formatting/
// Add trace back with same style but updated y range
Plotly.addTraces(el, {
type: 'scatter',
mode: 'lines',
x: [3, 3],
y: [0, el.layout.yaxis.range[1]* 0.95],
line: {color: 'red', width: 2, dash: 'dash'},
hovertemplate: '<i>Max-Y-Value</i>: %{y}',
showlegend: false
});
return true;
});
}
")
NOTE Both approaches assume, that the user does not manually rescale the y-axis. If they do so, the red line would also go right up to the y-range value. If you want to make this more robust, you would need to iterate over the 7 traces in data.data, take the ones which have "visible = true" and add up each common bar to get the maxima, then assign it to the vertical line.
set.seed(7)
df <- data.frame(
group = rep(LETTERS[1:4], each = 4),
subgroup = letters[1:16],
value = sample.int(16)
)
df_mtcars <- mtcars |>
transform(gear = as.factor(gear),
carb = as.factor(carb))