1

I'm trying to use R plot_ly to create a line chart which lines can be toggled on and off using filter buttons that filter along "Product" and "Chip_type". The idea is that suppliers ("Supplier"/"Supplier_text") supply different kinds of chips ("Chip_type") monthly ("Date") for different product segments of a company ("Product"). To get an overview over the top suppliers, I would like to draw one line per supplier, with the "Supplier_text" displayed in the legend, legend entries sorted descendingly by the abs(number) displayed in front of the "Supplier_text". The data tibble is sorted correctly in that regard. The "Overall" entries refer to the sum of all suppliers for that product.

The full data set is to be found at the end of the post.

sample from dat :

    Date(chr) Supplier              Supplier_text                        order(int) Chip_type Product     n(chr)
1   2019-11   Overall Smartphones   94757 |   17.9% - Overall Smartphones     1     Micro   Smartphones    106
2   2019-11   Overall Smartphones   94757 |   17.9% - Overall Smartphones     1      Nano   Smartphones  16920
3   2019-11   Overall Smartphones   94757 |   17.9% - Overall Smartphones     1    BiMech   Smartphones  61216
4   2019-11   Overall Smartphones   94757 |   17.9% - Overall Smartphones     1     Titan   Smartphones 363698
5   2019-11   Overall Smartphones   94757 |   17.9% - Overall Smartphones     1   Quantum   Smartphones  50797
6   2019-11   Overall Smartphones   94757 |   17.9% - Overall Smartphones     1  Platinum   Smartphones  52715
7   2019-11   Overall Smartphones   94757 |   17.9% - Overall Smartphones     1 PlainChip   Smartphones 174342
8   2019-11   Overall Smartphones   94757 |   17.9% - Overall Smartphones     1   Classic   Smartphones   9319
9   2019-12   Overall Smartphones   94757 |   17.9% - Overall Smartphones     1     Micro   Smartphones     92
10  2019-12   Overall Smartphones   94757 |   17.9% - Overall Smartphones     1      Nano   Smartphones  16928
11  2019-12   Overall Smartphones   94757 |   17.9% - Overall Smartphones     1    BiMech   Smartphones  40920
17  2019-11      Overall Monitors     -33239 |  -37.8% - Overall Monitors     2     Micro      Monitors      3
18  2019-11      Overall Monitors     -33239 |  -37.8% - Overall Monitors     2      Nano      Monitors   1536
19  2019-11      Overall Monitors     -33239 |  -37.8% - Overall Monitors     2    BiMech      Monitors   6793
20  2019-11      Overall Monitors     -33239 |  -37.8% - Overall Monitors     2     Titan      Monitors  45146
21  2019-11      Overall Monitors     -33239 |  -37.8% - Overall Monitors     2   Quantum      Monitors   7922
22  2019-11      Overall Monitors     -33239 |  -37.8% - Overall Monitors     2  Platinum      Monitors   5359
23  2019-11      Overall Monitors     -33239 |  -37.8% - Overall Monitors     2 PlainChip      Monitors  27390
24  2019-11      Overall Monitors     -33239 |  -37.8% - Overall Monitors     2   Classic      Monitors   1131
25  2019-12      Overall Monitors     -33239 |  -37.8% - Overall Monitors     2     Micro      Monitors     12
33  2019-11                     A                    -17385 |  -88.0% - A     3     Titan   Smartphones   3619
34  2019-11                     A                    -17385 |  -88.0% - A     3  Platinum   Smartphones     13
35  2019-11                     A                    -17385 |  -88.0% - A     3   Quantum   Smartphones      2

To keep the order (and later be able to toggle the correct lines!) I'm looping to add traces to an empty plot_ly object like this:

library(stringr)
library(dplyr)
library(plotly)


# "Rebuilding" the data frame as the loop runs to see if what the loop does to the traces ends up being the same (order) as the original data frame. For that, I create an empty object first:
dat_plotly_object_copy = c() 


plotly_object <- plot_ly()
id = 1

# I loop along "order", which marks all data of a single supplier:
for(id in 1:max(dat$order)){ 
  dat_one_supplier <- filter(dat, order == id)
  plotly_object <- plotly_object %>% add_trace(., data = dat_one_supplier,
# I filter the data set by supplier, to be able to create a line along the dates (~x) per supplier (~Supplier_text) and Chip_type (~n):
                                 x = ~Date,
                                 y = ~n,
                                 color = ~Supplier_text,
                                 type = "scatter",
                                 mode = "lines") 
  dat_plotly_object_copy <- dat_plotly_object_copy %>% 
    rbind(.,dat_one_supplier)
}

identical(dat, dat_plotly_object_copy)
# The created data frame seems to be identical to what the loop does - so the order should match (?)

Using this code to set the legend...

Parts_legend <- list(
  font = list(
    family = "sans-serif",
    size = 12,
    color = "#000"),
  title = list(text="<b> Delta previous month by Supplier - Absolute </b>"),
  bgcolor = "#E2E2E2",
  bordercolor = "#FFFFFF",
  borderwidth = 2,
  layout.legend = "constant",
  traceorder = "grouped")

.. and showing the object:

plotly_object %>% 
  layout(legend = Parts_legend,
         title = "by supplier delta previous month",
         xaxis = list(title = 'Date'),
         yaxis = list(title = 'Chip Volume'))

Leaves me with the following chart, which seems correct: Suppliers are entered by the abs(number) preceding the name! [1]: https://i.stack.imgur.com/bDTWZ.png

Now I will need to add the buttons. In the first step, I create two data frames that are supposed to indicate, if a line will later be visible (TRUE) nor not (FALSE). I seek to create them in the same format like dat - so that I get a TRUE or FALSE for every line of dat/the values the filtered variable can take:

Parts_product_filter <- select(dat,Supplier_text,order,Product,Chip_type) %>% 
  mutate(Smartphones = ifelse(Product == "Smartphones",T,F) %>% sapply(.,list), 
         TVs = ifelse(Product == "TVs",T,F) %>% sapply(.,list),
         Monitors = ifelse(Product == "Monitors",T,F) %>% sapply(.,list),
         Miscellaneous = ifelse(Product == "Miscellaneous",T,F) %>% sapply(.,list))


