Cicero & Shiny

Create Guided Tours of your Shiny Application

John Coene https://john-coene.com
11-20-2019

This post shortly introduces the package cicerone(Coene 2019a) and why it was created. Just a bit about the package name and logo before we dive in.

Cicerone is an old term for a guide, one who conducts visitors and sightseers to museums, galleries, etc., and explains matters of archaeological, antiquarian, historic or artistic interest. The word is presumably taken from Marcus Tullius Cicero as a type of learning and eloquence.

Hence the package name and the presence of Cicero on the package sticker.

Rationale

I am one who always argued that, as much as I love Shiny, applications created with the latter framework tend to be ugly. The reason why exactly that is, is not entirely known to me.

It’s probably in part due to the fact that developers who use Shiny are very different from the average dev who uses nodejs or Django. It’s probably also due to the fact that Shiny apps tend to convey complex ideas (e.g.: model outputs) and therefore need to present information in a somewhat more thorough and wordy manner.

This is why I love the idea of Rinterface created by David Granjon because it demonstrates that it does not have to be ugly, on the contrary.

This is also why I have created tippy(Coene 2019b). Tooltips, are much underrated in my view: they’ll enable you to make your application much less wordy, only showing information when people need it and want it. Otherwise keeping it out of the way.

This is all well and good but does not entirely solve the problem, data scientists often have a to communicate complex models to a diverse audience, they cannot merely remove all the text and let users guess what is on screen, and cramming 300 words in a tooltip certainly won’t make the app any better.

Meet cicerone.

Cicerone will let you easily create guided tours of your Shiny applications. This allows the developer to, in a sense, show an “explanatory interface” on top of the application only when necessary, putting it away when it is not needed.

There already exists rintrojs(Ganz 2016) which is based on intro.js, the latter is also used by RStudio within a Shiny app here. However, I very arrogantly argue that {cicerone} is much more convenient. To be clear, there is no issue with the {rintrojs} package itself, but the underlying intro.js JavaScript library is not convenient and this subsequently handicaps the package. The way the latter is built one needs to mess around with the UI of the Shiny application in such a way that it quickly becomes unmanageable, at least for me. Cicerone relies on the much easier to use driver.js library.

Examples

Let’s demonstrate with a simple application: it takes some text and on hitting a button simply prints it. We’ll pretend it’s really complicated and create a simple guide to walk people through those steps 1) enter text 2) hit the submit button.


library(shiny)

ui <- fluidPage(
  textInput("text", "Enter some text"),
  actionButton("submit", "Submit text"),
  verbatimTextOutput("print")
)

server <- function(input, output){
  txt <- eventReactive(input$submit, {
    input$text
  })

  output$print <- renderPrint(txt())
}

shinyApp(ui, server)

Simply load the library, initialise a guide from the Cicerone object then add steps one by one; order is taken into account. First thing you’ll notice is that this is completely disconnected from the Shiny app. One does not have to fiddle in the server or the UI to create the guide: leave your actual application untouched.


library(cicerone)

guide <- Cicerone$
  new()$ 
  step(
    el = "text",
    title = "Text Input",
    description = "This is where you enter the text you want to print."
  )$
  step(
    "submit",
    "Send the Text",
    "Send the text to the server for printing"
  )

Then, in your application, source the dependencies with use_cicerone in the UI, and in the server initialise and start the guide.


library(shiny)

ui <- fluidPage(
  use_cicerone(), # include dependencies
  textInput("text", "Enter some text"),
  actionButton("submit", "Submit text"),
  verbatimTextOutput("print")
)

server <- function(input, output){

  # initialise then start the guide
  guide$init()$start()

  txt <- eventReactive(input$submit, {
    input$text
  })

  output$print <- renderPrint(txt())
}

shinyApp(ui, server)

There are tons of options both at the guide creation (new) and at every step, which I encourage you to explore e.g.: setting allow_close to FALSE, forcing users to go through the entire guide before they can interact with your app.

We could also add a button to let the user go through the guide again.


library(shiny)

ui <- fluidPage(
  use_cicerone(), # include dependencies
  actionButton("guide", "Start guide"),
  textInput("text", "Enter some text"),
  actionButton("submit", "Submit text"),
  verbatimTextOutput("print")
)

server <- function(input, output){

  # initialise then start the guide
  guide$init()$start()

  txt <- eventReactive(input$submit, {
    input$text
  })

  output$print <- renderPrint(txt())

  observeEvent(input$guide, {
    guide$start()
  })
}

shinyApp(ui, server)

Coene, John. 2019a. Cicerone: Provide Tours of ’Shiny’ Applications.

———. 2019b. Tippy: Add Tooltips to ’R Markdown’ Documents or ’Shiny’ Apps. http://tippy.john-coene.com/.

Ganz, Carl. 2016. “rintrojs: A Wrapper for the Intro.js Library.” Journal of Open Source Software 1 (6). http://dx.doi.org/10.21105/joss.00063.

Citation

For attribution, please cite this work as

Coene (2019, Nov. 20). John Coene's blog: Cicero & Shiny. Retrieved from https://cicerone.john-coene.com

BibTeX citation

@misc{coene2020cicerone,
  author = {Coene, John},
  title = {John Coene's blog: Cicero & Shiny},
  url = {https://cicerone.john-coene.com},
  year = {2019}
}