41

I'm using the plotly package and I'm trying to add a horizontal line to a graph. Is there any way of doing it using plotly?

It can be done using ggplot2 and the ggplotly function as shown below:

library(plotly)

p <- ggplot() +
  geom_hline(yintercept = 4) +
  xlim(c(0,10)) +
  ylim(c(0,10))

ggplotly(p)

But I can't add this to an existing plotly plot.

Also, the axis of my charts are not fixed, so it would be difficult (but not impossible) to just work out an x and y coordinate system for a horizontal line, but I'd rather just add one automatically.

I've looked into the y0 and dy arguments, but I can't seem to get the code for those to work, either. I'm not quite sure what they do exactly, but I think they're maybe what I'm looking for? I can't find good examples of their usage.

Sam Helmich
  • 949
  • 1
  • 8
  • 18

4 Answers4

78

There are two main ways to do this (using either data or 'paper' coordinates). Assuming data coordinates, the easiest current way is via add_segments():

plot_ly() %>%
  add_segments(x = 4, xend = 4, y = 0, yend = 10) %>%
  add_segments(x = 3, xend = 5, y = 5, yend = 5)

enter image description here

Notice how we've hard coded the extent of these lines in data coordinates; so when zooming and panning the plot, the line will be "clipped" at those values. If you don't want these lines to be clipped, use a line shape with xref/yref set to paper (this puts the graph region on a 0-1 scale, rather than on the x/y data scale):

vline <- function(x = 0, color = "red") {
  list(
    type = "line", 
    y0 = 0, 
    y1 = 1, 
    yref = "paper",
    x0 = x, 
    x1 = x, 
    line = list(color = color)
  )
}

hline <- function(y = 0, color = "blue") {
  list(
    type = "line", 
    x0 = 0, 
    x1 = 1, 
    xref = "paper",
    y0 = y, 
    y1 = y, 
    line = list(color = color)
  )
}

plot_ly() %>%
  layout(shapes = list(vline(4), hline(5)))

enter image description here

Carson
  • 2,617
  • 1
  • 21
  • 24
  • 1
    thank you. how can I avoid the added vertical line to be displayed in the legend? – RockScience Jan 24 '18 at 04:24
  • 2
    For some reason, the first code in your answer does not produce both lines. It only produces the horizontal line. But if I change it like this: `plot_ly() %>% add_lines(x = c(4, 4), y = c(0, 10)) %>% add_lines(x = c(3, 5), y = c(5, 5))`, it produces both lines. I have `plotly` version `4.7.0` – umair durrani Mar 30 '18 at 20:35
  • 1
    how do we use this if we have dual y axis and want to show to hlines – SNT Aug 31 '20 at 19:35
18

Alternatively, you could add a shape (i.e. line) under layout(). The following example adds a vertical line:

p <- plot_ly(data, x = ~x.data, y = ~y.data, text = ~text.data, type = 'scatter', 
       mode = 'markers', marker = list(size = ~size.data, opacity= 0.5)) %>%
     layout(shapes=list(type='line', x0= 0.2, x1= 0.2, y0=min(allyvalues), y1=max(allyvalues), line=list(dash='dot', width=1)),
       title = 'This is the Title',
       xaxis = list(title = "X-Axis", showgrid = TRUE),
       yaxis = list(title = "Y-Axis", showgrid = TRUE))
p
user3268362
  • 239
  • 2
  • 6
4

Building on Carson's nice answer above, here is a convenience function closer to ggplot's geom_vline()

# Add vertical line(s) at position x to plotly plot p
# Additional arguments: color, width (px), dash ('solid','dot', 'dash', etc)
# See https://plotly.com/r/reference/#layout-shapes-items-shape-line
add_vline = function(p, x, ...) {
  l_shape = list(
    type = "line", 
    y0 = 0, y1 = 1, yref = "paper", # i.e. y as a proportion of visible region
    x0 = x, x1 = x, 
    line = list(...)
  )
  p %>% layout(shapes=list(l_shape))
}
Pierre Gramme
  • 1,209
  • 7
  • 23
  • 3
    I can't add multiple vlines this way: `fig %>% add_vline(...) %>% add_vline(...)` because I think multiple `layout` calls aren't additive. A little "gotcha" that got me. For multiple lines, construct a list and pass to `shapes` in `layout()`. – Spacedman Jul 10 '20 at 13:57
1

To make the function additive the following modifications to the function can be used


add_vline = function(p, x, ...) {

  if(!is.null(p$x$layoutAttrs)){
      index <- unname(which(sapply(p$x$layoutAttrs, function(x) 
  !is.null(x$shapes))))
    } else {
      index <- integer()
    }

    l_shape = list(
      type = "line",
      y0 = 0, y1 = 1, yref = "paper", # i.e. y as a proportion of visible region
      x0 = x, x1 = x,
      line = list(
        ...
      ),
      layer = "below"
    )

    if(length(index) > 0){
      shapes <- p$x$layoutAttrs[[index]]$shapes
      shapes[[length(shapes) + 1]] <- l_shape
      p$x$layoutAttrs[[index]]$shapes <- shapes
    } else {
      p <- plotly::layout(
        p = p,
        shapes = list(l_shape)
      )
    }
   p
}