Parts_chip_type_filter <- select(dat,Supplier_text,order,Product,Chip_type) %>% 
  mutate(Micro = ifelse(Chip_type == "Micro",T,F) %>% sapply(.,list),
         Nano = ifelse(Chip_type == "Nano",T,F) %>% sapply(.,list),
         BiMech = ifelse(Chip_type == "BiMech",T,F) %>% sapply(.,list),
         Titan = ifelse(Chip_type == "Titan",T,F) %>% sapply(.,list),
         Quantum = ifelse(Chip_type == "Quantum",T,F) %>% sapply(.,list),
         Platinum = ifelse(Chip_type == "Platinum",T,F) %>% sapply(.,list),
         PlainChip = ifelse(Chip_type == "PlainChip",T,F) %>% sapply(.,list),
         Classic = ifelse(Chip_type == "Classic",T,F) %>% sapply(.,list))

Adding the buttons to the plotly_object, I try to set them so that they filter based on the individual columns of the "_filter" data frames created above:

plotly_object %>% 
  layout(legend = Parts_legend,
         title = "by supplier delta previous month",
         xaxis = list(title = 'Date'),
         yaxis = list(title = 'Chip Volume'),
         updatemenus = list(
           list(
             active = 0,
             type = "dropdown",
             y = 1.1,
             direction = "right",

# See from here:
             buttons = list(
               
               list(label = "All",
                    method = "restyle",
                    args = list("visible",T)),
               
               list(label = "Smartphones",
                    method = "restyle",
                    args = list("visible",Parts_product_filter$Smartphones)),
               
               list(label = "TVs",
                    method = "restyle",
                    args = list("visible",Parts_product_filter$TVs)),
               
               list(label = "Monitors",
                    method = "restyle",
                    args = list("visible",Parts_product_filter$Monitors)),
               
               list(label = "Miscellaneous",
                    method = "restyle",
                    args = list("visible",Parts_product_filter$Miscellaneous))
             )
           ),
           list(
             active = 0,
             type = "dropdown",
             y = 1.03,
             direction = "right",
             buttons = list(
               
               list(label = "All",
                    method = "restyle",
                    args = list("visible",T)),
               
               list(label = "Micro",
                    method = "restyle",
                    args = list("visible",Parts_chip_type_filter$Micro)),
               
               list(label = "Nano",
                    method = "restyle",
                    args = list("visible",Parts_chip_type_filter$Nano)),
               
               list(label = "BiMech",
                    method = "restyle",
                    args = list("visible",Parts_chip_type_filter$BiMech)),
               
               list(label = "Titan",
                    method = "restyle",
                    args = list("visible",Parts_chip_type_filter$Titan)),
               
               list(label = "Quantum",
                    method = "restyle",
                    args = list("visible",Parts_chip_type_filter$Quantum)),
               
               list(label = "Platinum",
                    method = "restyle",
                    args = list("visible",Parts_chip_type_filter$Platinum)),
               
               list(label = "PlainChip",
                    method = "restyle",
                    args = list("visible",Parts_chip_type_filter$PlainChip)),
               
               list(label = "Classic",
                    method = "restyle",
                    args = list("visible",Parts_chip_type_filter$Classic))
             )
             
           )
         )
  )

And exactly that does not work. I must be setting the filters wrong. I know because when I filter the combination of "Product = TVs" and "Chip_type = Nano", no lines appear....

https://i.stack.imgur.com/MaJ5r.png

... although there is data:

> dat %>% filter(Product == "TVs") %>% filter(Chip_type == "Nano")
# A tibble: 8 x 7
  Date    Supplier    Supplier_text                 order Chip_type Product n    
  <chr>   <chr>       <chr>                         <int> <chr>    <chr>   <chr>
1 2019-11 Overall TVs 14373 |    6.0% - Overall TVs     4 Nano     TVs     4643 
2 2019-12 Overall TVs 14373 |    6.0% - Overall TVs     4 Nano     TVs     6904 
3 2019-11 J           2603 |    5.8% - J               13 Nano     TVs     3    
4 2019-12 J           2603 |    5.8% - J               13 Nano     TVs     3    
5 2019-11 M           -1711 |  -19.4% - M              16 Nano     TVs     2    
6 2019-12 M           -1711 |  -19.4% - M              16 Nano     TVs     1    
7 2019-11 O           1315 |   23.6% - O               19 Nano     TVs     2    
8 2019-12 O           1315 |   23.6% - O               19 Nano     TVs     1 

I'm really looking forward to your suggestions how to set the visibility toggle of the buttons correctly!

I know that there is two similiar posts, but focussed on multiple graphs. It may very well be my lack of skill, but I could not get my problem solved with the provided solution and would appreciate your consideration and help! Switch displayed traces via plotly dropdown menu Multiple lines/traces for each button in a Plotly drop down menu in R

Something similar, but with one filter, done in Python (not R): Plotly: How to toggle traces with a button similar to clicking them in legend?

The follow-up would be: Is it possible to select multiple categories, i. e. "Nano" and "Classic", and possibly "Smartphones" and "TVs" from the other filter, at the same time? Here is a post for Python, but no answers, unfortunately: Selecting multiple buttons at once in a plotly graph

Thank you so much in advance!

Full data set for import:

