блестящий, ggvis и add_tooltip с HTML

Как я могу использовать функции tags$... из интерактивной графики ggvis?

"Маленький" и надуманный пример:

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

(Правда, не самый изящный для построения таблиц.)

Когда я использую ttFunc1 в строке add_tooltip(...), всплывающая подсказка отображается правильно. Однако когда я использую относительно эквивалентный ttFunc2, это пустая всплывающая подсказка.

Сравнение ttFunc1(x=list(rownum=2)) с ttFunc2(x=list(rownum=2)) показывает, что они функционально эквивалентны.

Что мне не хватает?


person r2evans    schedule 31.07.2014    source источник


Ответы (1)


Далее предполагается, что у вас установлена ​​последняя версия Chrome с установленными инструментами разработчика.

Прелюдия

Начнем с обзора кода JavaScript для ggvis -- особенно его интерфейс с Shiny.

ggvis, как и Shiny, взаимодействует с серверной частью R через HTTP-запросы, которые включены в https://github.com/rstudio/httpuv (первоначально основанном на в библиотеке libuv C++). В частности, он осуществляет некоторые коммуникации по протоколу веб-сокетов: R и JavaScript постоянно перетасовывают сообщения. взад и вперед друг к другу, используя открытое соединение Websockets.

Отладка с помощью инструментов разработчика Chrome

В частности, наведя указатель мыши на всплывающую подсказку, откройте консоль разработчика Chrome, щелкнув правой кнопкой мыши и выбрав «Проверить элемент».

введите здесь описание изображения

(Если вы его не видите, возможно, вам придется включить его — Google вам в помощь). Затем откройте вкладку «Сеть», перезагрузите страницу, наведите указатель мыши на точку данных и просмотрите содержимое с помощью ttFunc2 после выбора ресурса "websocket/":

введите здесь описание изображения

Вы можете щелкнуть правой кнопкой мыши и скопировать содержимое в файл:

{
   "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>"
 }}}}

Таким образом, первый запрос получает объект Javascript, представляющий HTML, а второй — необработанный HTML. Вскоре мы увидим, почему это так. А пока обратите внимание на код JavaScript код, обрабатывающий это сообщение:

 // 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 непосредственно в элемент html сообщения Websocket. Поскольку jQuery никогда не предполагал, что он будет взаимодействовать с потоком веб-вывода из пакета Rhtmltools, конечным результатом будет то, что он получит объект JavaScript вместо строки, а поведение по умолчанию заключается в том, чтобы не отображать ничего.

К исправлению

Теперь, когда мы изолировали нашу ошибку, у нас есть выбор: мы можем исправить это на стороне R или на стороне JavaScript. Я предлагаю первое, так как преобразование htmltools вывода не должно быть задачей клиентского кода и нарушает основные принципы разработчика, такие как модульность.

Таким образом, мы должны выяснить, где он находится на стороне R. Начнем с того, что перейдем к коду ggvis github и найдем "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))
}

Ошибка в том, что в нашем примере html является объектом shiny.tag, а не character. К счастью, shiny.tag можно преобразовать в его представление HTML с помощью as.character, как мы можем проверить из консоли:

  > 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 и отправив запрос на включение (большой зеленая кнопка).

введите здесь описание изображения

введите здесь описание изображения

Если вы хотите сразу же использовать фиксированный код, не дожидаясь, пока Winston объединит его, вы можете ввести

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

и правильная версия будет установлена ​​(но не делайте этого после того, как этому посту исполнится неделя, так как мой форк, вероятно, устарел). Я протестировал его, используя как ttFunc1, так и ttFunc2, и теперь их поведение идентично.

Можно копаться во внутренностях пакета. Никогда не бойся!

person Robert Krzyzanowski    schedule 01.08.2014
comment
Очень хороший подробный ответ, Роберт! Я знаком с отладчиком JS в Chrome, но еще не вникал во взаимодействие между Shiny и JS. Это на самом деле будет полезно с другими вопросами, которые я размышлял. Большое спасибо! - person r2evans; 01.08.2014