【问题标题】:shiny, ggvis, and add_tooltip with HTML带有 HTML 的闪亮、ggvis 和 add_tooltip
【发布时间】:2014-07-31 22:58:58
【问题描述】:

如何在 ggvis 交互式图形中使用 tags$... 函数?

一个“小”且人为的例子:

library(ggvis)
library(shiny)
n <- 20
data <- data.frame(
    xs = 1:n, ys = rnorm(n),
    color = sample(c('red', 'green', 'blue'), n, replace = TRUE),
    size = 25 * sample(6, n, replace = TRUE),
    rownum = 1:n)

ttFunc1 <- function(x) {
    paste('<table>',
          paste(apply(data.frame(n = names(data),
                                 x = unlist(format(data[x$rownum,]))), 1,
                      function(h) paste('<tr><td>', h[1],
                                        '</td><td>', h[2],
                                        '</td></tr>')),
                collapse = ''),
          '</table>')
}

ttFunc2 <- function(x) {
    tags$table(
        lapply(1:ncol(data),
               function(cc) {
                   tags$tr(tags$td(names(data)[cc]),
                           tags$td(format(data[x$rownum,cc])))
               }))
}

shinyApp(
    ui = fluidPage(
        uiOutput('gg_ui'),
        ggvisOutput('gg')
        ),
    server = function(input, output, session) {
        data %>%
            ggvis(~xs, ~ys, key := ~rownum) %>%
                layer_points(fill := ~color, size := ~size) %>%
                    add_tooltip(ttFunc2, 'hover') %>%
                        bind_shiny('gg', 'gg_ui')
    },
    options = list(height = 500)
)

(不可否认,这对于构建表格来说不是最优雅的。)

当我在add_tooltip(...) 行中使用ttFunc1 时,工具提示会正确显示。但是,当我使用相对等效的 ttFunc2 时,它是一个空的工具提示。

ttFunc1(x=list(rownum=2))ttFunc2(x=list(rownum=2)) 的比较表明它们在功能上是等效的。

我错过了什么?

【问题讨论】:

    标签: r shiny ggvis


    【解决方案1】:

    以下假设您拥有安装了开发者工具的最新版 Chrome。

    前奏

    让我们先回顾一下 ggvis 的 JavaScript 代码 -- specifically its interface with Shiny

    ggvis 和 Shiny 一样,通过 httpuv package 启用的 HTTP 请求与 R 后端通信(最初基于 libuv C++ 库)。特别是,它通过Websockets protocol 执行一些通信:R 和 JavaScript 使用开放的 Websockets 连接不断地相互交换消息。

    使用 Chrome 开发者工具进行调试

    特别是,将鼠标悬停在工具提示上后,通过右键单击并选择 Inspect Element 打开 Chrome 开发者控制台。

    (如果您没有看到它,您可能需要启用它——Google 是您的朋友)。接下来,打开 Network 选项卡,重新加载页面,将鼠标悬停在数据点上,然后在选择 "websocket/" 资源后使用 ttFunc2 观察内容:

    您可以右键单击并将内容复制到文件中:

    {
       "custom": {
          "ggvis_message": {
             "type": "show_tooltip",
             "id": null,
             "data": {
                "pagex":    382,
                "pagey":    175,
                "html": {
                   "name": "table",
                   "attribs": [],
                   "children": [
                    [
                     {
                        "name": "tr",
                        ...
    

    (我已经截断了一些内容)。如您所见,ggvis 正在接收带有工具提示正文的消息,但结构为 JavaScript 对象。将此与 ttFunc1 输出进行比较:

     {
      "custom": {
      "ggvis_message": {
      "type": "show_tooltip",
     "id": null,
     "data": {
      "pagex":    264,
     "pagey":    238,
     "html": "<table> <tr><td> xs </td><td> 7 </td></tr><tr><td> ys </td><td> -0.07295337 </td></tr><tr><td> color </td><td> red </td></tr><tr><td> size </td><td> 150 </td></tr></table>"
     }}}}
    

    所以前一个请求正在接收一个表示 HTML 的 Javascript 对象,后者正在接收原始 HTML。我们将立即看到为什么会这样。同时,通知the JavaScript code that is processing this message

     // Tooltip message handlers
     ggvis.messages.addHandler("show_tooltip", function(data, id) {
       /* jshint unused: false */
       // Remove any existing tooltips
       $('.ggvis-tooltip').remove();
    
       // Add the tooltip div
       var $el = $('<div id="ggvis-tooltip" class="ggvis-tooltip"></div>')
         .appendTo('body');
    
       $el.html(data.html);
       ...
    

    啊哈!所以它使用 jQuery 将 HTML 直接设置为 Websocket 消息的html 元素。由于 jQuery 从未期望与来自 R htmltools 包的网络流输出进行交互,因此最终结果是它接收到一个 JavaScript 对象而不是字符串,并且默认行为是不显示任何内容而静默失败。

    开始修复

    现在我们已经隔离了我们的错误,我们有一个选择:我们可以在 R 端或 JavaScript 端修复这个问题。我建议前者,因为转换 htmltools 输出不应该是前端代码的工作,并且违反了模块化等基本开发人员原则。

    因此,我们必须弄清楚它在 R 侧的位置。我们首先访问ggvis github code 并搜索"tooltip"(这很有用——您可以使用 Github 搜索整个代码库!):

    我们找到interact_tooltip.R 并注意到函数:

    show_tooltip <- function(session, l = 0, t = 0, html = "") {
      ggvis_message(session, "show_tooltip",
      list(pagex = l, pagey = t, html = html))
    }
    

    错误在于,在我们的示例中,htmlshiny.tag 对象,而不是 character。幸运的是,shiny.tag 可以使用 as.character 转换为代表 HTML,因为我们可以从控制台进行测试:

      > as.character(tags$table(tags$tr(tags$td('test'))))
      <table>
        <tr>
          <td>test</td>
        </tr>
      </table>
    

    所以我们可以继续修复代码:

    show_tooltip <- function(session, l = 0, t = 0, html = "") {
      ggvis_message(session, "show_tooltip",
      list(pagex = l, pagey = t, html = as.character(html)))
    }
    

    帮助你的朋友

    既然我们已经找到了修复,我们应该与我们的朋友分享它,以便他们也可以使用它。我们可以通过在 Github 和 submitting a pull request(绿色大按钮)上 fork 存储库来做到这一点。

    如果您想立即使用固定代码而不等待 Winston 合并它,您可以键入

    require(devtools); install_github('robertzk/ggvis')
    

    并且会安装正确的版本(但不要在这篇文章发布一周后这样做,因为我的 fork 可能已经过时了)。我已经使用ttFunc1ttFunc2 对其进行了测试,现在它们的行为是相同的。

    可以深入研究包内部。永远不要害怕!

    【讨论】:

    • 非常详细的答案,罗伯特!我对chrome的js调试器很熟悉,但是还没有深入研究shiny和js之间的交互。这实际上对我一直在思考的其他问题很有帮助。非常感谢!
    猜你喜欢
    • 1970-01-01
    • 2015-06-23
    • 2016-02-22
    • 1970-01-01
    • 1970-01-01
    • 2017-03-15
    • 2016-10-11
    • 2014-09-16
    • 2021-04-01
    相关资源
    最近更新 更多