<!-- begin snippet: js hide: true -->
dat <- structure(list(Date = c("2019-11", "2019-11", "2019-11", "2019-11", 
"2019-11", "2019-11", "2019-11", "2019-11", "2019-12", "2019-12", 
"2019-12", "2019-12", "2019-12", "2019-12", "2019-12", "2019-12", 
"2019-11", "2019-11", "2019-11", "2019-11", "2019-11", "2019-11", 
"2019-11", "2019-11", "2019-12", "2019-12", "2019-12", "2019-12", 
"2019-12", "2019-12", "2019-12", "2019-12", "2019-11", "2019-11", 
"2019-11", "2019-11", "2019-12", "2019-12", "2019-12", "2019-12", 
"2019-11", "2019-11", "2019-11", "2019-11", "2019-11", "2019-11", 
"2019-11", "2019-11", "2019-12", "2019-12", "2019-12", "2019-12", 
"2019-12", "2019-12", "2019-12", "2019-12", "2019-11", "2019-11", 
"2019-11", "2019-11", "2019-12", "2019-12", "2019-12", "2019-12", 
"2019-12", "2019-11", "2019-11", "2019-11", "2019-11", "2019-11", 
"2019-12", "2019-12", "2019-12", "2019-12", "2019-11", "2019-11", 
"2019-11", "2019-11", "2019-11", "2019-12", "2019-12", "2019-12", 
"2019-12", "2019-12", "2019-11", "2019-11", "2019-11", "2019-11", 
"2019-11", "2019-12", "2019-12", "2019-12", "2019-12", "2019-12", 
"2019-12", "2019-11", "2019-11", "2019-12", "2019-12", "2019-11", 
"2019-12", "2019-12", "2019-11", "2019-11", "2019-11", "2019-12", 
"2019-12", "2019-11", "2019-12", "2019-12", "2019-12", "2019-12", 
"2019-11", "2019-11", "2019-12", "2019-12", "2019-11", "2019-11", 
"2019-11", "2019-12", "2019-12", "2019-11", "2019-11", "2019-11", 
"2019-12", "2019-12", "2019-12", "2019-11", "2019-11", "2019-12", 
"2019-12", "2019-12", "2019-12", "2019-11", "2019-11", "2019-11", 
"2019-11", "2019-11", "2019-11", "2019-11", "2019-12", "2019-12", 
"2019-12", "2019-12", "2019-12", "2019-12", "2019-12", "2019-12", 
"2019-11", "2019-11", "2019-12", "2019-12", "2019-12", "2019-12", 
"2019-12", "2019-11", "2019-11", "2019-11", "2019-11", "2019-12", 
"2019-12", "2019-11", "2019-11", "2019-11", "2019-11", "2019-12", 
"2019-12", "2019-12", "2019-12", "2019-11", "2019-11", "2019-11", 
"2019-12", "2019-12", "2019-12", "2019-11", "2019-12", "2019-12", 
"2019-11", "2019-11", "2019-12", "2019-12"), Supplier = c("Overall Smartphones", 
"Overall Smartphones", "Overall Smartphones", "Overall Smartphones", 
"Overall Smartphones", "Overall Smartphones", "Overall Smartphones", 
"Overall Smartphones", "Overall Smartphones", "Overall Smartphones", 
"Overall Smartphones", "Overall Smartphones", "Overall Smartphones", 
"Overall Smartphones", "Overall Smartphones", "Overall Smartphones", 
"Overall Monitors", "Overall Monitors", "Overall Monitors", "Overall Monitors", 
"Overall Monitors", "Overall Monitors", "Overall Monitors", "Overall Monitors", 
"Overall Monitors", "Overall Monitors", "Overall Monitors", "Overall Monitors", 
"Overall Monitors", "Overall Monitors", "Overall Monitors", "Overall Monitors", 
"A", "A", "A", "A", "A", "A", "A", "A", "Overall TVs", "Overall TVs", 
"Overall TVs", "Overall TVs", "Overall TVs", "Overall TVs", "Overall TVs", 
"Overall TVs", "Overall TVs", "Overall TVs", "Overall TVs", "Overall TVs", 
"Overall TVs", "Overall TVs", "Overall TVs", "Overall TVs", "B", 
"B", "B", "B", "B", "B", "B", "B", "B", "C", "C", "C", "C", "C", 
"C", "C", "C", "C", "D", "D", "D", "D", "D", "D", "D", "D", "D", 
"D", "E", "E", "E", "E", "E", "E", "E", "E", "E", "E", "E", "F", 
"F", "F", "F", "G", "G", "G", "H", "H", "H", "H", "H", "I", "I", 
"I", "I", "I", "J", "J", "J", "J", "K", "K", "K", "K", "K", "L", 
"L", "L", "L", "L", "L", "M", "M", "M", "M", "M", "M", "Overall Miscellaneous", 
"Overall Miscellaneous", "Overall Miscellaneous", "Overall Miscellaneous", 
"Overall Miscellaneous", "Overall Miscellaneous", "Overall Miscellaneous", 
"Overall Miscellaneous", "Overall Miscellaneous", "Overall Miscellaneous", 
"Overall Miscellaneous", "Overall Miscellaneous", "Overall Miscellaneous", 
"Overall Miscellaneous", "Overall Miscellaneous", "N", "N", "N", 
"N", "N", "N", "N", "O", "O", "O", "O", "O", "O", "P", "P", "P", 
"P", "P", "P", "P", "P", "C", "C", "C", "C", "C", "C", "Q", "Q", 
"Q", "R", "R", "R", "S"), Supplier_text = c("94757 |   17.9% - Overall Smartphones", 
"94757 |   17.9% - Overall Smartphones", "94757 |   17.9% - Overall Smartphones", 
"94757 |   17.9% - Overall Smartphones", "94757 |   17.9% - Overall Smartphones", 
"94757 |   17.9% - Overall Smartphones", "94757 |   17.9% - Overall Smartphones", 
"94757 |   17.9% - Overall Smartphones", "94757 |   17.9% - Overall Smartphones", 
"94757 |   17.9% - Overall Smartphones", "94757 |   17.9% - Overall Smartphones", 
"94757 |   17.9% - Overall Smartphones", "94757 |   17.9% - Overall Smartphones", 
"94757 |   17.9% - Overall Smartphones", "94757 |   17.9% - Overall Smartphones", 
"94757 |   17.9% - Overall Smartphones", "-33239 |  -37.8% - Overall Monitors", 
"-33239 |  -37.8% - Overall Monitors", "-33239 |  -37.8% - Overall Monitors", 
"-33239 |  -37.8% - Overall Monitors", "-33239 |  -37.8% - Overall Monitors", 
"-33239 |  -37.8% - Overall Monitors", "-33239 |  -37.8% - Overall Monitors", 
"-33239 |  -37.8% - Overall Monitors", "-33239 |  -37.8% - Overall Monitors", 
"-33239 |  -37.8% - Overall Monitors", "-33239 |  -37.8% - Overall Monitors", 
"-33239 |  -37.8% - Overall Monitors", "-33239 |  -37.8% - Overall Monitors", 
"-33239 |  -37.8% - Overall Monitors", "-33239 |  -37.8% - Overall Monitors", 
"-33239 |  -37.8% - Overall Monitors", "-17385 |  -88.0% - A", 
"-17385 |  -88.0% - A", "-17385 |  -88.0% - A", "-17385 |  -88.0% - A", 
"-17385 |  -88.0% - A", "-17385 |  -88.0% - A", "-17385 |  -88.0% - A", 
"-17385 |  -88.0% - A", "14373 |    6.0% - Overall TVs", "14373 |    6.0% - Overall TVs", 
"14373 |    6.0% - Overall TVs", "14373 |    6.0% - Overall TVs", 
"14373 |    6.0% - Overall TVs", "14373 |    6.0% - Overall TVs", 
"14373 |    6.0% - Overall TVs", "14373 |    6.0% - Overall TVs", 
"14373 |    6.0% - Overall TVs", "14373 |    6.0% - Overall TVs", 
"14373 |    6.0% - Overall TVs", "14373 |    6.0% - Overall TVs", 
"14373 |    6.0% - Overall TVs", "14373 |    6.0% - Overall TVs", 
"14373 |    6.0% - Overall TVs", "14373 |    6.0% - Overall TVs", 
"-8387 |  -80.6% - B", "-8387 |  -80.6% - B", "-8387 |  -80.6% - B", 
"-8387 |  -80.6% - B", "-8387 |  -80.6% - B", "-8387 |  -80.6% - B", 
"-8387 |  -80.6% - B", "-8387 |  -80.6% - B", "-8387 |  -80.6% - B", 
"5701 |   79.2% - C", "5701 |   79.2% - C", "5701 |   79.2% - C", 
"5701 |   79.2% - C", "5701 |   79.2% - C", "5701 |   79.2% - C", 
"5701 |   79.2% - C", "5701 |   79.2% - C", "5701 |   79.2% - C", 
"5155 |   49.2% - D", "5155 |   49.2% - D", "5155 |   49.2% - D", 
"5155 |   49.2% - D", "5155 |   49.2% - D", "5155 |   49.2% - D", 
"5155 |   49.2% - D", "5155 |   49.2% - D", "5155 |   49.2% - D", 
"5155 |   49.2% - D", "4977 |   95.4% - E", "4977 |   95.4% - E", 
"4977 |   95.4% - E", "4977 |   95.4% - E", "4977 |   95.4% - E", 
"4977 |   95.4% - E", "4977 |   95.4% - E", "4977 |   95.4% - E", 
"4977 |   95.4% - E", "4977 |   95.4% - E", "4977 |   95.4% - E", 
"3676 |18380.0% - F", "3676 |18380.0% - F", "3676 |18380.0% - F", 
"3676 |18380.0% - F", "-3132 |  -99.4% - G", "-3132 |  -99.4% - G", 
"-3132 |  -99.4% - G", "3065 |   33.6% - H", "3065 |   33.6% - H", 
"3065 |   33.6% - H", "3065 |   33.6% - H", "3065 |   33.6% - H", 
"-2854 |  -56.1% - I", "-2854 |  -56.1% - I", "-2854 |  -56.1% - I", 
"-2854 |  -56.1% - I", "-2854 |  -56.1% - I", "2603 |    5.8% - J", 
"2603 |    5.8% - J", "2603 |    5.8% - J", "2603 |    5.8% - J", 
"2564 |   39.4% - K", "2564 |   39.4% - K", "2564 |   39.4% - K", 
"2564 |   39.4% - K", "2564 |   39.4% - K", "1843 |  334.5% - L", 
"1843 |  334.5% - L", "1843 |  334.5% - L", "1843 |  334.5% - L", 
"1843 |  334.5% - L", "1843 |  334.5% - L", "-1711 |  -19.4% - M", 
"-1711 |  -19.4% - M", "-1711 |  -19.4% - M", "-1711 |  -19.4% - M", 
"-1711 |  -19.4% - M", "-1711 |  -19.4% - M", "-1662 |  -30.0% - Overall Miscellaneous", 
"-1662 |  -30.0% - Overall Miscellaneous", "-1662 |  -30.0% - Overall Miscellaneous", 
"-1662 |  -30.0% - Overall Miscellaneous", "-1662 |  -30.0% - Overall Miscellaneous", 
"-1662 |  -30.0% - Overall Miscellaneous", "-1662 |  -30.0% - Overall Miscellaneous", 
"-1662 |  -30.0% - Overall Miscellaneous", "-1662 |  -30.0% - Overall Miscellaneous", 
"-1662 |  -30.0% - Overall Miscellaneous", "-1662 |  -30.0% - Overall Miscellaneous", 
"-1662 |  -30.0% - Overall Miscellaneous", "-1662 |  -30.0% - Overall Miscellaneous", 
"-1662 |  -30.0% - Overall Miscellaneous", "-1662 |  -30.0% - Overall Miscellaneous", 
"-1439 |  -95.6% - N", "-1439 |  -95.6% - N", "-1439 |  -95.6% - N", 
"-1439 |  -95.6% - N", "-1439 |  -95.6% - N", "-1439 |  -95.6% - N", 
"-1439 |  -95.6% - N", "1315 |   23.6% - O", "1315 |   23.6% - O", 
"1315 |   23.6% - O", "1315 |   23.6% - O", "1315 |   23.6% - O", 
"1315 |   23.6% - O", "193 |  232.5% - P", "193 |  232.5% - P", 
"193 |  232.5% - P", "193 |  232.5% - P", "193 |  232.5% - P", 
"193 |  232.5% - P", "193 |  232.5% - P", "193 |  232.5% - P", 
"-152 |  -38.1% - C", "-152 |  -38.1% - C", "-152 |  -38.1% - C", 
"-152 |  -38.1% - C", "-152 |  -38.1% - C", "-152 |  -38.1% - C", 
"-98 |  -79.7% - Q", "-98 |  -79.7% - Q", "-98 |  -79.7% - Q", 
"92 | 3066.7% - R", "92 | 3066.7% - R", "92 | 3066.7% - R", "-70 |  -90.9% - S"
), order = c(1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 
1L, 1L, 1L, 1L, 1L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 
2L, 2L, 2L, 2L, 2L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 4L, 4L, 4L, 
4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 5L, 5L, 5L, 
5L, 5L, 5L, 5L, 5L, 5L, 6L, 6L, 6L, 6L, 6L, 6L, 6L, 6L, 6L, 7L, 
7L, 7L, 7L, 7L, 7L, 7L, 7L, 7L, 7L, 8L, 8L, 8L, 8L, 8L, 8L, 8L, 
8L, 8L, 8L, 8L, 9L, 9L, 9L, 9L, 10L, 10L, 10L, 11L, 11L, 11L, 
11L, 11L, 12L, 12L, 12L, 12L, 12L, 13L, 13L, 13L, 13L, 14L, 14L, 
14L, 14L, 14L, 15L, 15L, 15L, 15L, 15L, 15L, 16L, 16L, 16L, 16L, 
16L, 16L, 17L, 17L, 17L, 17L, 17L, 17L, 17L, 17L, 17L, 17L, 17L, 
17L, 17L, 17L, 17L, 18L, 18L, 18L, 18L, 18L, 18L, 18L, 19L, 19L, 
19L, 19L, 19L, 19L, 20L, 20L, 20L, 20L, 20L, 20L, 20L, 20L, 21L, 
21L, 21L, 21L, 21L, 21L, 22L, 22L, 22L, 23L, 23L, 23L, 24L), 
    Chip_type = c("Micro", "Nano", "BiMech", "Titan", "Quantum", 
    "Platinum", "PlainChip", "Classic", "Micro", "Nano", "BiMech", 
    "Titan", "Quantum", "Platinum", "PlainChip", "Classic", "Micro", 
    "Nano", "BiMech", "Titan", "Quantum", "Platinum", "PlainChip", 
    "Classic", "Micro", "Nano", "BiMech", "Titan", "Quantum", 
    "Platinum", "PlainChip", "Classic", "Titan", "Platinum", 
    "Quantum", "Nano", "Titan", "Platinum", "Nano", "PlainChip", 
    "Micro", "Nano", "BiMech", "Titan", "Quantum", "Platinum", 
    "PlainChip", "Classic", "Micro", "Nano", "BiMech", "Titan", 
    "Quantum", "Platinum", "PlainChip", "Classic", "Titan", "Platinum", 
    "Quantum", "Nano", "Titan", "Nano", "Quantum", "Platinum", 
    "PlainChip", "PlainChip", "Platinum", "Nano", "Quantum", 
    "Classic", "PlainChip", "Platinum", "Nano", "Quantum", "PlainChip", 
    "Platinum", "Quantum", "Nano", "Classic", "PlainChip", "Nano", 
    "Platinum", "Quantum", "Classic", "PlainChip", "Quantum", 
    "Platinum", "Classic", "Nano", "PlainChip", "Quantum", "Platinum", 
    "Nano", "Classic", "BiMech", "Titan", "Nano", "Titan", "Quantum", 
    "Titan", "Titan", "Platinum", "PlainChip", "Nano", "Classic", 
    "PlainChip", "Nano", "PlainChip", "PlainChip", "Quantum", 
    "Nano", "Classic", "Titan", "Nano", "Titan", "Nano", "Platinum", 
    "PlainChip", "Quantum", "Platinum", "PlainChip", "Titan", 
    "PlainChip", "Platinum", "Titan", "PlainChip", "Platinum", 
    "Titan", "Nano", "Titan", "Platinum", "Nano", "PlainChip", 
    "Nano", "BiMech", "Titan", "Quantum", "Platinum", "PlainChip", 
    "Classic", "Micro", "Nano", "BiMech", "Titan", "Quantum", 
    "Platinum", "PlainChip", "Classic", "Titan", "Quantum", "Titan", 
    "Nano", "Quantum", "Platinum", "PlainChip", "Titan", "Quantum", 
    "Nano", "Micro", "Titan", "Nano", "Platinum", "Quantum", 
    "Nano", "Classic", "Quantum", "Platinum", "Nano", "Classic", 
    "Classic", "Quantum", "Nano", "Classic", "Quantum", "Nano", 
    "Quantum", "Quantum", "Nano", "Quantum", "Nano", "Quantum", 
    "Titan"), Product = c("Smartphones", "Smartphones", "Smartphones", 
    "Smartphones", "Smartphones", "Smartphones", "Smartphones", 
    "Smartphones", "Smartphones", "Smartphones", "Smartphones", 
    "Smartphones", "Smartphones", "Smartphones", "Smartphones", 
    "Smartphones", "Monitors", "Monitors", "Monitors", "Monitors", 
    "Monitors", "Monitors", "Monitors", "Monitors", "Monitors", 
    "Monitors", "Monitors", "Monitors", "Monitors", "Monitors", 
    "Monitors", "Monitors", "Smartphones", "Smartphones", "Smartphones", 
    "Smartphones", "Smartphones", "Smartphones", "Smartphones", 
    "Smartphones", "TVs", "TVs", "TVs", "TVs", "TVs", "TVs", 
    "TVs", "TVs", "TVs", "TVs", "TVs", "TVs", "TVs", "TVs", "TVs", 
    "TVs", "Monitors", "Monitors", "Monitors", "Monitors", "Monitors", 
    "Monitors", "Monitors", "Monitors", "Monitors", "Smartphones", 
    "Smartphones", "Smartphones", "Smartphones", "Smartphones", 
    "Smartphones", "Smartphones", "Smartphones", "Smartphones", 
    "Smartphones", "Smartphones", "Smartphones", "Smartphones", 
    "Smartphones", "Smartphones", "Smartphones", "Smartphones", 
    "Smartphones", "Smartphones", "Smartphones", "Smartphones", 
    "Smartphones", "Smartphones", "Smartphones", "Smartphones", 
    "Smartphones", "Smartphones", "Smartphones", "Smartphones", 
    "Smartphones", "Monitors", "Monitors", "Monitors", "Monitors", 
    "Monitors", "Monitors", "Monitors", "Monitors", "Monitors", 
    "Monitors", "Monitors", "Monitors", "Monitors", "Monitors", 
    "Monitors", "Monitors", "Monitors", "TVs", "TVs", "TVs", 
    "TVs", "Smartphones", "Smartphones", "Smartphones", "Smartphones", 
    "Smartphones", "TVs", "TVs", "TVs", "TVs", "TVs", "TVs", 
    "TVs", "TVs", "TVs", "TVs", "TVs", "TVs", "Miscellaneous", 
    "Miscellaneous", "Miscellaneous", "Miscellaneous", "Miscellaneous", 
    "Miscellaneous", "Miscellaneous", "Miscellaneous", "Miscellaneous", 
    "Miscellaneous", "Miscellaneous", "Miscellaneous", "Miscellaneous", 
    "Miscellaneous", "Miscellaneous", "Monitors", "Monitors", 
    "Monitors", "Monitors", "Monitors", "Monitors", "Monitors", 
    "TVs", "TVs", "TVs", "TVs", "TVs", "TVs", "Miscellaneous", 
    "Miscellaneous", "Miscellaneous", "Miscellaneous", "Miscellaneous", 
    "Miscellaneous", "Miscellaneous", "Miscellaneous", "Miscellaneous", 
    "Miscellaneous", "Miscellaneous", "Miscellaneous", "Miscellaneous", 
    "Miscellaneous", "Miscellaneous", "Miscellaneous", "Miscellaneous", 
    "Miscellaneous", "Miscellaneous", "Miscellaneous", "Miscellaneous"
    ), n = c("106", "16920", "61216", "363698", "50797", "52715", 
    "174342", "9319", "92", "16928", "40920", "270963", "48605", 
    "34068", "114333", "4024", "3", "1536", "6793", "45146", 
    "7922", "5359", "27390", "1131", "12", "1311", "5431", "48107", 
    "6230", "5133", "21161", "505", "3619", "13", "2", "1", "19720", 
    "13", "10", "4", "96", "4643", "14534", "166664", "17178", 
    "17489", "30048", "5010", "96", "6904", "10463", "158060", 
    "15864", "20149", "24173", "2390", "12102", "7", "2", "1", 
    "10390", "5", "4", "2", "1", "11036", "329", "224", "2", 
    "2", "6936", "176", "85", "1", "15335", "55", "53", "48", 
    "14", "10292", "86", "47", "32", "11", "6559", "667", "631", 
    "419", "416", "4416", "336", "285", "105", "74", "2", "18", 
    "2", "18", "2", "86", "3151", "1", "14682", "77", "10", "9098", 
    "26", "2833", "5083", "2", "1", "1", "41051", "3", "45233", 
    "3", "10763", "44", "2", "6508", "2", "370", "265", "6", 
    "461", "86", "4", "5996", "2", "8826", "2", "1", "1", "503", 
    "5", "79", "3348", "742", "199", "989", "1", "473", "11", 
    "152", "3681", "363", "54", "804", "1702", "1", "1500", "2", 
    "1", "1", "1", "4868", "5", "2", "1", "5573", "1", "312", 
    "113", "3", "3", "42", "30", "6", "5", "371", "53", "19", 
    "312", "64", "23", "97", "121", "2", "3", "1", "3", "77")), row.names = c(NA, 
-182L), class = c("tbl_df", "tbl", "data.frame"))

