class: center, middle, inverse, title-slide .title[ # 量化金融与金融编程 ] .subtitle[ ##
L08
shiny
&
*dashboard
] .author[ ###
曾永艺 ] .institute[ ### 厦门大学管理学院 ] .date[ ###
2023-12-01 ] --- class: inverse, center, middle, hide_logo background-image: url(imgs/logo-shiny.svg) background-size: 4em background-position: 30% 45%
# 1. `shiny` <sup>.font60[v1.8.0]</sup> .font150[(_Web Application Framework for R_)] --- layout: true ### >> `shiny` --- - .font130[`shiny` app 是指和一台正运行着 R 会话(live R session)的计算机(即服务端 **Server**)相连的网页(即用户界面 **UI**)。] -- <img src="imgs/shiny_01.png" width="50%" style="display: block; margin: auto;" /> -- <img src="imgs/shiny_02.png" width="55%" style="display: block; margin: auto;" /> -- - .font130[用户直接在 **UI** 中操作,而 **Server** 根据要求运行 R 代码并更新 **UI** 内容。] --- layout: true ### >> `shiny` Workflow --- .pull-left[ .full-width[.content-box-blue.bold[File | New File | Shiny Web App ... <sup>.red.font80[*]</sup>]] .code90[ ``` # app.R # library(shiny) # 1. Define UI for application ui <- fluidPage( # Nested R functions that assemble # an HTML UI for your app ) # 2. Define server logic server <- function(input, output) { # Instructions on how to (re)build # the R objects displayed in the UI } # 3. Combines ui and server into an app shinyApp(ui = ui, server = server) ``` ] ] -- .pull-right.font100.bold[ 1. 基于模板创建 app.R 文档 2. 定义 app 的 UI: * 定义 UI 的布局(layout) * 通过 `*Input()`、`*Botton()` 等函数增加用户输入界面 * 通过 `*Output()` 函数增加输出界面 3. 告诉服务端如何根据 `server` 函数中 R 代码来计算并呈现输出: * 用 `output$<id>` 连接输出 * 用 `input$<id>` 连接输入 * 将代码嵌入 `render*()` 函数中以得到拟输出的结果 4. 用按键或命令 `runApp(<path>)` 运行 app 5. 分享你的 app(如通过<https://www.shinyapps.io/>) ] .footnote.red[\* 也可以用快捷方式输入_snippet_:`shinyapp` -> .kbd.code90[`Shift+Tab`]] --- layout: true ### >> `shiny` Basics --- .full-width[.content-box-blue.bold[UI >> example codes]] ```r # Define UI for application that draws a histogram *ui <- fluidPage( # Application title titlePanel("Old Faithful Geyser Data"), # Sidebar with a slider input for number of bins * sidebarLayout( * sidebarPanel( * sliderInput( inputId = "bins", label = "Number of bins:", min = 1, max = 50, value = 30) ), # Show a plot of the generated distribution mainPanel( * plotOutput(outputId = "distPlot") ) ) ) ``` --- .full-width[.content-box-blue.bold[UI >> layouts]] .pull-left[ - app 的 UI 本质上就是个 HTML 文档 ```r fluidPage( titlePanel("Old Faithful Geyser"), textInput("a", "") ) ``` .center[⇓] ```html <div class="container-fluid"> <h2>Old Faithful Geyser</h2> <div class="form-group shiny-input-container"> <label class="control-label" id="a-label" for="a"></label> <input id="a" type="text" class="form-control" value=""/> </div> </div> ``` ] -- .pull-right[ - .bold[Pages] .font90[`fluidPage(); navbarPage(); fixedPage(); fillPage(); bootstrapPage(); basicPage()`] - .bold[Layouts] .font90[`sidebarLayout(); flowLayout(); splitLayout(); verticalLayout(); fluidRow(); fillRow(); fixedRow()`] - .bold[Panels] .font90[`titlePanel(); sidebarPanel(); mainPanel(); absolutePanel(); conditionalPanel(); fixedPanel(); headerPanel(); inputPanel(); navlistPanel(); tabPanel(); tabsetPanel(); wellPanel()`] - .bold[Inputs and Outputs] ▼ ] --- .full-width[.content-box-blue.bold[UI >> Inputs]] .font110.bold[ - 通过 .red[`*inputfunc*(inputId, label, ...)`] 来收集用户的输入 - 通过 .red[`input$<inputId>`] 获得响应式输入对象的当前取值 ] -- <img src="imgs/shiny_03_inputs.jpg" width="88%" style="display: block; margin: auto;" /> --- .full-width.content-box-blue.bold[UI >> Outputs] .font110.bold[ - 前端 .red[`\*Output()`] 和 后台 .red[`render\*()`] 👫 工作来给 UI 增加输出 ] -- <img src="imgs/shiny_04_outputs.png" width="85%" style="display: block; margin: auto;" /> --- .full-width.content-box-blue.bold[UI >> [shinyuieditor](https://rstudio.github.io/shinyuieditor)] <iframe src="https://rstudio.github.io/shinyuieditor/live-demo/" width="100%" height="500px" data-external="1"></iframe> --- .full-width[.content-box-blue.bold[server >> example codes]] <br> .code100[ ```r # Define server logic required to draw a histogram server <- function(input, output, session) { * output$distPlot <- renderPlot({ # generate bins based on input$bins from ui x <- faithful[, 2] * bins <- seq(min(x), max(x), length.out = input$bins + 1) # draw the histogram with the specified number of bins hist(x, breaks = bins, col = 'darkgray', border = 'white') }) } ``` ] --- .full-width[.content-box-blue.bold[reactivity]] <br> <img src="imgs/shiny_05_reactivity.png" width="90%" style="display: block; margin: auto;" /> --- .full-width[.content-box-blue.bold[`shiny::runExample("01_hello")`]] .center.w-85[ <img src="imgs/shiny_06.gif" width="85%" style="display: block; margin: auto;" /> ] .footnote.red[\* `shiny::runExample()` 列出内置的11个App示例,可尝试运行并查看代码。] --- layout: false class: inverse, center, middle background-image: url(imgs/logo-bs4Dash.png) background-size: 4em background-position: 69% 52% # 2. `shinydashboard` <sup>.font60[v0.7.2]</sup> # `bs4Dash` <sup>.font60[v2.3.0]</sup> .font150[(_Create Dashboards with `Shiny`_)] --- layout: true ### >> `shinydashboard` Structure --- .font120.bold[`shinydashboard` 利用 [{{AdminLTE}}](https://adminlte.io/) <sup> .red[*] </sup> 提供的模板主题工具,这让使用 `shiny` 开发仪表盘式的网页 App 变得更加容易。] .footnote.red.font80[\* Best open source admin dashboard & control panel theme. Built on top of Bootstrap, AdminLTE provides a range of responsive, reusable, and commonly used components.] -- .panelset[ .panel[.panel-name.font80[代码] .code90[ ``` ## app.R ## library(shiny) library(shinydashboard) ui <- dashboardPage( # 仪表盘页面由三部分组成: dashboardHeader(), # 表头 dashboardSidebar(), # 边栏 dashboardBody() # 主栏 ) server <- function(input, output) { } shinyApp(ui, server) ``` ] ] .panel[.panel-name.font80[仪表盘] <img src="imgs/dashboard01_blank.png" width="60%" style="display: block; margin: auto;" /> ] ] --- layout: true ### >> `shinydashboard` Structure >> `dashboardHeader()` --- <img src="imgs/dashboard02_header.png" width="100%" style="display: block; margin: auto;" /> .code85[ ```r ui <- dashboardPage( * dashboardHeader( # 表头 title = "My awesome DashBoard", * dropdownMenu( # 可重复多个 dropdownMenu() type = "messages", # "notifications" / "tasks" badgeStatus = "primary", icon = NULL, * messageItem( # 可重复多个 messageItem() from, message, icon, time, href ), # ... ), # dropdownMenuOutput() # dynamic dropdown menu (UI-side) ), dashboardSidebar(), # 边栏 dashboardBody() # 主栏 ) ``` ] --- layout: true ### >> `shinydashboard` Structure >> `dashboardSidebar()` --- .left-column[ <img src="imgs/dashboard03_sidebar.png" width="100%" style="display: block; margin: auto;" /> ] .right-column[ .code80[ ```r SIDEBAR <- dashboardSidebar( * sidebarMenu( * menuItem("Dashboard", tabName = "dashboard", icon = icon("dashboard")), menuItem("Widgets", icon = icon("th"), tabName = "widgets", badgeLabel = "new", badgeColor = "green") # menuItemOutput() # dynamic sidebar menu item (UI-side) ), # sidebarMenuOutput(), # dynamic sidebar menu (UI-side) # sidebarSearchForm(), sidebarUserPanel() * sliderInput(inputId = "threshold", label = "Threshold", min = 1, max = 20, value = 5), # *Input() ) BODY <- dashboardBody( * tabItems( tabItem(tabName = "dashboard", h2("Dashboard tab content")), tabItem(tabName = "widgets", h2("Widgets tab content")) ) ) dashboardPage(dashboardHeader(), SIDEBAR, BODY) ``` ] ] --- layout: true ### >> `shinydashboard` Structure >> `dashboardBody()` --- .panelset[ .panel[.panel-name.font80[代码示例] .code80[ ```r BODY <- dashboardBody( # mixed row and column layout fluidRow( box(title = "Box title", width = 6, status = "primary", "Box content"), box(status = "warning", width = 6, "Box content") ), fluidRow( column(width = 4, box( title = "Title 1", width = NULL, solidHeader = TRUE, status = "primary", "..." ), box( with = NULL, background = "black", "A box with a solid black background" ) ), # column(width = 4, box(...), box(...)), # column(width = 4, box(...), box(...)) ) ) # box() / tabBox(tabpanel()) / infoBox()/infoBoxOutput() / valueBox()/valueBoxOutput() ``` ] ] .panel[.panel-name.font80[仪表盘示例] <img src="imgs/dashboard04_layout-mixed.png" width="50%" style="display: block; margin: auto;" /> ] ] --- layout: false ### >> `shinydashboard` [{{示例:Streaming CRAN data}}](https://gallery.shinyapps.io/087-crandash/) <iframe src="https://gallery.shinyapps.io/087-crandash/" width="100%" height="550px" data-external="1"></iframe> --- layout: false ### >> `bs4Dash` [{{示例}}](https://dgranjon.shinyapps.io/bs4DashDemo/) <iframe src="https://dgranjon.shinyapps.io/bs4DashDemo/" width="100%" height="550px" data-external="1"></iframe> --- layout: false class: inverse, center, middle background-image: url(imgs/logo-flexdashboard.png) background-size: 4em background-position: 20% 45% # 3. `flexdashboard` <sup>.font60[v0.6.2]</sup> .font150[(_R Markdown Format for Flexible Dashboards_)] --- layout: true ### >> `flexdashboard` --- .font130.bold[ - 使用 **R Markdown** 将一组相关的数据可视化作为仪表盘图表展示 - 支持多种多样的内容组件(**components**),包括 htmlwidgets、基础图、lattice 图和 grid 图、表格数据、仪表(gauge)和数值框(value boxes)以及文本注释等等 - 灵活且容易设定的基于列或行的布局(**layouts**),内容组件会智能调整大小,填充浏览器空间并适合移动终端的显示。 - 可选择 Storyboard 布局(连环画布局)来展示系列可视化图表和相应的文字说明 - 可选择使用 [{{**Shiny**}}](https://shiny.posit.co/) 作为动态可视化的驱动引擎 - 可选择使用 [{{`bslib` 包}}](https://rstudio.github.io/bslib/),更容易个性化定制颜色、字体等 ] --- layout: true ### >> `flexdashboard` Template --- .full-width[.content-box-blue.bold[File | New File | R Markdown ... | From Template | Flex Dashboard ...]] -- .full-width[.content-box-blue.bold[_head codes_]] .code100[ ``` --- title: "Untitled" output: * flexdashboard::flex_dashboard: * orientation: columns * vertical_layout: fill --- ``` ] -- .code100[ ```` *```{r setup, include=FALSE} *library(flexdashboard) * *``` ```` ] --- .pull-left[ .full-width[.content-box-blue.bold[_layout codes_]] <img src="imgs/flex_01.png" width="100%" style="display: block; margin: auto;" /> ] -- .pull-right[ .full-width[.content-box-blue.bold[Knit it (_Ctrl+Shift+K_) and get ...]] <img src="imgs/flex_02.png" width="100%" style="display: block; margin: auto;" /> ] --- layout: true ### >> `flexdashboard` Layouts --- .pull-left.code90[ .full-width[.content-box-blue.bold[Layouts >> markdown headers]] ``` # level 1: pages ## level 2: columns / rows ### level 3: boxes ``` <hr> ``` level 1: pages === level 2: columns / rows --- ### level 3: boxes ``` <br> .full-width[.content-box-blue.bold[Layouts >> options]] - `vertical_layout: fill | scroll` - `orientation: columns | rows` - [`storyboard: false | true`](https://testing-apps.shinyapps.io/flexdashboard-storyboard/) ] -- .pull-right.font100[ .full-width[.content-box-blue.bold[Layouts >> attributes]] - `{data-orientation=columns|rows}`; `{data-navmenu="Menu A"}`; `{data-icon="fa-github"}`; `{.hidden}`; `{.storyboard}` - `{data-height=#}`; `{data-width=#}`; `{data-padding=8}`; `{.no-padding}`; `{.no-title}`; `{.sidebar}` - `{data-commentary-width=300}` - `{.mobile}`; `{.no-mobile}` ] -- .bold.right[[>>{{Sample Layouts,总有一款适合你!}}<<](https://pkgs.rstudio.com/flexdashboard/articles/layouts.html)] --- layout: true ### >> `flexdashboard` [{{Components}}](https://pkgs.rstudio.com/flexdashboard/articles/using.html#components) --- .pull-left.font110[ - .bold[[{{`htmlwidgets`}}](http://www.htmlwidgets.org/)] 提供对[多个](https://gallery.htmlwidgets.org/) JavaScript 数据可视化库的 R 语言绑定,如 .font90[`leaflet`、`dygraphs`、`plotly`、`rbokeh`、`highcharter`、`visNetwork`]等 - .bold[R 基础图、lattice 图、grid 图等] - .bold[表格数据] * 静态的 dashboard 用 .font80[`knitr::kable()`] * 基于 .font80[`shiny`] 的 dashboard 用 .font80[`shiny::renderTable()`] * .font80[`DT`] 表格:静态的 dashboard 用 .font80[`DT::datatable()`],基于 .font80[`shiny`] 的 dashboard 则用 .font80[`DT::renderDataTable()`] ] -- .pull-right.code80[ - .font110.bold[仪表与数值框] ```r # 仪表 示例 *gauge( rating, min = 0, max = 50, gaugeSectors( success = c(41, 50), warning = c(21, 40), danger = c(0, 20) ) ) # 数值框 示例 *valueBox( spam, icon="fa-trash", href="#details" color = ifelse( spam > 10, "warning", "primary") ) ``` - .font110.bold[文本说明] - .font110.bold[导航栏] ] --- layout: false ### >> `flexdashboard` [{{示例:sharpe_ratio_flexdashboard}}](https://yyzeng.shinyapps.io/sharpe_ratio_shiny/) <iframe src="https://yyzeng.shinyapps.io/sharpe_ratio_shiny/" width="100%" height="550px" data-external="1"></iframe> --- layout: false class: inverse, center, middle, animated, lightSpeedIn, slideOutLeft # 学习参考资源 --- class: animated, slideInRight .font120.bold[ 相信同学们现在对 `shiny`、`shinydashboard` / `bs4Dash` 和 `flexdashboard` 有了初步的认识,以下列出相关的学习参考资料,同学们可进一步学习精进下 🏆 ] .pull-left.font110[ - [{{`shiny` 官网}}](https://shiny.posit.co/r/getstarted/shiny-basics/lesson1/index.html) 上有大量的学习资料! - 👍👍👍:**循序渐进的 [{{7 份教程}}](https://shiny.rstudio.com/tutorial/written-tutorial/lesson1/)** .font90[ > 1. _Welcome to Shiny_ > > 2. _Build a user interface_ > > 3. _Add control widgets_ > > 4. _Display reactive output_ > > 5. _Use R scripts and data_ > > 6. _Use reactive expressions_ > > 7. _Share your apps_ ] - 👍👍👍:[{{示例库}}](https://shiny.posit.co/r/gallery/) - 📰 [.bold[{{`shiny`的cheatsheet}}]](docs/shiny-cheatsheet.pdf) ] .pull-right.font105[ - 📖 [{{Mastering Shiny}}](https://mastering-shiny.org/) <img src="imgs/mastering-shiny.png" width="42%" style="display: block; margin: auto;" /> - 😎 [{{**awesome-shiny-extensions**}}](https://github.com/nanxstats/awesome-shiny-extensions) - 🌐 [{{`shinydashboard` 主页}}](https://pkgs.rstudio.com/shinydashboard/) - 🌐 [{{`bs4Dash` 主页}}](https://bs4dash.rinterface.com/) - 🌐 [{{`flexdashboard` 主页}}](https://pkgs.rstudio.com/flexdashboard/) ] --- layout: false class: inverse, center, middle background-image: url(imgs/logo-bslib.png) background-size: 5%, 100% background-position: 5% 3% .left.font80[[`bslib`](https://rstudio.github.io/bslib/) <sup>.font60[v0.6.0]</sup>: Custom 'Bootstrap' 'Sass' Themes for `shiny` and `rmarkdown`] <iframe src="https://rstudio.github.io/bslib/" width="100%" height="580px" data-external="1"></iframe> --- layout: false class: inverse, center, middle background-image: url(imgs/logo-thematic.png) background-size: 4% background-position: 5% 3% .left.font80[   [`thematic`](https://rstudio.github.io/thematic/) <sup>.font60[v0.1.4]</sup>: Unified and Automatic 'Theming' of `ggplot2`, `lattice`, and `base` R Graphics] <iframe src="https://rstudio.github.io/thematic/" width="100%" height="580px" data-external="1"></iframe> --- layout: false class: center, middle, animated, zoomInDown background-image: url(imgs/logo-xaringan.png) background-size: 12% background-position: 50% 40% <br><br><br><br><br><br><br> <hr color='#f00' size='2px' width='80%'> <br> .Large.red[_**本网页版讲义的制作由 R 包 [{{`xaringan`}}](https://github.com/yihui/xaringan) 赋能!**_]