Search code examples
rplotly

Dynamic Ordering and Customization in R Plotly


I have a large dataset with observation values over time. I am using plotly to visualize the trend.

Currently, because I have specific custom colors, widths, and line types, I have been treating each subject as a separate trace and manually rearranging them every time a new value comes in -- wanting the legend order to match the most recent values.

I'm close to a more elegant solution, but am unsure how to apply the width argument and also how to order the legend so the Subject with the highest most recent value is first (ie Susan has lowest 2000 value so would be last).

library(tidyverse)
library(plotly)

# raw data
df <-
  tribble(
    ~Year, ~Subject, ~Value,
    1980, 'A', 100,
    1990, 'A', 200, 
    2000, 'A', 250,
    1980, 'B', 400,
    1990, 'B', 300,
    2000, 'B', 100,
    1980, 'C', 200,
    1990, 'C', 100,
    2000, 'C', 500
  )

# each subject has specific custom viz elements
custom_stuff <-
  tribble(
    ~Subject, ~Name, ~LineColor, ~LineType, ~LineWidth,
    'A', 'John', 'blue', 'solid', 2,
    'B', 'Susan', '#ef0107', 'dash', 1,
    'C', 'Subject C', 'orange', 'dot', 1
  )

# i'm currently transposing the data like this
df_wide <-
  df |> 
  pivot_wider(names_from = 'Subject', values_from = 'Value')

# and then manually applying my custom elements and manually ordering the traces based on most recent value. yuck.
plot_ly(df_wide, x = ~Year) |> 
  add_trace(y = ~C, name = 'Subject C', type = 'scatter', mode = 'lines', 
            line = list(shape = 'spline', color = 'orange', width = 1, dash = 'dot')) |> 
  add_trace(y = ~A, name = 'John', type = 'scatter', mode = 'lines', 
            line = list(shape = 'spline', color = 'blue', width = 2, dash = 'solid')) |> 
  add_trace(y = ~B, name = 'Susan', type = 'scatter', mode = 'lines', 
            line = list(shape = 'spline', color = '#ef0107', width = 1, dash = 'dash'))

# i'd like to do something more dynamic and elegant like this, but unsure how to handle width and ordering of legend.
custom_df <-
  df |> 
  inner_join(custom_stuff, by = 'Subject')

plot_ly(custom_df, 
        x = ~Year,
        y = ~Value,
        color = ~I(LineColor),
        name = ~Name,
        type = 'scatter',
        mode = 'lines',
        linetype = ~I(LineType),
        # width = ~I(LineWidth),
        line = list(shape = 'spline', smoothing = 1.3))

Solution

  • Here is one possible approach which builds on your first approach using a wide dataset but adds the traces dynamically using Reduce and determines the order in which subjects/traces using some data wrangling:

    library(tidyverse)
    library(plotly)
    
    # Order of subjects and traces
    subject_order <- df |>
      slice_max(Year, by = Subject) |>
      arrange(desc(Year), desc(Value)) |>
      mutate(Subject = fct_inorder(Subject)) |>
      pull(Subject) |>
      levels()
    
    plot_ly(df_wide, x = ~Year) |>
      Reduce(\(x, y) {
        custom_stuff <- custom_stuff |> filter(Subject == y)
        add_trace(
          x,
          y = reformulate(y), name = custom_stuff$Name, type = "scatter", mode = "lines",
          line = list(
            shape = "spline", color = custom_stuff$LineColor,
            width = custom_stuff$LineWidth,
            dash = custom_stuff$LineType
          )
        )
      }, x = subject_order, init = _)