#[1]: https://i.stack.imgur.com/bDTWZ.png #[2]: https://i.stack.imgur.com/MaJ5r.png

Henrik
  • 23
  • 8

1 Answers1

0

You created a list of 182 T or F, but what you really needed is a list of 24 T or F because there are 24 lines, 24 groups, 24 traces that plotly is going to show or not.

First, you did a great job of grouping the plot by a field in your data. I have another variation of the first plot- which calls plot_ly one time and still creates all 24 traces, like your loop.

First I made Supplier_text and ordered factor so that I could control the order of the legend.

# this will keep the legend order in order
d2 <- dat %>%
  mutate(Supplier_text = ordered(Supplier_text, 
                                 levels = rev(unique(dat$Supplier_text))))

Then I created the 24 traces.

(plt <- plot_ly(data = d2,
                x = ~Date,
                y = ~n,
                color = ~Supplier_text,
                type = "scatter",
                mode = "lines"))

Then I changed the filter you created for products. You have 24 traces and now you have 24 T or F for this filter.

# changed this so it is one for each trace, not one for each row
Parts_product_filter <- d2 %>% select(Supplier_text, Product) %>%  
  mutate(Smartphones = ifelse(Product == "Smartphones",T,F) %>% sapply(.,list), 
         TVs = ifelse(Product == "TVs",T, F) %>% sapply(.,list),
         Monitors = ifelse(Product == "Monitors",T,F) %>% sapply(.,list),
         Miscellaneous = ifelse(Product == "Miscellaneous",T,F) %>% sapply(.,list)) %>% 
  unique()

