'R Plotly Bar Chart - Add horizontal line markers

I want to draw a bar chart with two bars side by side for each category (LETTERS) and add a horizontal line to each bar (y_ref as red lines & z_ref as green lines). It should look like this:

enter image description here

I tried it with add_trace (and add_lines, add_segment, etc) but can't find the correct way to make it work. Here's one of the reprex's I tried so far.

df <- tibble(x = LETTERS[1:5], y = runif(5), z = runif(5), y_ref = runif(5), z_ref = runif(5))

plot_ly(
  df,
  x = ~x,
  y = ~y,
  type = "bar",
  name = "a"
) %>% add_trace(
  y = ~z, 
  name = "b"
) %>% layout(
  legend = list(
    orientation = "h"
  )
) %>% add_trace(
  y = ~y_ref, 
  type = 'scatter', 
  mode = 'lines',
  marker = list(
    line = list(
      width = 2,
      color = "red"
    )
  )
) %>% add_trace(
  y = ~z_ref, 
  type = 'scatter', 
  mode = 'lines',
  marker = list(
    line = list(
      width = 2,
      color = "green"
    )
  )
)

Edit: I need a solution for n bars.



Solution 1:[1]

You have to use shapes for line segments in Plotly. Additionally, because the x-axis is discrete, the x for the shapes will need to be in paper space.

When working in paper space, you use the plot domain to figure out the values for x. Plotly has a default domain set to [0, 1] for both x and y axes.

There is a gap between bars; that has to be accounted for as well. I used three functions to create the line segments, using all of this information.

Lastly, I used a seed for repeatability.

Libraries, seed, and base plot

library(tidyverse)
library(plotly)

set.seed(8)
df <- tibble(x = LETTERS[1:5], y = runif(5), z = runif(5),
             y_ref = runif(5), z_ref = runif(5))

p = plot_ly(df, x = ~x, y = ~y,
        type = "bar", name = "a") %>% 
  add_trace(y = ~z, name = "b") 

Create the segments

# standard shape elements, call for color
details <- function(col){
  list(type = 'line',
       line = list(color = col),
       xref = "paper", yref = "y")
}
# green lines for orange bars
segs = lapply(1:nrow(df),
              function(k){
                x1 <- (k + k)/10 - .02  # if the domain is [0, 1]
                x0 <- x1 - .08
                y0 <- y1 <- df[k, ]$z_ref
                line = list("x0" = x0, "x1" = x1,
                            "y0" = y0, "y1" = y1)
                deets = details("green")
                c(deets, line)
              })
# green lines for blue bars
segs2 = lapply(1:nrow(df),
               function(j){
                 x1 <- (j + j - 1)/10  # if the domain is [0, 1]
                 x0 <- x1 - .08
                 y0 <- y1 <- df[j, ]$y_ref
                 line = list("x0" = x0, "x1" = x1,
                             "y0" = y0, "y1" = y1)
                 deets = details("red")
                 c(deets, line)
               })
segs = append(segs, segs2)

Put it together

p %>% layout(legend = list(orientation = "h"),
             shapes = segs) 

enter image description here

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Kat