• R语言
  • 怎样让含有 shiny 元素的 flexdashboard 生成 html 文档?

俺最近想画一个可以调整宽度的直方图给用户看看,搜到 shiny 里面的selectInput()刚好满足需求,可是含有 shiny 元素的 flexdashboard 的.Rmd保存以后就没有 knit 那个图标,而是变成一个绿色三角符号的 Run Document。我在 R Console 里面输入rmarkdown::render('test.Rmd')后会报错,也无法生出来 html 文档。

File shared/selectize/css/selectize.bootstrap3.css not found in resource path
Error: pandoc document conversion failed with error 99

上网搜了搜,好像含有 shiny 元素的不管什么类型的文档都得部署到服务器上面去,就没别的路走了吗?

栗子代码

---
title: "举个栗子"
output: 
  flexdashboard::flex_dashboard:
    orientation: rows
    vertical_layout: scroll
runtime: shiny    
---

```{r setup, include=FALSE}
library(flexdashboard)
library(echarts4r)
library(shiny)
```

```{r global, include=FALSE}
# 导入数据
data <-
  data.frame(type = rep(c('A', 'B', 'C'), 100), value = sample(1:10000, 300))
```

## Inputs {.sidebar data-width=200}

```{r}
# shiny inputs defined here
selectInput(
  "n_breaks",
  label = "频数调整(等宽)",
  choices = c(5, 10, 15, 20),
  selected = 5)
```

## Row {data-height = 500}

### Chart A

```{r}
renderEcharts4r(
  data |>
    e_charts() |>
    e_histogram(value, breaks = as.numeric(input$n_breaks)) |>
    e_tooltip()|>
    e_labels(show=TRUE,position = 'top')
)
```

    Cloud2016
    可是据我理解 crosstalk 是用来做筛选、过滤之类的功能的,而且用到筛选或过滤的框里还必须是共享数据里面的某个字段。或者你告诉我用 crosstalk 咋跟直方图联动起来?

      yuanfan 是的,shiny 无法像普通静态网页一样独立运行,它一定得有一个 R 进程来支撑(因为后端运算都得在 R 里面进行)。这个 R 进程可以是在服务器上(如果这个例子需要其他人远程访问),也可以在你本地的电脑上。包含 shiny 元素的 R Markdown 文档只能点绿色三角形运行,或者用 rmarkdown::run(),没有 Knit 图标可以点,也不能用 rmarkdown::render()

      Cloud2016 我不知道 echarts4r 是否支持 crosstalk,要是不支持的话恐怕 crosstalk 也帮不上忙。至少这个页面上没有列出 echarts4r:https://rstudio.github.io/crosstalk/widgets.html

        yihui
        echarts4r 不支持 crosstalk,我试过了。

        那么有没有别的路子,不直接用shiny,但是仅仅用selectInput()呢?因为我的初衷就是鼓捣一个可以调整宽度的直方图然后发给用户看。shiny 我刚接触,部署那块还没开始学,稍微看看都是针对.R文档的,没看见怎么把.Rmd怎么弄到服务器上面去的(PS.也可能是我对这个工具太陌生而看漏了)。

          yuanfan 最简单的路子就是把这个 .Rmd 文档部署到服务器上,跟部署 .R 文件没有什么本质区别。

          其次简单的路子是你根据你的 selectInput() 里预设的窗宽把所有的直方图预先画出来保存成图片,然后在 HTML 页面上用 <select> 元素的 JavaScript 事件来控制显示哪幅图。这倒是个不错的 JS 练习。我就是在 2007 年这样玩图片开始玩起 JS 来的。

            yihui
            明白了,我选最简单的路子。
            次简单的路子对我这个纯纯的新手来说,可能是一种蠢蠢的死法。有一次,我把 knit 生成的 html 文档不用浏览器打开,而是当做文本文件打开看,里面的 css / html 有很多元素或者参数设置我都没见过,但是感觉网上搜搜就能看明白,而 JavaScript 那些定义这那、返回这那的都看不明白。我觉得 css/html 很好入门,将来经过量的积累就能达成质的飞跃,但是 JavaScript 我还完全迷蒙,连量的积累阶段都还没摸到。

              yuanfan 可能是一种蠢蠢的死法

              十分钟写个例子(窃以为这个例子简短高能,值得收受西瓜贿赂,前面的反斜杠解释还不值),只是看一眼总不会死吧:

              ---
              title: "下拉菜单控制直方图的窗宽"
              output: html_document
              ---
              
              提供一个下拉菜单。
              
              ```{r, echo=FALSE}
              n_breaks = c(5, 10, 15, 20)
              shiny::selectInput(
                  "n_breaks",
                  label = "频数调整(等宽)",
                  choices = n_breaks,
                  selected = 5)
              ```
              
              预先画出所有的图,放在一个类名为 `hist-all` 的层中。
              
              ```{r, class.chunk='hist-all'}
              x = faithful$eruptions
              for (i in n_breaks) {
                hist(x, breaks = seq(min(x), max(x), length.out = i + 1),
                     main = paste('n = ', i))
              }
              ```
              
              但是先不要显示它们。
              
              ```{css}
              /* 默认隐藏所有图片 */
              .hist-all img { display: none; }
              /* 但图上如果加了 show 类就显示 */
              .hist-all .show { display: block; }
              ```
              
              召唤 JS 法力。
              
              ```{js}
              (d => {
                const s = d.getElementById('n_breaks'),  // 下拉菜单
                      imgs = d.querySelectorAll('.hist-all img');  // 图片元素数组
                
                // 按照 s 中被选中菜单项的下标 i 来向第 i 幅图添加类名“show”并移除其它图的类名
                function showImg() {
                  const i = s.selectedIndex;
                  imgs.forEach((el, k) => {
                    // k 为图片元素的下标,i 为选中菜单的下标,若二者相等则添加 show 类
                    el.classList.toggle('show', k == i); 
                  });
                }
                
                // 小试牛刀,显示第一幅图
                showImg();
                
                // 当下拉菜单的值变动时(触发 onchange 事件),重新显示相应的图片
                s.onchange = showImg;
              })(document);
              ```

              这是个纯静态 HTML 页面,不需要部署到服务器,用 R Markdown 生成之后就可以脱离 R 运行。JS 代码中我用了相对较新的 => 语法,它是用来生成匿名函数的,x => {} 等价于 function(x) {};同理,(x, y) => {} 等价于 function(x, y) {}。最外面一层函数是为了避免创建全局对象、污染全局环境,而把所有对象都创建在一个匿名函数之内,最后再执行这个匿名函数(这是 JS 里最常见的伎俩),这伎俩 R 里面一样可以用(如 (function(x) {x + 1})(5))。除此之外,别的代码应该是不言自明的。

                yihui

                只是看一眼总不会死吧

                哈哈,《蠢蠢的死法》是一个游戏的名字。只看一眼当然不会狗带。西瓜和西红柿并不是贿赂,是表达感谢的心意。

                叫我写,就写不出来;看,能看明白。何况大神给每个步骤都写了详细的注释。但很奇怪的是我看到的代码,部分是蓝色的正体字,部分是黑色的斜体字。

                  yuanfan 《蠢蠢的死法》是一个游戏的名字

                  世上竟然有如此这般的游戏名字,本游戏盲真是死也不会想到。

                  yuanfan 很奇怪的是我看到的代码,部分是蓝色的正体字,部分是黑色的斜体字。

                  这是论坛 Markdown 程序的一个小问题,它蠢蠢地把一切下划线当做斜体语法了(n_breaks 里有个下划线,一直划过天际到下一个 n_breaks,真的是蠢到死)。