The other parts of your code remained the same, with the exception of object name of plotly_object, now plt in any calls connecting the plot (the two layouts and Chip_type filter).


Update REPLACEMENT

This only includes the "both" button.

I was answering your questions in comments and realized that something wasn't right with some traces. So when I fixed that issue, I also added hovering, so you could visualize why you get vertical lines. Remember, n is a factor. Plotly doesn't know which left n value you want to connect with which value on the right, other than by color.

When you make n a numeric field, plotly will add the values together (unless you provide plotly some other way of dividing the content up). Sadly computers can't read minds... yet...

I added a hovertemplate. If you see something in the legend that's not on the plot, there's something plotly didn't know what to do with. If you hover on the plot, you might even get a value, but no line. I have some examples at the end.

d2 <- dat %>%
  mutate(Supplier_text = ordered(Supplier_text, 
                                 levels = rev(unique(dat$Supplier_text))),
         Product = ordered(Product,
                           levels = sort(unique(dat$Product))),
         Chip_type = ordered(Chip_type, 
                             levels = sort(unique(dat$Chip_type))),
         n = as.numeric(dat$n) %>% sort(decreasing = T) %>%
           as.character() %>% ordered(., levels = unique(.))) %>% 
  arrange(n)

The all trace:

#------------- base plot ----------------
(plt <- d2 %>% 
    plot_ly(x = ~Date,
            y = ~n,
            color = ~Supplier_text,
            type = 'scatter',
            mode = 'lines',
            text = ~Product,
            hovertext = ~Chip_type,
            visible = T
))

