A question about R shiny ggvis plot tooltips and reactivity - r

I have an app with a global function that makes a ggvis plot. I use a global function because I recreate the plot many times with slightly different settings. I need the tooltip to respond to some user inputs, but even when reactivity forces a recalculation of the plot, the tooltip does not seem to get recalculated. Here's an example of the issue:
library(shiny)
library(ggvis)
df = mtcars
df$name = row.names(df)
make_plot <- function(data, slider){
hover_values <- function(x) {
gear = data$gear[data$name == x$name]
paste("gear times slider is:", gear*slider)
}
data %>%
ggvis(~mpg, ~hp, key := ~name) %>%
layer_points() %>%
add_tooltip(hover_values, "hover")
}
ui <- fluidPage(
sliderInput("slider", label = "slider:", min = 1, max = 10, value = 1),
ggvisOutput("plot")
)
server <- function(input, output, session) {
output$plot <- eventReactive(input$slider, {
print("remaking plot...")
make_plot(df, input$slider)
}) %>% bind_shiny("plot")
}
shinyApp(ui = ui, server = server)
You can see that the tooltips are calculated using the slider input, but when you run the app, the tooltips do not change when the slider is changed. Can someone explain what is wrong with my approach or what I need to do to get this working?

MrFlick pointed out that I needed to move where bind_shiny() is used. The corrected code for the server function is:
server <- function(input, output, session) {
output$plot <- eventReactive(input$slider, {
print("remaking plot...")
make_plot(df, input$slider) %>%
bind_shiny("plot")
})
}

Related

In R/Shiny best alternative to htmlOutput()/renderUI()?

