plotly.R icon indicating copy to clipboard operation
plotly.R copied to clipboard

`updatemenus` > `buttons` don't behave as expected when restyling `y` axis (ignores `color` channel)

Open jack-davison opened this issue 1 year ago • 1 comments

library(plotly)

dat <- structure(list(date = structure(c(1230768000, 1230771600, 1230775200, 
  1230778800, 1230782400, 1230786000, 1230789600, 1230793200, 1230796800, 
  1230800400, 1230768000, 1230771600, 1230775200, 1230778800, 1230782400, 
  1230786000, 1230789600, 1230793200, 1230796800, 1230800400, 1230768000, 
  1230771600, 1230775200, 1230778800, 1230782400, 1230786000, 1230789600, 
  1230793200, 1230796800, 1230800400, 1230768000, 1230771600, 1230775200, 
  1230778800, 1230782400, 1230786000, 1230789600, 1230793200, 1230796800, 
  1230800400), tzone = "GMT", class = c("POSIXct", "POSIXt")), 
      nox = c(113, 40, 48, 36, 40, 50, 50, 53, 80, 111, NA, NA, 
      NA, NA, NA, NA, NA, NA, NA, NA, 130, 63, 76, 69, 61, 61, 
      61, 92, 57, 50, 31, 34, 27, 19, 21, 25, 19, 31, 42, 48), 
      pm10 = c(46, 49, 46, NA, 38, 32, 36, 32, 30, 32, NA, NA, 
      NA, NA, NA, NA, NA, NA, NA, NA, 53, 46, 45, 43, 37, 35, 37, 
      38, 33, 31, 44, 39, 40, 35, 34, 30, 28, 31, 29, 27), site = structure(c(1L, 
      1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 2L, 2L, 2L, 2L, 2L, 2L, 
      2L, 2L, 2L, 2L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 4L, 
      4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L), levels = c("1", "2", 
      "3", "4"), class = "factor")), row.names = c(NA, -40L), class = c("tbl_df", 
  "tbl", "data.frame"))

plot_ly(x = dat$date, y = dat$nox) |>
  add_lines(color = dat$site) |>
  layout(updatemenus = list(list(
    type = "buttons",
    y = 0.8,
    buttons = list(
      list(
        method = "restyle",
        args = list("y", list(dat$nox)),
        label = "NO<sub>x</sub>"
      ),
      list(
        method = "restyle",
        args = list("y", list(dat$pm10)),
        label = "PM<sub>10</sub>"
      )
    )
  )))

I would imagine that clicking the "NOx" button would do effectively nothing, but instead it seems to strip away the color feature of the plot.

Before clicking:

image

After clicking:

image

Am I doing something wrong, or is this a bug?

jack-davison avatar Aug 15 '24 12:08 jack-davison

It's not a bug, but I wouldn't say it's intuitive for R users, either. layout.updatemenus, plotlyProxy(), and the like more closely resemble the plotly.js side of things than they do the handy abstractions you find in plot_ly(). In your case, you left off the optional third argument inside of the lists you gave to args. That argument specifies which trace(s) you want your button to update, and it updates all of them if the argument is left off.

What's happening here isn't that the color is being stripped away, it's that all the y values for each trace are being updated to match that of the first trace; since the x values already matched each other, this stacks each trace one on top of the other, and the extra y values you gave aren't plotted since they don't have corresponding x values. The code here has what I think was your expected behavior:

dat |>
  plot_ly(x = ~date, y = ~nox) |>
  add_lines(color = ~site) |>
  layout(updatemenus = list(list(
    type = 'buttons',
    y = 0.8,
    buttons = ~list(
      list(
        method = 'restyle',
        # args = list('y', list(nox[1:10], nox[21:30], nox[31:40])),
        args = list('y', na.omit(nox) |> split(site[!is.na(nox)], drop = TRUE) |> unname()),
        label = 'NO<sub>x</sub>'
      ),
      list(
        method = 'restyle',
        # args = list('y', list(pm10[1:10], pm10[21:30], pm10[31:40])),
        args = list('y', na.omit(pm10) |> split(site[!is.na(pm10)], drop = TRUE) |> unname()),
        label = 'NO<sub>10</sub>'
      )
    )
  )))

In that example, I commented what the code would be if I hardcoded the new y values, but the uncommented version is more robust (though less straightforward). If you're looking for an easier way to do this, I have an old PR for a helper function called plotly_merge() that would do most of the heavy lifting for you. Here's an example of what it would look like:

list(
  'NO<sub>x</sub>' = dat |>
    plot_ly(x = ~date, y = ~nox) |>
    add_lines(color = ~site),
  'NO<sub>10</sub>' = dat |>
    plot_ly(x = ~date, y = ~pm10) |>
    add_lines(color = ~site)
) |> 
  plotly_merge(type = 'buttons', y = 0.8)

Edit: here is a link to the PR

wholmes105 avatar Oct 14 '24 20:10 wholmes105