I realized that while I was counting traces, when the plot rendered, I didn't get 45 traces, I was getting a whole lot more! With only the base plot there are 24 traces. With both base and the combination button, there are 127 traces.

This is how I figured it out and validated the correction.

#------------- trace count ----------------
# I used length(plt$x$attrs) to confirm the number of traces
       # -- that was a mistake!
# collect data, since it's not in the plotly object (errr)
pj = plotly_json(plt)

# read the JSON back
pjj = jsonlite::fromJSON(pj$x$data)

# number of traces:
nrow(pjj$data)
# [1] 24  # one trace for each color

The combination traces:

#------------- add combination traces ----------------
# each of the possible button groups when both filters are opted
cmb = expand.grid(Product = levels(d2$Product), 
                  Chip_type = levels(d2$Chip_type))    
# create combo traces
invisible(
  lapply(1:nrow(cmb),  # filter for both
         function(x){
           d3 = d2 %>% filter(Product == cmb[x, 1] %>% toString(),
                              Chip_type == cmb[x, 2] %>% toString()) %>% 
             droplevels
           if(nrow(d3) < 1) {
             print(cmb[x, ]) # let me know what was skipped
             return()        # if no rows, don't make the trace
           } # end if 
           plt <<- plt %>% 
             add_trace(inherit = F,
                       data = d2 %>%
                         filter(Product == cmb[x, 1] %>% toString(),
                                Chip_type == cmb[x, 2] %>% toString()),
                       x = ~Date, y = ~n,
                       color = ~Supplier_text,
                       type = 'scatter',
                       mode = 'lines',
                       text = ~Product,
                       hovertext = ~Chip_type,
                       hovertemplate = paste0("Products: %{text}",
                                              "\nChips: %{hovertext}"),
                       visible = F #,
                       #inherit = F
             )
         })
)
cmb # validate