Hello This chart bellow is a part of my dashboard but its making the dashboard have a bad performance.
It takes time do update.
Is there any alternative to the functions htmlOutput()+ renderUI() ?
How can I improve the performance? Is it the case to create and generate the charts outside the server?
library(shiny)
library(highcharter)
library(tidyverse)
df <-tibble(months = month.abb, value = ts(cumsum(rnorm(100)))[1:12] )
ui <- fluidPage(
h1("Highcharts"),
htmlOutput('chart_grid')
)
server <- function(input, output, session) {
output$chart_grid<- renderUI({
charts <- lapply(1:9, function(x) {
highchart() %>%
hc_add_series(type = 'spline',data = df, hcaes(x = months,y = value))%>%
hc_xAxis(categories = df$months)
})
hw_grid(charts, rowheight = 300,add_htmlgrid_css = TRUE)%>%
htmltools::browsable()
})
}
shinyApp(ui, server)
TL;DR highcharts JS boost module for many data points, static HTML for a plot if there is one or a few plot versions, own grid and highcharter::highchartOutput
Static HTML
Strategy is to create the needed plot before the shiny app is started, but we have to assume that the plot have one or a few versions as a huge collection of html's could be a wrong direction too.
Usually we are using htmlwidgets::saveWidget to save any shiny widgets so we could get its html representation. As the hw_grid is not returning a shiny widgets I save it as a regular html but we have to take care of the dependencies.
Here I use the hchart (not highchart) as it persists labels on the plots.
You do not have to leave the code for html creation in the app.R file. But in my example you need still leave the dependencies list.
Then you could add dependencies in the DOM head with htmltools::renderDependencies(htmltools::resolveDependencies(DEPS)).
Remember that when you deploy the shiny app you have to add static files with e.g. addResourcePath https://shiny.rstudio.com/reference/shiny/1.0.2/addResourcePath.html
library(shiny)
library(highcharter)
library(tidyverse)
library(magrittr)
df <- tibble::tibble(months = month.abb, value = ts(cumsum(rnorm(100)))[1:12])
charts <- lapply(1:9, function(x) {
hchart(df, type = "spline", hcaes(x = months, y = value)) %>%
hc_xAxis(categories = df$months) %>%
hc_boost(enabled = TRUE)
})
hc_test <- hw_grid(charts, rowheight = 300, add_htmlgrid_css = TRUE, browsable = TRUE)
# get HTML of the plot
# we could not use htmlwidgets::saveWidget as it is not a widget
writeLines(as.character(hc_test), "hc_test.html")
# get the dependencies of the plot
hc_deps <- htmltools::findDependencies(hc_test)
# unique dependencies in the HTML format, could be added in the head
# htmltools::renderDependencies(htmltools::resolveDependencies(hc_deps))
ui <- fluidPage(
h1("Highcharts"),
htmlOutput("chart_grid")
)
server <- function(input, output, session) {
output$chart_grid <- renderUI({
# Load HTML with proper dependencies
htmltools::attachDependencies(
shiny::HTML(readLines("hc_test.html")),
htmltools::resolveDependencies(hc_deps)
)
})
}
shinyApp(ui, server)
Boost module
The source highcharts JS library offers a boost module which could boost the performance. From the perspective of R it is as easy as adding hc_boost(enabled = TRUE) to your pipeline.
https://www.highcharts.com/docs/advanced-chart-features/boost-module
As I understand the hc_boost could improve performance only in specific scenarios and for the cost of losing some functionality. I did not test if it truly works as expected.
library(shiny)
library(highcharter)
library(tidyverse)
df <- tibble(months = month.abb, value = ts(cumsum(rnorm(100)))[1:12])
ui <- fluidPage(
h1("Highcharts"),
htmlOutput("chart_grid")
)
server <- function(input, output, session) {
output$chart_grid <- renderUI({
charts <- lapply(1:9, function(x) {
highchart() %>%
hc_add_series(type = "spline", data = df, hcaes(x = months, y = value)) %>%
hc_xAxis(categories = df$months) %>%
hc_boost(enabled = TRUE)
})
hw_grid(charts, rowheight = 300, add_htmlgrid_css = TRUE, browsable = TRUE)
})
}
shinyApp(ui, server)
Highcharts and own grid
library(shiny)
library(highcharter)
library(tidyverse)
df <- tibble(months = month.abb, value = ts(cumsum(rnorm(100)))[1:12])
ui <- fluidPage(
h1("Highcharts"),
shiny::tags$div(
style = "display:flex;flex-wrap: wrap;",
lapply(1:9, function(x) shiny::tags$div(
style = "flex: 1 1 30%;",
highcharter::highchartOutput(sprintf("hplot%s", x)))
)
)
)
server <- function(input, output, session) {
charts <- lapply(1:9, function(x) {
output[[sprintf("hplot%s", x)]] <- highcharter::renderHighchart(
highchart(width = 600) %>%
hc_add_series(type = "spline", data = df, hcaes(x = months, y = value)) %>%
hc_xAxis(categories = df$months)
)
})
}
shinyApp(ui, server)
BTW.
My personal opinion is that the highcharter package need much work regarding the code quality and documentation, e.g. hw_grid does not even have documented the return value.

Plotly click event with table shall disappear again when clicked somewhere in the chart

Problem: The following code produces a plotly bar chart. If you click on the bars data will be shown. If I am not interested anymore in the underlying table I want to click somewhere (i.e. not the bar) on the chart and the table shall "disappear".
Any idea how to do that? I already tried to add the is.null if-condition which is not working due to the req() (and req() is needed). Many thanks!
library(shiny)
library(plotly)
library(DT)
ui <- fluidPage(
plotlyOutput("plot"),
dataTableOutput("table")
)
server <- function(input, output) {
output$plot <- renderPlotly({
mtcars %>%
group_by(cyl) %>%
summarise(m = mean(mpg)) %>%
plot_ly(., x = ~cyl,
y = ~m, source = "test_plot") %>%
add_bars()
})
observe({
p <- event_data("plotly_click", source = "test_plot")
req(p)
output$table <- renderDataTable({
if (is.null(p)){
return(NULL)
}
mtcars
})
})
}
# Run the application
shinyApp(ui = ui, server = server)

Creating hover info box and reactive dropdown menu in Shiny

