ronclickplotlytoggle

How do I make two filters in r plotly toggle visibility of lines of a chart? How do I multi-select in the filters?


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.sstatic.net/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.sstatic.net/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.sstatic.net/bDTWZ.png #[2]: https://i.sstatic.net/MaJ5r.png


Solution

  • 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).