Check the number of traces now:

#------------- combination traces updated trace count ----------------
# collect count
pj = plotly_json(plt)

# read the JSON back
pjj = jsonlite::fromJSON(pj$x$data)

# number of traces:
nrow(pjj$data)
# [1] 127  # whoa!

Create a data from the traces to make sure that the T/F is right

#------------- trace data frame ----------------
# create data frame of the JSON content so that traces can be match with combos
plt.df = data.frame(nm = pjj$data$name, # this is Supplier_text
                    # valCount is the number of observations in the trace
                    valCount = unlist(map(pjj$data$x, ~length(.x))), 
                    # whether it's visible (is it all or not?)
                    vis = pjj$data$visible)
# inspect what you expect
tail(plt.df)

Combination part of button

#------------- set up for button for combos ----------------
tracs = d2 %>% 
  group_by(Product, Chip_type, Supplier_text) %>%
  summarise(ct = n(), .groups = "drop") %>% 
  mutate(traces = 25:127)

# is the order the same in the plot?
tail(tracs, 10)

tail(plt.df, 10) # definitely not!

# check?
tracs %>% arrange(Chip_type) %>% tail(10)
     # that's the right order

# update tracs' order
tracs <- tracs %>% arrange(Chip_type) %>% 
  mutate(traces = 25:nrow(plt.df)) # fix trace assignment

# double-check!
plt.df[25:35,]
tracs[1:11,]
# they aren't the same, but plotting groups are

# adjust cmb to be ordered before id trace to group combos
cmb <- cmb %>% arrange(Chip_type)

Now that the data is aligned (order-wise), we need to find exactly what traces go with which groups. There will be a trace for each color in the group (i.e., if Misc Titan has 5 in/ 5 out in 3 different colors, there will be three traces for Misc Titan.

#--------------- collect group to trace number ----------------
# between cmb, d2, and the traces, the three vars - product, chip, and 
# supplier text are ordered factors so the order will be the same
cmbo = invisible(
  lapply(1:nrow(cmb),
         function(x){
           rs = tracs %>% filter(Product == cmb[x, 1] %>% toString(),
                                 Chip_type == cmb[x, 2] %>% toString()) %>% 
             select(traces) %>% unlist() %>% unique(use.names = F)
           list(traces = rs)
           }) %>% setNames(paste0(cmb[, 1], " ", cmb[, 2])) # add the names
)# 32 start and stop points for the 103 traces

# check
cmbo[1:6]

Now the button layout code can be written:

#---------------------- the button ----------------------
# now for the buttons...finally
# create the empty 
raw_v <- rep(F, nrow(plt.df))

cButton <- 
  lapply(1:length(cmbo),
         function(x){
           traces <- cmbo[[x]][[1]] %>% unlist()
           raw_v[traces] <- T
           as.list(unlist(raw_v))
         }) %>% setNames(names(cmbo))
# validate
length(cButton[[1]])
# [1] 127 
length(cButton)
# [1] 32 

# looks good
cmbBtn2 = lapply(1:length(cButton),
                 function(x){
                   label = names(cButton)[x] %>% gsub("\\.", " ", x = .)
                   method = "restyle"
                   args = list("visible", cButton[[x]])
                   list(label = label, method = method, args = args)
                 })

The all part of the button

#------------- set up button for "all" ----------------
all = list(list(label = "All",
                method = "restyle",
                args = list("visible",
                            as.list(unlist(
                              c(rep(T, 24), 
                                rep(F, nrow(plt.df) - 24)
                                )))) # end args
                )) # end list list

Now put it all together:

#---------------------- the layout ----------------------
Parts_legend <- list(
  font = list(
    family = "sans-serif",
    size = 12,
    color = "#000"),
  title = list(text="<b> Delta previous month by Supplier - Absolute </b>"),
  bgcolor = "#E2E2E2",
  bordercolor = "#FFFFFF",
  borderwidth = 2,
  layout.legend = "constant",
  traceorder = "grouped")


plt %>% 
  layout(legend = Parts_legend,
         title = "by supplier delta previous month",
         xaxis = list(title = 'Date'),
         yaxis = list(title = 'Chip Volume'),
         margin = list(l = 120, t = 100),
         updatemenus = list(
           list(
             active = 0,
             type = "dropdown",
             y = 1.2,
             direction = "down",
             buttons = append(all, cmbBtn2)))
  ) # end layout

Some checks:

# check some of these for accuracy
d2 %>% filter(Product == "TVs", Chip_type == "PlainChip") # correct
d2 %>% filter(Product == "Miscellaneous", Chip_type == "BiMech") # correct
d2 %>% filter(Product == "Monitors", Chip_type == "Classic") # NOT right! 
                    # there are 2 in and 2 out, but 1 in and 1 out match, 
                    # the other's are different colors, so the line's not drawn
d2 %>% filter(Product == "TVs", Chip_type == "Micro") # not correct; 
                    # that's because there is more in than out

enter image description here

enter image description here

enter image description here

enter image description here

The last thing, and I'll finally stop! The vertical lines--I zoomed in on the 'All'. Here are multiple views of the same plot, same zoom, same quasi-horizontal line, same vertical line:

enter image description here enter image description here

enter image description here enter image description here

All plotly has for, let's say—rules, is x, y, and color. It doesn't care about the other data, you didn't bind it that way (well, I didn't).