This is my first Shiny app, and I just got the basics working to where it allows the user to select from a dropdown menu of clients, then a dropdown menu of test codes to receive a plot of the results for the selected test.
I'd like the second dropdown menu to be updated with the available test codes for that client (all are not present for each client). Also, I would like to be able to hover over the point in the plot and receive more information from the row in the original dataframe.
I've looked into tooltips and the nearPoints() function, but I'm not sure if these can be used on this data since it is manipulated. I'm not sure if at this point it would be easier to import the data in a different way (it will ultimately need to accept either excel files or .csv). Thanks for any help that you would be able to provide, please let me know if there is any other supporting info I can give.
Here is my code:
library(shiny)
library(scales)
library(ggplot2)
labData <-
read.table("MockNLData.csv",
header=TRUE, sep=",")
#convert '<10' and '<20' results
labData$ModResult <- labData$Result
levels(labData$ModResult)[levels(labData$ModResult)=="<10"]
<- "0"
levels(labData$ModResult)[levels(labData$ModResult)=="<20"]
<- "0"
#convert results to scientific notation
SciNotResult <-
formatC(as.numeric(as.character(labData$ModResult)),
format="e", digits=2)
ui <- fluidPage(
headerPanel("Dilution History"),
sidebarLayout(
sidebarPanel(
selectInput(inputId="client", label="Select Client
Name", choices=levels(labData$Client.Name)
),
selectInput(inputId="test", label="Select Test Code",
choices=levels(labData$Analysis))
),
mainPanel(
plotOutput("line", hover="plot_hov"),
verbatimTextOutput("info"))
)
)
server <- function(input, output) {
#selected client into data frame
selDF <- reactive({labData[labData[,1]==input$client,]
})
#selected test code into data frame
subsetDF <- reactive({selDF()[selDF()[,5]==input$test,]
})
#points to be plotted
points <-
reactive({as.numeric(levels(subsetDF()$ModResult))
[subsetDF()$ModResult]
})
#plot
output$line <- renderPlot({
qplot(seq_along(points()), points(), xlab ="Index",
ylab ="Result")
})
#hover information
output$info <- renderText({
paste0("x=", input$plot_hov$x, "\ny=",
input$plot_hov$y)
})
}
shinyApp(ui = ui, server = server)
Here is what the data looks like:
MockNLData.csv
EDIT: I figured out updating the menu with updateSelectInput()
In the future, make sure you share a reproducible example :)
Since your code is not reproducible please find below something you can understand and adapt to your case.
On your first question, if I understand correctly, you want to programatically generate a dropdown (selectInput) which is perfectly do-able. *Inputs are, in essence, just HTML content which you can dynamically generate, just like your plots. You do so with uiOutput (in your ui) and renderUI in your server.
library(shiny)
ui <- fluidPage(
selectInput("dataset", "Select a dataset", choices = c("cars", "mtcars")),
uiOutput("column"), # dynamic column selector
verbatimTextOutput("selected_column")
)
server <- function(input, output, session){
data <- reactive({
if(input$dataset == "cars")
return(cars)
else
return(mtcars)
})
output$column <- renderUI({
# build your selectInput as you normally would
selectInput("column_selector", "Select a column", choices = colnames(data()))
})
output$selected_column <- renderPrint({
# use input$column_selector!
print(input$column_selector)
})
}
shinyApp(ui, server)
On your second question, what you want is an interactive plot. There are numerous packages that will let you do that in R and Shiny. Below are some examples, by no means a comprehensive list:
plotly which will also let you make your ggplot2 charts interactive
highcharter another great, well tested library
echarts4r ECharts for R.
billboarder billboard.js for R and Shiny
Below is an example using highcharter. They all follow the same principle within Shiny, an *Output function coupled with a render* function.
library(shiny)
library(highcharter)
ui <- fluidPage(
highchartOutput("chart")
)
server <- function(input, output, session){
output$chart <- renderHighchart({
hchart(mpg, "scatter", hcaes(x = displ, y = hwy, group = class))
})
}
shinyApp(ui, server)
EDIT
Following your question on the flashing error. You need to require (req) the required input. When launching the app below the error will flash, uncomment the req(input$y) line and it'll go away.
library(shiny)
ui <- fluidPage(
uiOutput("sel"),
plotOutput("plot")
)
server <- function(input, output){
output$sel <- renderUI({
numericInput("y", "N:", value = 200, min = 5, max = 1000, step = 100)
})
output$plot <- renderPlot({
# req(input$y)
hist(runif(input$y, 1, 10))
})
}
shinyApp(ui, server)
In essence, since your plot relies on a dynamically generating input for a fraction of second that input is not available as it is being rendered, using req prevents that.
What I understand from your problem above are:
You want to make next dropdown menu based on what the user have chosen from previous dropdown menu.
When the mouse over the point on the plot, it will show row value.
So, here i will give you reproducible example and i hope it is useful for you.
In this example I use Rabbit dataset from library MASS.
To filter data for next dropdown menu, I use filter from library
dplyr (See line 30).
I use reactive expression to manage next dropdown menu (See line
29).
I use nearPoints() to manage hover point (See line 55).
library(shiny)
library(MASS)
library(dplyr)
library(ggplot2)
ui <- fluidPage(
titlePanel("Rabbit dataset from MASS library"),
fluidRow(
column(4, selectInput("var",
"Animal:",
unique(sort(Rabbit$Animal)))),
column(4, uiOutput("selected_var")),
column(4, uiOutput("selected_var1")),
column(12, plotOutput("selected_var2", hover = "plot_hover")),
column(12, verbatimTextOutput("info"))
)
)
server <- function(input, output) {
###FILTER NEXT DROPDOWN MENU BASED ON PREVIOUS SELECTED BY USER
dataset3 <- reactive({
unique(Rabbit %>% filter(Animal == input$var) %>% select(Treatment))
})
output$selected_var <- renderUI({
selectInput("var1", "Treatment:", c(dataset3()))
})
dataset4 <- reactive({
Rabbit %>% filter(Animal == input$var) %>% filter(Treatment == input$var1) %>% select(Run)
})
output$selected_var1 <- renderUI({
selectInput("var2", "Run:", c(dataset4()))
})
####
output$selected_var2 <- renderPlot({
ggplot(Rabbit %>% filter(Animal == input$var) %>% filter(Treatment == input$var1) %>% filter(Run == input$var2), aes(x = BPchange, y = Dose)) + geom_point()
})
###HOVER POINT USING nearPoints()
output$info <- renderPrint({
nearPoints(Rabbit %>% filter(Animal == input$var) %>% filter(Treatment == input$var1) %>% filter(Run == input$var2), input$plot_hover)
})
}
shinyApp(ui = ui, server = server)

