Lchiffon / wordcloud2

R interface to wordcloud for data visualization.
397 stars 113 forks source link

Click Event in shiny? #25

Open jas84 opened 7 years ago

jas84 commented 7 years ago

Is it possible to return a click on a word of the wordcloud as a click event in shiny in order to bind other objects (e.g. bsModal) to it? For example, in plotly this is a accomplished by generating an object that can be accessed from within the shiny session and holds the event data (e.g. click coordinates) (https://plot.ly/r/shinyapp-linked-click/).

Lchiffon commented 7 years ago

You can use the hoverFunction to define the event when you move your mouse on a word.

Unlucky, it should be a JS function rather than R function.

hoverFunction = htmlwidgets::JS("function hover() {}")
wordcloud2(demoFreq,hoverFunction = hoverFunction)

See these codes

AdamSpannbauer commented 6 years ago

Here is a function that can be added in shinyUI to add some click event functionality. This is adapted from an SO answer. The function is not heavily bulletproofed/tested, but has been getting the job done for me so far.

#function to give wordcloud2 click interactivity
wc2ClickedWord = function(cloudOutputId, inputId) {
  #ARGUMENTS
  #       - cloudOutputId: string; outputId of wordcloud2 obj being rendered (should be identical to the value passed to wordcloud2Output)
  #       -       inputId: string; inputId of word clicked on (ie you will reference in server the word by input$inputId)
  #OUPUT
  #       - referencing input in server will return a string of form word:freq (same as hover info shown in wordcloud; ie 'super:32')
  #USAGE
  # library(shiny)
  # library(wordcloud2)
  # shinyApp(
  #   ui=shinyUI(fluidPage(
  #     wordcloud2Output("my_wc"),
  #     wordcloud2Output("my_wc2"),
  #     
  #     wc2ClickedWord("my_wc", "selected_word"),
  #     wc2ClickedWord("my_wc2", "selected_word2"),
  #     
  #     verbatimTextOutput("print"),
  #     verbatimTextOutput("print2")
  #   )),
  #   server=shinyServer(function(input,output,session){
  #     output$my_wc = renderWordcloud2(wordcloud2(demoFreq))
  #     output$my_wc2 = renderWordcloud2(wordcloud2(demoFreq))
  #     
  #     output$print = renderPrint(input$selected_word)
  #     output$print2 = renderPrint(input$selected_word2)
  #   })
  # )
  shiny::tags$script(shiny::HTML(
    sprintf("$(document).on('click', '#%s', function() {", cloudOutputId),
    'word = document.getElementById("wcSpan").innerHTML;',
    sprintf("Shiny.onInputChange('%s', word);", inputId),
    "});"
  ))
}
Lchiffon commented 6 years ago

That's COOOOOOOL!

AdamSpannbauer commented 6 years ago

Note, if #32 PR is approved (which I hope it is since it fixes a hover info bug) the click function I posted above will break since it was using the hardcoded wcSpan Id. If #32 is approved then the below function seems to work for click events.

#function to give wordcloud2 click interactivity
wc2ClickedWord = function(cloudOutputId, inputId) {
  #ARGUMENTS
  #       - cloudOutputId: string; outputId of wordcloud2 obj being rendered (should be identical to the value passed to wordcloud2Output)
  #       -       inputId: string; inputId of word clicked on (ie you will reference in server the word by input$inputId)
  #OUPUT
  #       - referencing input in server will return a string of form word:freq (same as hover info shown in wordcloud; ie 'super:32')
  #USAGE
  # library(shiny)
  # library(wordcloud2)
  # shinyApp(
  #   ui=shinyUI(fluidPage(
  #     wordcloud2Output("my_wc"),
  #     wordcloud2Output("my_wc2"),
  #     
  #     wc2ClickedWord("my_wc", "selected_word"),
  #     wc2ClickedWord("my_wc2", "selected_word2"),
  #     
  #     verbatimTextOutput("print"),
  #     verbatimTextOutput("print2")
  #   )),
  #   server=shinyServer(function(input,output,session){
  #     output$my_wc = renderWordcloud2(wordcloud2(demoFreq))
  #     output$my_wc2 = renderWordcloud2(wordcloud2(demoFreq))
  #     
  #     output$print  = renderPrint(input$selected_word)
  #     output$print2 = renderPrint(input$selected_word2)
  #   })
  # )
  shiny::tags$script(shiny::HTML(
    sprintf("$(document).on('click', '#%s', function() {", cloudOutputId),
    sprintf('word = document.getElementById("%swcSpan").innerHTML;', cloudOutputId),
    sprintf("Shiny.onInputChange('%s', word);", inputId),
    "});"
  ))
}
giocomai commented 6 years ago

@AdamSpannbauer this solution is great (even if I had some issues in Firefox), and perhaps it would be nice to include the function in the main package itself. Have you found any effective way to reset input$inputId to NULL, or, in other words, to deselect the word after you've clicked on it (e.g. by clicking on the background?) clicking on other words works fine, but I haven't found an easy way to get back to the point when no word is selected.

AdamSpannbauer commented 6 years ago

@giocomai a possible way set input$inputId to NULL is outlined in this SO answer. You can see this gist for a minimal example of the technique paired with the wordcloud2 click event function I posted in this thread.

AdamSpannbauer commented 6 years ago

@Lchiffon I forked the project to look into adding click events to the wordcloud2::wordcloud2Output function.

I believe the functionality is fairly easily achieved, but the current method I have working would require adding shiny as a dependency. Are you open to this added dependency to add click events? If yes, I will polish up the code/documentation for further testing and submit a PR.

AdamSpannbauer commented 6 years ago

I went ahead and made PR #35 that adds a click event to the wordcloud2 widget. This PR as it currently stands would add shiny as an Import if merged.