1

I want to color code the background of my scatterplot, e.g. to classify the data into bad, medium and good areas. Hence, I want the limits of the squares to extend to the figure margins when zooming out.

As to my knowledge, there is not (yet) a hrect() function as in Python, I wrote a helper function using xref = "paper" on the X axis.

library("plotly")

fig <- plot_ly(data = iris, x = ~Sepal.Length, y = ~Petal.Length)

hrect <- function(y0 = 0, y1 = 1, fillcolor = "red", opacity = 0.2) {
  list(
    type = "rect",
    x0 = 0,
    x1 = 1,
    xref = "paper",
    y0 = y0,
    y1 = y1,
    line_width = 0,
    fillcolor = fillcolor,
    opacity = opacity,
    layer = "below"
  )
}

fig <- fig |> plotly::layout(shapes = list(
  hrect(y0 = 0, y1 = 3, fillcolor = "red"),
  hrect(y0 = 3, y1 = 6, fillcolor = "blue"),
  hrect(y0 = 6, y1 = 8, fillcolor = "green")
))

fig

enter image description here

This extends the zooming to +/- infinity on the X axis, but I would like to have the same behavior for the upper (green) and lower (red) rectangles on the Y axis. Is it maybe possible to use different yrefs for y0 and y1?

UPDATE:

Thanks to the comprehensive answer by @SamR, there's an easy work-around for my problem. But to clarify, my question was more if there is a "proper" way to do with plotly in R.

For illustration, you could achieve this behavior for the red rectangle in ggplot2 by setting xmin, xmax and (!) ymin to +/-Inf:

library("ggplot2")
gg <- ggplot() 
gg <- gg + geom_rect(
  aes(
    xmin = -Inf, 
    xmax = Inf, 
    ymin = -Inf, 
    ymax = 3), 
  fill = 2,
  alpha = 0.2
) 

gg <- gg + geom_point(
    data = iris,
    aes(
      x = Sepal.Length, 
      y = Petal.Length)
)

gg <- gg + coord_cartesian(
  xlim = c(-10, 10), 
  ylim = c(-10, 10)
)
gg

enter image description here

moremo
  • 315
  • 2
  • 11

1 Answers1

1

I like what you have done here. It is possible to extend it to create a rectangle that extends infinitely in the y-direction, like vrect in plotly.py.

But using it in combination with hrect will create two problems:

  1. It covers the whole y-axis, so will overlap with all hrect rectangles, changing their color.
  2. It only extends infinitely on the y-axis, so there is a change in color at the point on the x-axis it no longer overlaps.

Here is an example of what I mean:

vrect <- function(
    x0 = 0, x1 = 1, y0 = 0, y1= 1, 
    fillcolor = "red", opacity = 0.2) {
  list(
    type = "rect",
    x0 = x0,
    x1 = x1,
    yref = "paper",
    y0 = y0,
    y1 = y1,
    line_width = 0,
    fillcolor = fillcolor,
    opacity = opacity,
    layer = "below"
  )
}

fig <- plot_ly(data = iris, x = ~Sepal.Length, y = ~Petal.Length)

fig  |> plotly::layout(shapes = list(
  vrect(fillcolor = "red", x0 = 0, x1 = ~max(Sepal.Length)),
  hrect(y0 = 0, y1 = 3, fillcolor = "red"),
  hrect(y0 = 3, y1 = 6, fillcolor = "blue"),
  hrect(y0 = 6,  y1 = 8, fillcolor = "green")
))

All rectangles extend infinitely, but it is not the desired effect:

Not the desired effect

Practical solution

I think the best thing is to just extend your top and bottom hrect rectangles to a ridiculously large range on the y-axis, e.g. with iris you could choose c(-1000, 1000).

The range will vary according to the data, but unless you have enough RAM to plot an infinite number of points, there will always be a value well outside the range of the data.

You will also need to reset the zoom to the original, done here with yaxis = list(range = c(0, 8)) in the layout call:

Y_RANGE = 1000
fig  |> plotly::layout(shapes = list(
  hrect(y0 = Y_RANGE*-1, y1 = 3, fillcolor = "red"),
  hrect(y0 = 3, y1 = 6, fillcolor = "blue"),
  hrect(y0 = 6,  y1 = Y_RANGE, fillcolor = "green")),
  yaxis = list(range = c(0, 8))
)

Output (still image):

Still

Output (with zoom):

zoom

SamR
  • 8,826
  • 3
  • 11
  • 33
  • 1
    I have updated my question accordingly - but as long there's no "proper" way to do it, I will certainly do it as suggested! Thanks!! – moremo Jul 27 '22 at 09:24
  • Do you know if there's a way to get the "autoscale" function only to scale to the points in this practical workaround? – moremo Aug 09 '22 at 08:39
  • @moremo I don't think so - if you add a rectangle I think it will scale to it so you'll need to add the scale manually e.g. with `yaxis = list(range = c(0, 8))` in the example. – SamR Aug 09 '22 at 08:51
  • Thanks @SamR. With the fantastic dynamic zooming feature provided by `plotly` it's a real pity that certain traces can't just be used as background layers and are ignored in "autoscale" etc. – moremo Aug 09 '22 at 09:18
  • I've a follow question to your answer, as once you click on `autoscale` it's zooms out to the given ridiculous high `YRANGE`. Even more problematic is that this permanently modifies the aspect ratio of the plot. Anyhow to adapt for that in you solution? Compare also https://stackoverflow.com/questions/73741517/ignore-traces-for-autoscale-button-in-plotly – moremo Feb 21 '23 at 16:10
  • @moremo no easy way that I know - it could be done by editing the javascript of the `htmlwidget` created but it would be a hack and I think more trouble than it is worth. You could post a separate question if you want to try. – SamR Feb 21 '23 at 16:13
  • I have the color highlighting as an additional option, so every time it's selected this screws up the dimensions. As I watch the zoom (`plotly_relayout`) event, this is not the case if I zoom in/out. So maybe it would be possible to permanently set a zoom? – moremo Feb 21 '23 at 16:31
  • yes good idea - that is possible for either or both axes: `p |> layout(xaxis = list(fixedrange = TRUE), yaxis = list(fixedrange = TRUE))` - see [this question](https://stackoverflow.com/questions/62852809/how-to-disable-the-zoom-of-a-plotly-chart-in-r) – SamR Feb 21 '23 at 16:35