Shiny Modules: Handling a list of buttons - r

I am trying to build an app that relies on a list of buttons created via lapply. I can successfully reference the buttons using observeEvent when I am not working with modularized code. However, when I try to use modules, the observeEvent doesn't work. I suspect it has something to do with how Shiny handles the namespace id's, but despite a couple of days of experimentation, I have not been able to solve the issue.
Below I will post first the non-modularized dummy app that does work (stolen from this other stack overflow question: R Shiny: How to write loop for observeEvent). Then I will share my existing modularized code that does not work.
Working non-modularized code:
library("shiny")
ui <- fluidPage(
fluidRow(
column(
width = 6,
lapply(
X = 1:6,
FUN = function(i) {
sliderInput(inputId = paste0("d", i), label = i, min = 0, max = 10, value = i)
}
)
),
column(
width = 6,
verbatimTextOutput(outputId = "test")
)
)
)
server <- function(input, output){
vals <- reactiveValues()
lapply(
X = 1:6,
FUN = function(i){
observeEvent(input[[paste0("d", i)]], {
vals[[paste0("slider", i)]] <- input[[paste0("d", i)]]
})
}
)
output$test <- renderPrint({
reactiveValuesToList(vals)
})
}
shinyApp(ui = ui, server = server)
Modularized Code that fails:
library(shiny)
slidersUI <- function(id){
ns <- NS(id)
tagList(
fluidRow(
column(
width = 6,
lapply(
X = 1:6,
FUN = function(i) {
sliderInput(inputId = paste0("d", i), label = i, min = 0, max = 10, value = i)
} ),
column(
width = 6,
verbatimTextOutput(outputId = "test")
)
)))
}
slidersServer <- function(input, output, session){
vals <- reactiveValues()
lapply(
X = 1:6,
FUN = function(i){
output$test2 <- renderText(paste0("this is i:", i))
observeEvent(input[[paste0("d", i)]], {
vals[[paste0("slider", i)]] <- input[[paste0("d", i)]]
})
}
)
output$test <- renderPrint({
reactiveValuesToList(vals)
})
}
library("shiny")
ui <- fluidPage(
slidersUI("TheID")
)
server <- function(input, output){
callModule(slidersServer, "TheID")
}
shinyApp(ui = ui, server = server)
Thank you!

You need to wrap your IDs in ns to get the correct namespace. Here is the corrected module ui:
slidersUI <- function(id){
ns <- NS(id)
tagList(
fluidRow(
column(
width = 6,
lapply(
X = 1:6,
FUN = function(i) {
sliderInput(inputId = ns(paste0("d", i)), label = i, min = 0, max = 10, value = i)
} ),
column(
width = 6,
verbatimTextOutput(outputId = ns("test"))
)
)))
}

Related

Module inside module shiny

I'm trying to call a module from inside a module and having some problems.
This first code is working, it displays an app with a button that creates a popup. Inside the popup is a plot and a slider input. The popup-plot is defined in it's own module.
library(shiny)
library(shinyWidgets)
uiForModal <<- function(id) {
ns <- NS(id)
tagList(
fluidRow(
plotOutput(outputId = ns("plot")),
sliderInput(
inputId =ns( "clusters"),
label = "Number of clusters",
min = 2, max = 6, value = 3, width = "100%"
)
)
)
}
serverForModal <<- function(input, output, session) {
output$plot <- renderPlot({
print(head(iris))
plot(Sepal.Width ~ Sepal.Length,
data = iris, col = Species,
pch = 20, cex = 2)
points(kmeans(iris[, 1:2], input$clusters)$centers,
pch = 4, cex = 4, lwd = 4)
})
}
ui <- fluidPage(
actionButton("showPlot", "showPlot")
)
server <- function(input, output){
observeEvent(input$showPlot, {
show_alert(
title = "Some Title",
text = tags$div(
uiForModal("test1")
),
html = TRUE,
width = "80%"
)
})
callModule(serverForModal, "test1")
}
runApp(shinyApp(ui, server))
The problem occurs when I try to put the button inside its own module. The code below is my failed attempt at this. I think the problem is something to do with the namespace. In the code below, the button calls the UI with the popup and slider, but the plot doesn't show. So I think the problem is in the server namespace for the plot. Can someone please help me out?
library(shiny)
library(shinyWidgets)
uiForModal <<- function(id) {
print(id)
ns <- NS(id)
print(ns("plot"))
tagList(
fluidRow(
plotOutput(outputId = ns("plot")),
sliderInput(
inputId =ns( "clusters"),
label = "Number of clusters",
min = 2, max = 6, value = 3, width = "100%"
)
)
)
}
serverForModal <<- function(input, output, session) {
output$plot <- renderPlot({
print(head(iris))
plot(Sepal.Width ~ Sepal.Length,
data = iris, col = Species,
pch = 20, cex = 2)
points(kmeans(iris[, 1:2], input$clusters)$centers,
pch = 4, cex = 4, lwd = 4)
})
}
uiForButton <<- function(id) {
ns <- NS(id)
tagList(
fluidRow(
actionButton(ns("showPlot"), "showPlot")
)
)
}
serverForButton <<- function(input, output, session, ns) {
observeEvent(input$showPlot, {
show_alert(
title = "Some Title",
text = tags$div(
uiForModal(ns("test2"))
),
html = TRUE,
width = "80%"
)
})
callModule(serverForModal, ns("test2"))
}
ui <- fluidPage(
uiForButton("test1")
)
server <- function(input, output){
callModule(serverForButton, "test1", NS("test1"))
}
runApp(shinyApp(ui, server))
Change
callModule(serverForModal, ns("test2"))
to
callModule(serverForModal, "test2")

