class: inverse, center, title-slide, middle # Outstanding User Interfaces with Shiny ## David Granjon ### 2021-11-04 ### R in Pharma --- # Hi there ๐ We're in for 20 minutes of **fun**! - Grab a โ - Make yourself comfortable ๐ or ๐ง .flex.tc[ .w-30.mr3.center[ <img class="br-100" width="100px" height="100px" src="assets/images/granjda1.jpg" class="svg-img"/> <br> .f5[David] .small[Senior Expert Data Scientist, Novartis] ] ] --- class: inverse center title-slide middle # Introduction --- class: header_background # Shiny <br/> .center[ <img src="assets/images/shiny.svg" width="20%" style="display: block; margin: auto;" class="svg-img"/> ] - `{Shiny}` is about __10__ years old ๐ฒ๐ฒ๐ฒ. - Today, many tools can make your apps __shining__. - Let's review some of them ... --- class: header_background # What I will share today <br> .pull-left[ ### How it started ... <img src="assets/images/virtual-patient-1.png" width="100%"/> ] -- .pull-right[ ### ... 1 years after <img src="assets/images/virtual-patient-3.png" width="100%"/> ] --- class: header_background # From the simplest app ... ๐ถ <br> .center[ <iframe src="http://localhost:3511?showcase=0" width="720" height="400px" data-external="1"></iframe> ] --- class: header_background # Is this a Shiny app โ <br> .center[ <iframe width="50%" src="http://localhost:3512" allowfullscreen="" frameborder="0" scrolling="yes" height="500px" style="margin-top: -50px"></iframe> ] --- class: header_background # What's the difference โ <br> - __111__ lines of CSS. -- - 29 lines of JavaScript code. -- - 2 png images (dj gear + rotating wheel). -- - 36 lines of R code (`{wavesurfer}` htmlWidget). -- - Few custom HTML tags. --- class: header_background # What you'll learn today <br> .panelset[ .panel[.panel-name[๐จ Part 1] __Tweak__ apps style with CSS and Sass - CSS basics. - How Sass may help you: `{sass}`. - High level CSS and Sass tools: `{bslib}` and `{fresh}`. ] .panel[.panel-name[๐ Part 2] Unleash app __interactivity__ with JavaScript (JS) - Discover how Shiny deals with inputs. - Add React and any JS framework with `{packer}`. - Optimize your app with custom handlers. ] .panel[.panel-name[๐งโโ๏ธ Part 3] Create a new template from scratch - Discover `{charpente}`. ] .panel[.panel-name[๐ Part 4] Sorry I don't have time ... - Discover `{truelle}`. - Use preexisting templates. ] ] --- class: center middle <img src="assets/images/excited-minions.gif" width="40%" style="display: block; margin: auto;" /> --- class: inverse center title-slide middle # ๐จ Tweak with CSS and Sass ๐จ --- class: header_background # What is CSS? <br> CSS stands for Cascading Style Sheets, .small.pull-left[ ### Apply CSS .pull-left[ - Mechanism: ``` selector { property: value; } ``` ] .pull-right[ - With Shiny: ```r tags$style( "p { color: red; } " ) ``` ] ] .pull-right[ <iframe width="100%" src="http://localhost:3513" allowfullscreen="" frameborder="0" scrolling="yes" height="400px"></iframe> ] --- class: header_background # But CSS becomes messy ๐ <br> - Easy to __repeat__ yourself. - Difficult to read and maintain: ๐ __spaghetti__ ๐. - It's just a __mess__ ... <img src="assets/images/alors_heureux.gif" width="40%" style="display: block; margin: auto;" /> --- class: header_background # Tidy your CSS with Sass ๐งน <br> - Sass = Syntactically Awesome Style Sheets. - __Programmable__: variables, functions, loops, operators, modules, ... - Modern web development workflow. - __DRY__ programming. - Will __motivate__ you to learn more CSS. -- .pull-left[ An R interface exists: `{sass}`. ```r install.packages("sass") ``` ] .pull-right[ <img src="assets/images/sass.svg" width="40%" style="display: block; margin: auto;" class="svg-img"/> ] --- class: header_background # {sass} in action <br> .panelset[ .panel[.panel-name[Variables] .small.pull-left[ ```r library(sass) rule1 <- ".class-1{ color: $color; }" rule2 <- ".class-2{ background-color: $color; }" sass(input = list(color = "purple", rule1, rule2)) ``` ] .pull-right[ ``` #> /* CSS */ #> .class-1 { #> color: purple; #> } #> #> .class-2 { #> background-color: purple; #> } ``` ] ] .panel[.panel-name[Functions] .small.pull-left[ ```r sass( list( a = 2, b = 4, "@function multiply($parm1, $parm2) { @debug 'parm1 is #{$parm1}'; @debug 'parm2 is #{$parm2}'; @return $parm1 * $parm2; }", ".my-class { width: multiply($a, $b) * 1px; }" ) ) ``` ] .pull-right[ ``` stdin:4 DEBUG: parm1 is 2 stdin:5 DEBUG: parm2 is 4 #> /* CSS */ #> .my-class { #> width: 8px; #> } ``` ] ] .panel[.panel-name[โฟLoops] .pull-left[ ```r sass(input = list( colors = c("green", "red"), "@each $color in $colors { .alert-#{$color} { color: $color; } }" )) ``` ] .pull-right[ ``` #> .alert-green { #> color: green; #> } #> #> .alert-red { #> color: red; #> } ``` ] ] .panel[.panel-name[With Shiny] .xsmall.pull-left[ ```r # CSS to include in your app css <- sass( sass_layer( defaults = list( turquoise = "#03a4ff", cyan = "#e705be", green = "#f3d6e9", yellow = "#fdaf2c", red = "#ff483e", "scheme-main" = "hsl(0, 0%, 10%)" ), rules = sass_file( input = system.file( "sass/bulma/bulma.sass", package = "OSUICode" ) ) ) ) ``` ] .pull-right[ <iframe width="100%" src="http://localhost:3514" allowfullscreen="" frameborder="0" scrolling="yes" height="300px"></iframe> ] ] ] <br> --- class: header_background # Isn't there something easier? <br> -- .pull-left[ ## {fresh} - Built on top of `{sass}`. - For `{shiny}`, `{shinydashboard}`, ... ```r install.packages("fresh") ``` ] -- .pull-right[ ## {bslib} - Built on top of `{sass}`. - For `{shiny}`. - Live theming widget. - Dynamic theming. - Conditional rendering. - ... ```r install.packages("bslib") ``` ] --- class: header_background # A dark theme with {fresh} <br> .xsmall.pull-left[ ```r library(fresh) dark_theme <- create_theme( bs4dash_vars( navbar_light_color = "#bec5cb", navbar_light_active_color = "#FFF", navbar_light_hover_color = "#FFF" ), bs4dash_yiq( contrasted_threshold = 10, text_dark = "#FFF", text_light = "#272c30" ), bs4dash_layout(main_bg = "#353c42"), bs4dash_sidebar_dark( bg = "#272c30", color = "#bec5cb", hover_color = "#FFF", submenu_bg = "#272c30", submenu_color = "#FFF", submenu_hover_color = "#FFF" ), bs4dash_status(dark = "#272c30"), bs4dash_color(gray_900 = "#FFF", white = "#272c30") ) ``` ] .pull-right[ <img src="assets/images/bs4Dash-fresh.png" width="100%" style="display: block; margin: auto;" /> ] --- class: header_background # A neon theme with {bslib} <br> .xsmall.pull-left[ ```r library(bslib) bslib_neon_theme <- bs_theme( version = 4, bg = "#000000", fg = "#FFFFFF", primary = "#9600FF", secondary = "#1900A0", success = "#38FF12", info = "#00F5FB", warning = "#FFF100", danger = "#FF00E3", base_font = "Marker Felt", heading_font = "Marker Felt", code_font = "Chalkduster" ) bs_theme_preview(bslib_neon_theme, with_themer = FALSE) ``` ] .pull-right[ <iframe width="100%" src="http://localhost:3515" allowfullscreen="" frameborder="0" scrolling="yes" height="400px"></iframe> ] --- class: header_background # Dynamic theming example <br> Static plot (base R, ggplot2, ...) theming requires `{thematic}`. .center[ <iframe width="100%" src="http://localhost:3516" allowfullscreen="" frameborder="0" scrolling="yes" height="400px"></iframe> ] --- class: header_background # Dynamic theming with {bs4Dash} <br> .center[ <iframe width="100%" src="https://dgranjon.shinyapps.io/bs4DashDemo/" allowfullscreen="" frameborder="0" scrolling="yes" height="450px" style="font-size: 0.5em"></iframe> ] --- class: header_background # Conditional rendering <br> Only 1 code base, 2 possible outputs ๐๏ธ .pull-left[ ## BS4 accordion <iframe width="100%" src="http://localhost:3517" allowfullscreen="" frameborder="0" scrolling="yes" height="400px"></iframe> ] .pull-right[ ## BS5 accordion <iframe width="100%" src="http://localhost:3518" allowfullscreen="" frameborder="0" scrolling="yes" height="400px"></iframe> ] --- class: inverse center title-slide middle # ๐ Unleash interactivity with JS ๐ --- class: header_background # CSS does not solve all issues <br> What can JS do for me โ .pull-left[ - Add new __input widgets__. - __Lighten__ the R logic be delegating some tasks to JS. - More __interactive__ apps. - Overall, a better user experience. ] .pull-right[ <iframe width="100%" src="http://localhost:3523" allowfullscreen="" frameborder="0" scrolling="yes" height="400px"></iframe> ] --- class: header_background # Unleash app interactivity: example <br> .center[ <video height="400px" width="100%" controls> <source src="assets/movies/js-interactivity-demo.mov" type="video/mp4"> ] --- class: header_background # JS code can also become messy <br> -- - How do I include __JS__ in my Shiny project โ -- - What are the __best practices__ around JS code management โ -- - Let's try to bring some answers ... --- class: header_background # Relevant R/JS projects <br> .panelset[ .panel[.panel-name[๐ฃ Meet `{packer}`] .small.pull-left[ - A `{usethis}` for JavaScript/R projects. - Supports Shiny outputs, inputs, htmlwidgets, extensions, ... - Predefined templates: React, Vue, Framework7. ```r install.packages("packer") remotes::install_github("JohnCoene/packer") ``` ] .pull-right[ <a href="https://github.com/JohnCoene/packer"><img src="assets/images/packer.png" width="40%" class="svg-img"/></a> ] ] .panel[.panel-name[๐ฅ Scaffold project] .small.pull-left[ We call: ```r golem::create_golem(...) packer::scaffold_golem(framework7 = TRUE) packer::bundle() ``` - Sets all the necessary structure (npm, loaders, webpack, ...). - So that you don't have to worry too much. ] .pull-right[ <video autosize="true" controls> <source src="assets/movies/packer-demo.mov" type="video/mp4"> </video> ] ] ] --- class: header_background # Application to Framework7 <br> .small.pull-left[ - Framework7: first class mobile web framework. - R in Pharma 2021 RinteRface [workshop](https://rinpharma2021.rinterface.com/). - Advantages: - No need to develop a Shiny extension. - Faster app. - Ease collaboration with front end dev. ] .pull-right[ <div class="md-iphone-5 md-black-device" style="font-size: 0.5em; margin-top: -100px; !important;"> <div class="md-body"> <div class="md-buttons"></div> <div class="md-front-camera"></div> <div class="md-top-speaker"></div> <div class="md-screen"> <iframe width="100%" src="http://localhost:3519" allowfullscreen="" frameborder="0" scrolling="yes" height="470px"></iframe> </div> <button class="md-home-button"></button> </div> </div> ] --- class: header_background # Optimize: improve user experience <br> .panelset[ .panel[.panel-name[`renderUI` causes lags] .pull-left[ <iframe width="100%" src="http://localhost:3520" allowfullscreen="" frameborder="0" scrolling="yes" height="300px"></iframe> ] .small.pull-right[ ```r dummy_task <- reactive({ Sys.sleep(5) 12 }) output$custom_box <- renderUI({ * dummy_task() box( title = "Box", width = dummy_task(), "Box body", background = input$background ) }) ``` ] ] .panel[.panel-name[Prefer `update_*` functions] .pull-left[ <iframe width="100%" src="http://localhost:3521" allowfullscreen="" frameborder="0" scrolling="yes" height="300px"></iframe> ] .xsmall.pull-right[ ```r dummy_task <- reactive({ Sys.sleep(5) 12 }) observeEvent(dummy_task(), { * updateBox2( "mybox", action = "update", options = list( width = dummy_task(), title = tagList( shinydashboardPlus::dashboardBadge( "New", color = "red" ), "New title" ) ) ) }) ``` ] ] ] --- class: inverse center title-slide middle # ๐งโโ New template from scratch ๐งโโ --- class: header_background # Introducing {charpente} <br> .panelset[ .panel[.panel-name[๐ฃ Meet `{charpente}`] .xsmall.pull-left[ - Plug and play __package__ structure. - Import dependencies from [jsdelivr](https://www.jsdelivr.com/). - __Easy__ HTML to R conversion. - R and JS __boilerplates__: `{shiny}` input bindings, `{shiny}` message handlers, ... - Seamless JavaScript code management with [esbuild](https://esbuild.github.io/). ```r remotes::install_github("RinteRface/charpente") ``` ] .pull-right[ <a href="https://github.com/RinteRface/charpente"><img src="assets/images/charpente.png" width="40%" style="display: block; margin: auto;" class="svg-img"/></a> ] ] .panel[.panel-name[๐จโ๐ญ Workflow example] .xsmall.pull-left[ ```r library(charpente) path <- file.path(tempdir(), "mypkg") create_charpente(path, license = "mit") # Once the package is created and opened # Look for all bulma flavors get_dependency_versions("bulma") # Download bulma locally create_dependency("bulma") # Create JS handler create_custom_handler("modal") # Create input binding create_input_binding("myinput") # Compress JS for production build_js() devtools::load_all() ``` ] .pull-right[ <video height="400px" width="100%" controls> <source src="assets/movies/charpente-demo.mov" type="video/mp4"> </video> ] ] .panel[.panel-name[Applications] .center[ <a href="https://github.com/RinteRface/bs4Dash"><img src="assets/images/bs4Dash.svg" width="20%" style="margin: auto;" class="svg-img"/></a> <a href="https://github.com/RinteRface/shinyMobile"><img src="assets/images/shinyMobile.svg" width="20%" style="margin: auto;" class="svg-img"/></a> <a href="https://github.com/RinteRface/shinydashboardPlus"><img src="assets/images/shinydashboardPlus.svg" width="20%" style="margin: auto;" class="svg-img"/></a> ] ] ] --- class: end_part_1 --- class: inverse center title-slide middle # ๐ Sorry I don't have time ๐ --- class: header_background # Use existing templates: <a href="https://github.com/RinteRface/shinyMobile"><img src="assets/images/shinyMobile.svg" width="5%" style="display: inline; margin: auto;" class="svg-img"/></a> <br> Built on top of [Framework7](https://framework7.io/). .small.pull-left[ - Native look and feel for iOS and Android - PWA support: - Can be __installed__ on the device ... - ... But run via a web browser. - Provide __offline__ features (don't expect too much). - One code base (web languages). - ... also works for desktop apps ๐ ] .pull-right[ <div class="md-iphone-5 md-white-device" style="font-size: 0.5em; margin-top: -150px; !important;"> <div class="md-body"> <div class="md-buttons"></div> <div class="md-front-camera"></div> <div class="md-top-speaker"></div> <div class="md-screen"> <iframe width="100%" src="http://localhost:3522" allowfullscreen="" frameborder="0" scrolling="yes" height="470px"></iframe> </div> <button class="md-home-button"></button> </div> </div> ] --- class: header_background # I want to go faster! <br> ### {truelle} is a {golem} GUI generator <a href="https://github.com/RinteRface/truelle"><img src="assets/images/truelle.png" width="5%" style="margin: auto;" class="svg-img"/></a> -- .small.pull-left[ ```r remotes::install_github("RinteRface/truelle") library(truelle) run_app() ``` ] .pull-right[ <video height="400px" width="100%" controls> <source src="assets/movies/truelle-demo.mov" type="video/mp4"> </video> ] --- class: center middle # Want to learn more โ .center[ ๐ [Read my book](https://unleash-shiny.rinterface.com) ๐ ] <img src="assets/images/shiny-ui-book.png" width="20%" style="display: block; margin: auto;" /> .center[ Thanks ๐ ] --- class: end_part_2