Custom Shiny Input

Creating a custom Shiny Input
JavaScript
Author

Maya Gans

Published

August 30, 2021

With a little bit of JQuery, the JavaScript function Shiny.setInputValue lets you take any HTML element and make it a Shiny input which I’ll display here using a div called all_circles which contains three circles. Think of all_circles as a widget like selectInput, the circles themselves would then be the same as setting choices.

selectInput("inputID", "Name to Display", choices = c("Red Circle", "Yellow Circle", "Blue Circle"))

Because we can envision use cases to be beyond clicking on simple shapes, I wanted to use SVGs, which was my first learning curve.

<svg class="circ" height="100" width="100" xmlns="http://www.w3.org/2000/svg">
  <circle id ='circ_red' cx='50' cy='50' r='40' stroke='black' stroke-width='3' fill='red'/>
</svg>

Note that the svg class is circ and the circle has the unique id circ_red. Repeating this code two more times but changing the fill to blue and yellow and the circle ids to circ_blue and circ_yellow yeilds three circles, all of the same class but with their own unique ids:

Shiny can take on a lot of HTML tags, but I found to properly render SVGs I needed to add a uiOutput to the UI portion of the app

And put the SVG code in a reactive within the server:

 output$svgout <- renderUI({
    HTML(
      "<svg class='circ' height='100' width='100' xmlns='http://www.w3.org/2000/svg'>
        <circle id ='circ_red' cx='50' cy='50' r='40' stroke='black' stroke-width='3' fill='red'/>
      </svg>
      <svg id ='circ_blue' class='circ' height='100' width='100' xmlns='http://www.w3.org/2000/svg'>
        <circle id ='circ_blue' cx='50' cy='50' r='40' stroke='black' stroke-width='3' fill='blue' />
      </svg>
      <svg id ='circ_yellow'class='circ' height='100' width='100' xmlns='http://www.w3.org/2000/svg'>
        <circle id ='circ_yellow' cx='50' cy='50' r='40' stroke='black' stroke-width='3' fill='yellow'/>
      </svg>"
    )
  })

Note that I wrapped the SVG within a div called all_circles - this is the div that will act as our selectInput.

JavaScript Time!

In the next little chunk of code we use JQuery to go into the div of class all_circles and find the id of the circ class that was clicked on. We can then take that id and assign it as the value of the shiny input all_circles.

  tags$script(HTML(
    "$('#all_circles').on('click', '.circ', (ev) => {
        Shiny.setInputValue('all_circles', ev.target.id)
        })
        "
  ))

And there you have it! The # denotes that we’re looking for an id and the . a class name. We’re using a callback function to get the id of the clicked svg, which you can learn more about in some of my prior blog posts. By adding verbatimTextOutput("debug") and in the server input$debug <- renderText(input$all_circles) you’ll see that the input changes from red to yellow to blue on click!

My revelation to set custom Shiny inputs was an incredibly powerful one that I needed to share, as it enables assigning whatever you want as an input with only a little bit of JQuery to find the elements you want to set as your choices. For instance, I used This opensource library of a human body made fully with CSS and SVGs to create a div called human_body, then made the “choices” each of the body part SVGs. I hope this opens up new ways of exploring and interacting with data as it did for me!!