Kat
  • 15,669
  • 3
  • 18
  • 51
  • Hi @Kat, thank you so much for helping me with my problem! I'm sorry I didn't have time to continue lately, but here's my thoughts: What you built works as long as you keep one filter to "All". In that case, I have as many traces in the plot as I can toggle by the filters. My problem arose when I tried to toggle using both filters. Plot_ly seems to draw 24 traces. But I think it would need to be more, to match every combination there is of supplier x product x chip type in the data. This way, the filters could possibly control the traces, one by one. – Henrik Apr 07 '22 at 12:32
  • But like this, each trace is for one supplier, while each supplier is represented with a different n for each product/chip type. I'd be happy if you have any more ideas; you have anyways helped with the ordered factors, I had not thought of that but used usual factors instead :) – Henrik Apr 07 '22 at 18:30
  • I should have left a comment @Henrik. I edited my answer yesterday. – Kat Apr 09 '22 at 18:31
  • Thanks so much! I can't believe how much work you've put into solving my problem! I understand your approach and code and will try it in a bit. Bu first, I have some points for discussion and two questions for you. I think you've provided great code, trying hard to control for `plotly`'s strange behavior messing with the traces. Also, I think using two interconnected filters is a very common requirement nowadays so I'm in a bit of disbelief this kind of workaround is even necessary :( – Henrik Apr 11 '22 at 14:54
  • Question 1: n as sorted ordered factor: I like how you have taken one way from `plotly` to mess with the traces, but will this limit the way the y-scale is represented in general? I had still hoped to convert that into a regular numeric y-scale at some point (which does not list every single number with a single tick, but rather a continuous scale bith certain ticks at 5.000, 10.000, etc... – Henrik Apr 11 '22 at 14:59
  • Question 2: "Then I created the traces. Usually, plotly partitions the data by color, but that didn't happen here (I'm assuming it is because of the visibility declaration.)" Do you refer here to your "invisible(" calls that are intended to hide all traces except the "all" trace upon building the plot? I would say, the plot would be fine showing all traces in the beginning; as long as the filters will work. Would that make things easier at trace creation? – Henrik Apr 11 '22 at 15:01
  • Question 3: Filter buttons: What is the reason you have created 3 filter buttons now? As far as I understand, the third button contains all combinations of product x chip_type so what are the other two for? – Henrik Apr 11 '22 at 15:03
  • Question 4: I do not understand the last part about the "from" "to". I do see the vertical bars and I do not understand why they appear. You have created one trace per combination, each of which contains exactly two dates to draw the line. How can `plotly` not know where to draw the trace? Thank you so much, Kat! (Somehow turned into 4 instead of 2 questions) – Henrik Apr 11 '22 at 15:05
  • I started to answer your questions and wanted to give you some examples. I found a few things that looked wrong...sigh... revision 1039528036583209 is now in my answer ('Update REPLACEMENT' and everything below it). 1) in my update, I mention what happens when you make `n` a numeric- Plotly will add the values for like colors. 2) Turns out I was wrong about the number of traces...that should be answered in my update...too. 3) I created 3 because I didn't know if you might still want the others. I didn't keep it in my revision. 4) That's shown with visuals at the end of the update! (whew!) – Kat Apr 12 '22 at 03:40
  • @ismirsehregal just pointed out that `crosstalk` is an option for multiple selection, too. You can see a Q and A example [here](https://stackoverflow.com/questions/71494308/r-plotly-separate-functional-legends/71657272#71657272). – Kat Apr 12 '22 at 12:56
  • Thanks a lot, Kat. What do you mean by "in / out" in "There will be a trace for each color in the group (i.e., if Misc Titan has 5 in/ 5 out in 3 different colors, there will be three traces for Misc Titan." ? I understand there is different suppliers and there should be one trace per color (= supplier). Finally, as you show in your accuracy checks, it can be summarized that my intended plot is not going to work, right? Also, I believe the factored `n` is going to prevent a "proper" plot. Thank you also for pointing me to the `crosstalk` option! I will look into that next to see if ... – Henrik Apr 14 '22 at 13:31
  • ... that approach of filtering the dataset could get me past the problems that `plotly` is posing here (which I still can't believe considering it's among the most prominent libraries for interactive plots in R and my use case isn't exactly rocket science....). Unfortunately, I have too little reputation to upvote your answer :/ And I would like to keep this thread open for replies, in case it gets in handy. Can I do anything else to acknowledge your efforts, despite say a deeply felt "THANK YOU"? :) – Henrik Apr 14 '22 at 13:34
  • The hint to `crosstalk`absolutely saved me! Where did @IsMirSehrEgal point that out, I don't see any comment? – Henrik May 03 '22 at 14:09