Shiny Dynamic UI Resetting to Original Values

I have created a dynamic UI with the number of rows of a 'table' defined by a slider. I would like to use the numericInputs from the UI to perform further calculations. In the example below I have tried to calculate a rate from the two numeric inputs, which seems to work when new values are entered but immediately defaults back to the original starting values.
I tried using a button and changing the observe to an observeEvent to calculate the rates which worked to generate the result, but did not stop the numericInputs defaulting back to the starting values.
I have also tried to create the textboxes as a reactive and then call it to renderUI which gives the same 'broken' functionality.
output$groupings <- renderUI({ textboxes() })
textboxes <- reactive ({
I think I need to create vector or datatable to store the inputs so that I can call them later, however I've been unsuccessful so far. My working example is below:
library(shiny)
mod1UI <- function(id) {
ns <- NS(id)
tagList(
sliderInput(inputId = ns("groups"), label = "Number of Rows", min = 1, max = 6, value = 4, step = 1, width = NULL),
hr(),
fluidRow(
column(2,
strong("Speed")),
column(2,
strong("Amount")),
column(2,
strong("Run Rates"))
),
hr(),
uiOutput(ns("textboxes")),
)
}
mod1 <- function(input, output, session, data) {
ns <- session$ns
m <- reactiveValues(x=NULL)
output$textboxes <- renderUI ({
req(input$groups)
lapply(1:input$groups, function(i) {
fluidRow(
column(2,
numericInput(inputId = paste0(session$ns("speed"),i), value = 700, label = NULL, width = 80)
),
column(2,
numericInput(inputId = paste0(session$ns("amount"),i), value = 14, label = NULL, width = 80)
),
column(2,
(m$x[[i]])
)
)
})
})
observe({
lapply(1:input$groups, function(i){
m$x[[i]] <- input[[paste0("speed", i)]] * input[[paste0("amount", i)]] * 60
})
})
}
ui <- fluidPage(
fluidRow(
column(12,
mod1UI("input1"))
)
)
server <- function(input, output, session) {
y <- callModule(mod1, "input1")
}
shinyApp(ui, server)
Your problem is that you render all elements to one output, output$textboxes. Changing the input value of one of your numeric inputs leads to the calculation of a new rate, so the reactive Value m gets updated and the output$textboxes is rerendered.
Below I present you a solution where the different columns are rendered separately; you would have to play with HTML/CSS to display the values nicely. However, if you change the numbers of rows with the slider, all inputs are reset. Therefore I also added a solution where every row is a module that can be added.
library(shiny)
mod1UI <- function(id) {
ns <- NS(id)
tagList(
sliderInput(inputId = ns("groups"), label = "Number of Rows", min = 1, max = 6, value = 4, step = 1, width = NULL),
hr(),
fluidRow(
column(2,
strong("Speed")),
column(2,
strong("Amount")),
column(2,
strong("Run Rates"))
),
hr(),
fluidRow(
column(2,
uiOutput(ns("UI_speed"))),
column(2,
uiOutput(ns("UI_amount"))),
column(2,
uiOutput(ns("rates")))
)
)
}
mod1 <- function(input, output, session, data) {
ns <- session$ns
m <- reactiveValues(x=NULL)
output$UI_speed <- renderUI({
req(input$groups)
lapply(1:input$groups, function(i) {
numericInput(inputId = paste0(session$ns("speed"),i), value = 700, label = NULL, width = 80)
})
})
output$UI_amount <- renderUI({
req(input$groups)
lapply(1:input$groups, function(i) {
numericInput(inputId = paste0(session$ns("amount"),i), value = 14, label = NULL, width = 80)
})
})
output$rates <- renderUI({
req(input$groups)
text <- lapply(1:input$groups, function(i) {
m$x[[i]]
})
HTML(paste0(text, collapse = "<br>"))
})
observe({
lapply(1:input$groups, function(i){
m$x[[i]] <- input[[paste0("speed", i)]] * input[[paste0("amount", i)]] * 60
})
})
}
ui <- fluidPage(
fluidRow(
column(12,
mod1UI("input1"))
)
)
server <- function(input, output, session) {
y <- callModule(mod1, "input1")
}
shinyApp(ui, server)
Every row is a module
You get more flexibility if you have the slider in the main app and then add/remove a module. The module UI now consists of a set of inputs for Speed and Amount and an Output for the Rate. You can use insertUI and removeUI to dynamically control the amount of modules and with this the amount of displayed UI elements.
library(shiny)
mod1UI <- function(id) {
ns <- NS(id)
fluidRow(
id = id,
column(2,
uiOutput(ns("UI_speed"))),
column(2,
uiOutput(ns("UI_amount"))),
column(2,
textOutput(ns("rates")))
)
}
mod1 <- function(input, output, session, data) {
ns <- session$ns
output$UI_speed <- renderUI({
numericInput(inputId = ns("speed"), value = 700, label = NULL, width = 80)
})
output$UI_amount <- renderUI({
numericInput(inputId = ns("amount"), value = 14, label = NULL, width = 80)
})
output$rates <- renderText({
get_rate()
})
get_rate <- reactive({
input$speed * input$amount * 60
})
}
ui <- fluidPage(
fluidRow(
column(12,
sliderInput(inputId = "groups", label = "Number of Rows", min = 1, max = 6, value = 4, step = 1, width = NULL),
hr(),
fluidRow(
column(2,
strong("Speed")),
column(2,
strong("Amount")),
column(2,
strong("Run Rates"))
),
hr(),
tags$div(id = "insert_ui_here")
)
)
)
number_modules <- 4
current_id <- 1
server <- function(input, output, session) {
# generate the modules shown on startup
for (i in seq_len(number_modules)) {
# add the UI
insertUI(selector = '#insert_ui_here',
ui = mod1UI(paste0("module_", current_id)))
# add the logic
callModule(mod1, paste0("module_", current_id))
# update the id
current_id <<- current_id + 1
}
observeEvent(input$groups, {
# add modules
if (input$groups > number_modules) {
for (i in seq_len(input$groups - number_modules)) {
# add the UI
insertUI(selector = '#insert_ui_here',
ui = mod1UI(paste0("module_", current_id)))
# add the logic
callModule(mod1, paste0("module_", current_id))
# update the id
current_id <<- current_id + 1
}
} else {
# remove modules
for (i in seq_len(number_modules - input$groups)) {
# remove the UI
removeUI(selector = paste0("#module_", current_id - 1))
current_id <<- current_id - 1
}
}
# update the number of modules
number_modules <<- input$groups
}, ignoreInit = TRUE)
}
shinyApp(ui, server)

Order of repeated Shiny Modules using lapply and insertUI

I created a (for demonstration purposes reproducible) shiny app where the ui creates some Data (DataPack) (a list with two elements) by clicking the "Load"-button. Every element of this list is plotted via the module using lapply in the server function.
The app works, however, the plots come out in reverse order (DataPack$two with rnorm(n)^2 before DataPack$one with rnorm(n)) but are expected to be shown as called (lapply(names(DataPack()), function(DataSetName) {...})). How do I fix this/repeat calling modules in an exactly given order and what is the explanation for that behavior?
library(shiny)
library(TTR)
Module_ui <- function(id) {
ns <- shiny::NS(id)
shiny::uiOutput(ns("Plot"))
}
Module_Server <- function(
input, output, session,
DataPack, DataSetName, InputButton_GetData, xlim) {
AnalysedPack <- eventReactive(c(
InputButton_GetData(),
input$InputButton_ProcessData), {
message(paste("Analysed Pack", DataSetName))
AnalysedPack <-
runMean(DataPack()[[DataSetName]],
min(input$NumericInput_BW,
length(DataPack()[[DataSetName]])))
return(AnalysedPack)
})
output[['Plot']] <- renderUI({
ns <- session$ns
tags$div(
id = environment(ns)[['namespace']],
tagList(
fluidRow(
column(2, column(12, fluidRow(
numericInput(
inputId = ns("NumericInput_BW"),
label = NULL,
min = 1,
max = 100,
value = 10,
step = 1))),
fluidRow(
column(12, actionButton(
ns("InputButton_ProcessData"),
"Process", width = "100%")))),
column(10,
renderPlot({
message(paste("Base_Plot", DataSetName))
plot(DataPack()[[DataSetName]],
xlim = c(xlim()[1],
xlim()[2]))
lines(AnalysedPack(),
col = "tomato", lwd = 2)
}) ) )
)
)
})
}
ui <- fluidPage(
fluidRow(
column(
6,
column(
12,
fluidRow(h4("Data Generation")),
fluidRow(actionButton(
"InputButton_GetData", "Load", width = "100%")))),
column(
6,
column(
12,
fluidRow(h4("Update Plot")),
sliderInput(
"SliderInput_xAxis",
label = NULL,
min = 0,
max = 150,
value = c(0, 150),
animate = TRUE)
)
),
column(12, actionButton('addButton', '', icon = icon('plus')))
)
)
server <- function(input, output, session) {
DataPack <- eventReactive(
input$InputButton_GetData, {
message("DataPack")
n <- round(runif(1, min = 100, max = 500))
message(n)
DataPack <- NULL
DataPack$one <- rnorm(n)
DataPack$two <- rnorm(n)^2
updateSliderInput(
session = session,
inputId = "SliderInput_xAxis",
value = c(1, n),
min = 1,
max = n)
return(DataPack)
})
SliderInput_xAxis_rx <-
reactive(input$SliderInput_xAxis)
InputButton_GetData_rx <-
reactive(input$InputButton_GetData)
observeEvent(input$InputButton_GetData, {
lapply(names(DataPack()), function(DataSetName) {
id <- sprintf('Plot%s', DataSetName)
insertUI(
selector = "#addButton",
where = "afterEnd",
ui = Module_ui(id)
)
callModule(
Module_Server, id,
DataPack = DataPack,
DataSetName = DataSetName,
InputButton_GetData = InputButton_GetData_rx,
xlim = SliderInput_xAxis_rx)
})
})
}
shinyApp(ui, server)
This code:
insertUI(
selector = "#addButton",
where = "afterEnd",
ui = Module_ui(id)
)
inserts the UI after the element #addButton. So the first call generates, schematically:
#addButton
ui1
And the second call, as the first one, inserts after #addButton, not after ui1:
#addButton
ui2
ui1
So, reverse the names.

shiny dynamically add input fields and data without getting re-rendered

I'm trying to dynamically add new variables to my shiny app which is working but if I start editing one, the values (text and numeric) reset each time I then add an additional variable. This example works without needing a for loop using reactiveValuesToList() but when I apply it to my code, it doesn't work. Here is my working example:
library(shiny)
dist <- c("Normal", "Gamma")
ui <- shinyUI(fluidPage(
sidebarPanel(
actionButton("add_btn", "Add Textbox"),
actionButton("rm_btn", "Remove Textbox"),
textOutput("counter")
),
mainPanel(uiOutput("textbox_ui"))
))
server <- shinyServer(function(input, output, session) {
# Track the number of input boxes to render
counter <- reactiveValues(n = 0)
# Track all user inputs
AllInputs <- reactive({
x <- reactiveValuesToList(input)
})
observeEvent(input$add_btn, {counter$n <- counter$n + 1})
observeEvent(input$rm_btn, {
if (counter$n > 0) counter$n <- counter$n - 1
})
output$counter <- renderPrint(print(counter$n))
textboxes <- reactive({
n <- counter$n
if (n > 0) {
isolate({
lapply(seq_len(n), function(i) {
fluidRow(
selectInput(inputId = paste0("news", i),
label = paste0("Variable ", i),
choices = dist),
conditionalPanel(
condition = sprintf("input.%s=='Normal'", paste0("news", i)),
textInput("txt", "Text input:", paste0("var", i)),
column(width = 3, numericInput('normal_mean', 'Mean', value = '0')), column(width = 3, numericInput('normal_sd', 'Standard deviation', value = '1'))),
conditionalPanel(
condition = sprintf("input.%s=='Gamma'", paste0("news", i)),
textInput("txt", "Text input:", paste0("var", i)),
column(width = 3, numericInput('gamma_shape', 'Shape', value = '0')), column(width = 3, numericInput('gamma_scale', 'Scale', value = '1')))
)
})
})
}
})
output$textbox_ui <- renderUI({ textboxes() })
})
shinyApp(ui, server)
Now if I try and add AllInputs()[[]] to textInput it doesn't keep the text in the conditionalPanel call:
conditionalPanel(
condition = sprintf("input.%s=='Normal'", paste0("news", i)),
textInput("txt", "Text input:", AllInputs()[[paste0("var", i)]]),
column(width = 3, numericInput('normal_mean', 'Mean', value = '0')), column(width = 3, numericInput('normal_sd', 'Standard deviation', value = '1')))
I'm also not sure how to include AllInputs()[[]] to the numeric values so that they dont change.
I think the problem is because of my condition line within conditionalPanel but can't figure it out, any suggestions? thanks
You should consider using modules and insertUI / removeUI. Clicking on the buttons will not reset your changes on the inputs you already called. Here, you just have inputs so you only need to call the function add_box I created, but if you want to add outputs in the module, then you will need to use the function callModule in observeEvent. This is explained in the article I refer to.
This is not the method you suggested but it works.
library(shiny)
dist <- c("Normal", "Gamma")
add_box <- function(id){
ns <- NS(id)
tags$div(id = paste0("new_box", id),
selectInput(inputId = ns("news"),
label = paste0("Variable ", id),
choices = dist),
conditionalPanel(
condition = "input.news=='Normal'",
ns = ns,
textInput(ns("txt"), "Text input:", paste0("var", id)),
column(width = 3, numericInput(ns('normal_mean'), 'Mean', value = '0')),
column(width = 3, numericInput(ns('normal_sd'), 'Standard deviation', value = '1'))),
conditionalPanel(
condition = "input.news=='Gamma'",
ns = ns,
textInput(ns("txt"), "Text input:", paste0("var", id)),
column(width = 3, numericInput(ns('gamma_shape'), 'Shape', value = '0')),
column(width = 3, numericInput(ns('gamma_scale'), 'Scale', value = '1')))
)
}
ui <- shinyUI(fluidPage(
sidebarPanel(
actionButton("add_btn", "Add Textbox"),
actionButton("rm_btn", "Remove Textbox"),
textOutput("counter")
),
mainPanel(column(width = 12, id = "column"))
))
server <- shinyServer(function(input, output, session) {
# Track the number of input boxes to render
counter <- reactiveValues(n = 0)
# Track all user inputs
AllInputs <- reactive({
x <- reactiveValuesToList(input)
})
observeEvent(input$add_btn, {
counter$n <- counter$n + 1
insertUI(selector = "#column",
where = "beforeEnd",
ui = add_box(counter$n)
)
})
observeEvent(input$rm_btn, {
if (counter$n > 0) {
removeUI(selector = paste0("#new_box", counter$n))
counter$n <- counter$n - 1
}
})
output$counter <- renderPrint(print(counter$n))
})
shinyApp(ui, server)

Math mode in shiny table

Using withMathJax, I would like to render a table with rownames with some math expressions. Here is a basic example:
library(shiny)
ui <- fluidPage(
titlePanel("Hello Shiny!"),
mainPanel(withMathJax(),
tableOutput(outputId = "table"))
)
server <- function(input, output) {
output$table <- renderTable({
x <- rnorm(2)
y <- rnorm(2, 1)
tab <- data.frame(x = x, y = y)
withMathJax()
rownames(tab) <- c("\\(\\alpha\\)",
"\\(\\beta\\)")
tab
},
include.rownames = T,
include.colnames = T)
}
shinyApp(ui, server)
This unfortunately does not work. I also tried:
rownames(tab) <- c(withMathJax("\\(\\alpha\\)"),
withMathJax("\\(\\beta\\)"))
and
rownames(tab) <- c(paste(withMathJax("\\(\\alpha\\)")),
paste(withMathJax("\\(\\beta\\)")))
but without any success. In latter case I got alpha and beta correctly rendered, however with also
<script>if (window.MathJax) MathJax.Hub.Queue(["Typeset", MathJax.Hub]);</script>
EDIT:
The approach should preferably work even in case when table is re-rendered. Using suggestion by #Stéphane Laurent, I updated the code:
library(shiny)
ui <- fluidPage(
titlePanel("Hello Shiny!"),
mainPanel(
numericInput("mean", label = "mean", value = 1),
withMathJax(tableOutput("table"))
)
)
server <- function(input, output) {
output$table <- renderTable({
x <- rnorm(2)
y <- rnorm(2, input$mean)
tab <- data.frame(x = x, y = y)
rownames(tab) <- c("\\(\\alpha\\)",
"\\(\\beta\\)")
tab
},
include.rownames = TRUE,
include.colnames = TRUE)
}
shinyApp(ui, server)
You can use xtable to generate a LaTeX table:
library(shiny)
library(xtable)
ui <- fluidPage(
titlePanel("Hello Shiny!"),
mainPanel(
uiOutput("table")
)
)
server <- function(input, output) {
output$table <- renderUI({
x <- rnorm(2)
y <- rnorm(2, 1)
tab <- data.frame(x = x, y = y)
rownames(tab) <- c("\\alpha",
"\\beta")
LaTeXtab <- print(xtable(tab, align=rep("c", ncol(tab)+1)),
floating=FALSE, tabular.environment="array", comment=FALSE,
print.results=FALSE,
sanitize.rownames.function = function(x) x)
tagList(
withMathJax(),
HTML(paste0("$$", LaTeXtab, "$$"))
)
})
}
shinyApp(ui, server)
If you don't want to use xtable, you can do:
library(shiny)
ui <- fluidPage(
titlePanel("Hello Shiny!"),
mainPanel(
withMathJax(tableOutput("table"))
)
)
server <- function(input, output) {
output$table <- renderTable({
x <- rnorm(2)
y <- rnorm(2, 1)
tab <- data.frame(x = x, y = y)
rownames(tab) <- c("\\(\\alpha\\)",
"\\(\\beta\\)")
tab
},
include.rownames = TRUE,
include.colnames = TRUE)
}
shinyApp(ui, server)
EDIT
As noted by the OP, this doesn't work when the table is re-rendered. Here is a working solution:
ui <- fluidPage(
titlePanel("Hello Shiny!"),
mainPanel(
numericInput("mean", label = "mean", value = 1),
uiOutput("tableUI")
)
)
server <- function(input, output) {
output$table <- renderTable({
x <- rnorm(2)
y <- rnorm(2, input$mean)
tab <- data.frame(x = x, y = y)
rownames(tab) <- c("\\(\\alpha\\)",
"\\(\\beta\\)")
tab
},
include.rownames = TRUE,
include.colnames = TRUE)
output$tableUI <- renderUI({
input$mean # in order to re-render when input$mean changes
tagList(
withMathJax(),
withMathJax(tableOutput("table"))
)
})
}
EDIT 2
The previous solution works but there are some jumps, and it is not convenient because it requires to include the reactive dependencies in the renderUI. Below is a solution which uses katex instead of MathJax. No jumps, and no renderUI.
library(shiny)
js <- "
$(document).on('shiny:value', function(event) {
if(event.name === 'table'){
var matches = event.value.match(/(%%+[^%]+%%)/g);
var newvalue = event.value;
for(var i=0; i<matches.length; i++){
var code = '\\\\' + matches[i].slice(2,-2);
newvalue = newvalue.replace(matches[i], katex.renderToString(code));
}
event.value = newvalue;
}
})
"
ui <- fluidPage(
tags$head(
tags$link(rel="stylesheet", href="https://cdn.jsdelivr.net/npm/katex#0.10.0-beta/dist/katex.min.css", integrity="sha384-9tPv11A+glH/on/wEu99NVwDPwkMQESOocs/ZGXPoIiLE8MU/qkqUcZ3zzL+6DuH", crossorigin="anonymous"),
tags$script(src="https://cdn.jsdelivr.net/npm/katex#0.10.0-beta/dist/katex.min.js", integrity="sha384-U8Vrjwb8fuHMt6ewaCy8uqeUXv4oitYACKdB0VziCerzt011iQ/0TqlSlv8MReCm", crossorigin="anonymous"),
tags$script(HTML(js))
),
titlePanel("Hello Shiny!"),
mainPanel(
numericInput("mean", "Enter mean", value = 1),
tableOutput("table")
)
)
server <- function(input, output) {
output$table <- renderTable({
x <- rnorm(2)
y <- rnorm(2, input$mean)
tab <- data.frame(x = x, y = y, z = c("hello", "%%gamma%%%%delta%%"))
rownames(tab) <- c("%%alpha%%", "%%beta%%")
tab
}, rownames = TRUE)
}
shinyApp(ui, server)
Every occurrence like %%string%% is replaced by \\string and then rendered in math.

Resources