Shiny error while using nearPoints in renderPlotly click event

I would like to fetch nearPoints using the data from a click event.
I have found the below snippet from the Shiny webpage and it works fine as expected.
output$plot <- renderPlot({
d <- data()
plot(d$speed, d$dist)
})
output$plot_clickedpoints <- renderPrint({
# For base graphics, we need to specify columns, though for ggplot2,
# it's usually not necessary.
res <- nearPoints(data(), input$plot_click, "speed", "dist")
if (nrow(res) == 0)
return()
res
})
I tried to mimic the above the approach to identify the nearPoints in the Plotly plots using the click event data. However, it did not work.
output$plot <- renderPlotly({
d <- data()
plot(d$speed, d$dist)
})
output$plot_clickedpoints <- renderPrint({
# For base graphics, we need to specify columns, though for ggplot2,
# it's usually not necessary.
res <- nearPoints(data(), event_data("plotly_click"), "speed", "dist")
if (nrow(res) == 0)
return()
res
})
Any idea on how to pass the coordinate information to the plotly plot?
I am not sure on how to do this with the nearPoints function, but is using that function really necessary? You could find the points that are within a threshold of the clicked point as well with the following code:
library(shiny)
library(plotly)
library(DT)
threshold_mpg = 3
threshold_cyl = 1
shinyApp(
ui <- shinyUI(
fluidPage(
plotlyOutput("plot"),
DT::dataTableOutput("table")
)
),
function(input,output){
data <- reactive({
mtcars
})
output$plot <- renderPlotly({
d <- data()
plot_ly(d, x= ~mpg, y=~cyl, mode = "markers", type = "scatter", source="mysource")
})
output$table<- DT::renderDataTable({
event.data <- event_data("plotly_click", source = "mysource")
print(event.data)
if(is.null(event.data)) { return(NULL)}
# A simple alternative for the nearPoints function
result <- data()[abs(data()$mpg-event.data$x)<=threshold_mpg & abs(data()$cyl-event.data$y)<=threshold_cyl, ]
DT::datatable(result)
})
}
)
Hope this helps.
The "plotly_selected" plotly.js event returns more information than event_data("plotly_selected") actually gives you, including coordinate information (this was arguably a design mistake made by event_data() that's too late to change). Fortunately, if you know a bit of JavaScript, know how to listen to plotly select events, and how to send data from client to a shiny server, you can do something like this to access that info:
library(shiny)
library(plotly)
library(htmlwidgets)
ui <- fluidPage(
plotlyOutput("p"),
verbatimTextOutput("info")
)
server <- function(input, output, session, ...) {
output$p <- renderPlotly({
plot_ly(x = 1:10, y = 1:10) %>%
layout(dragmode = "select") %>%
onRender(
"function(el, x) {
var gd = document.getElementById(el.id);
gd.on('plotly_selected', function(d) {
// beware, sometimes this event fires objects that can't be seralized
console.log(d);
Shiny.onInputChange('my-select-event', d.range)
})
}")
})
output$info <- renderPrint({
print(session$rootScope()$input[["my-select-event"]])
})
}
shinyApp(ui, server)
Using the coordinate information you could write a function that works in a similar way to nearPoints().

Shiny in R: How to properly use reactive, observe and renderUI?

I have a problem with my code. Every time I click a button my plot (built with ggvis) is showing up but vanishes immediately. Since my code is very long, the following code reproduces my problem. I want to reuse the reactive data frame test0 in my render function and I guess this is exactly what causes my problem. But this is essential to me. The three steps (reactive, observe, render) are the same than in my code. I would very much appreciate your help!
server.R
library(shiny)
library(ggvis)
library(dplyr)
data(mtcars)
shinyServer(function(input, output) {
test0 <- reactive({
df <- mtcars %>% select(mpg, wt)
(input$NextCounter + 1)*df
})
observe({
df <- test0()
if (!is.null(df)) {
ggvis(df, x = ~wt, y = ~mpg) %>% bind_shiny("plotggvis")
}
})
output$test1 <- renderUI({
df <- test0()
ggvisOutput("plotggvis")
})
})
ui.R
library(shiny)
shinyUI(fluidPage(
sidebarLayout(
sidebarPanel(
actionButton("NextCounter", "Next")
),
mainPanel(
uiOutput("test1")
)
)
))
this one working for me
library(shiny)
library(ggvis)
library(dplyr)
ui <- shinyUI(fluidPage(
sidebarLayout(
sidebarPanel(
actionButton("NextCounter", "Next")
),
mainPanel(
ggvisOutput("plotggvis")
)
)
))
server <- shinyServer(function(input, output) {
data(mtcars)
test0 <- reactive({
df <- mtcars %>% select(mpg, wt)
(input$NextCounter + 1)*df
})
my_graph <- reactive({
df <- test0()
ggvis(df, x = ~wt, y = ~mpg)
})
my_graph %>% bind_shiny("plotggvis")
})
})
shinyApp(ui = ui, server = server)
You don't need to have a ggvisOutput in the UI to solve your problem. Actually the problem in your code is having the bind_shiny function inside an observer that will be executed again every time your test0 data changes. It is expected to bind your ggvis only once, otherwise it will have that behavior of showing up and vanishes immediately. Also, one great feature of ggvis is having a nice transitions when data is changing, so you don't need to create a ggvis object every time your data changes, just make sure that you only bind that ggvis object once in your UI.
Below is a modified version of your code to solve your problem and show the animated transition of data.
library(shiny)
library(ggvis)
library(dplyr)
data(mtcars)
ui <- fluidPage(fluidPage(
sidebarLayout(
sidebarPanel(
actionButton("NextCounter", "Next")
),
mainPanel(
uiOutput("test1")
)
)
))
server <- function(input, output) {
test0 <- reactive({
input$NextCounter
df <- mtcars %>% select(mpg, wt)
df[sample(nrow(df),nrow(df)/2), ]
})
output$test1 <- renderUI({
# bind the ggvis only once
test0 %>% ggvis(x = ~wt, y = ~mpg) %>% bind_shiny("plotggvis")
ggvisOutput("plotggvis")
})
}
shinyApp(ui, server)
You can also modify some ggvis parameters using input widgets by putting the ggvis inside of a reactive expression.

Resources