I have created a shiny app to show a correlation heatmap for a large dataset. When the heatmap tiles are pressed, the corresponding scatterplots are displayed.
However, I need to make several of these apps and this exceeds my limit of publishing on shinyapps.io. My company is unwilling to upgrade to a paying plan. I have tried using alternative methods to publish the app such as RInno, to no avail (I think the app is too complex?).
If someone could please tell me how I could produce the same with plotly alone and NOT with shiny, I would be forever grateful. I believe something like crosstalk might be the path to take in order to link the heat map tiles to the scatter plots?
Thank you
Example from here.
library(plotly)
library(shiny)
# compute a correlation matrix
correlation <- round(cor(mtcars), 3)
nms <- names(mtcars)
ui <- fluidPage(
mainPanel(
plotlyOutput("heat"),
plotlyOutput("scatterplot")
),
verbatimTextOutput("selection")
)
server <- function(input, output, session) {
output$heat <- renderPlotly({
plot_ly(x = nms, y = nms, z = correlation,
key = correlation, type = "heatmap", source = "heatplot") %>%
layout(xaxis = list(title = ""),
yaxis = list(title = ""))
})
output$selection <- renderPrint({
s <- event_data("plotly_click")
if (length(s) == 0) {
"Click on a cell in the heatmap to display a scatterplot"
} else {
cat("You selected: \n\n")
as.list(s)
}
})
output$scatterplot <- renderPlotly({
s <- event_data("plotly_click", source = "heatplot")
if (length(s)) {
vars <- c(s[["x"]], s[["y"]])
d <- setNames(mtcars[vars], c("x", "y"))
yhat <- fitted(lm(y ~ x, data = d))
plot_ly(d, x = ~x) %>%
add_markers(y = ~y) %>%
add_lines(y = ~yhat) %>%
layout(xaxis = list(title = s[["x"]]),
yaxis = list(title = s[["y"]]),
showlegend = FALSE)
} else {
plotly_empty()
}
})
}
shinyApp(ui, server)
The best answer would likely be to use crosstalk in conjunction with flexdashboard https://rmarkdown.rstudio.com/flexdashboard/.
A live example of using both be found here: http://rstudio-pubs-static.s3.amazonaws.com/209203_02f14fea3274448bbbf8d04c99c6051b.html.
The end result is just an html page which is easy to share and use. Based on your example you shouldn't need shiny and it you should be able to use crosstalk for this use case. You'd need to step outside of R to get that functionality otherwise. Best of luck!
Related
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.
I am trying to make a shiny app in R using Plotly plots.
I am trying to create zoom functionality in the backend of a shiny app, responsive to a click event on a plotly graph (i.e. when a point is clicked, zoom in on that point). However, the only solution I have found so far is to completely re-layout the plotly object with new view ranges.
For large plots, this is incredibly slow because shiny re-renders the entire plot, and is much slower than plotly's built-in zoom functionality on the front end (the plotly user interface buttons at the top right) when the number of data points is large. Is there a way to use the plotly zoom functionality in the backend so that the whole plotly object doesn't have to re-render to zoom?
Example:
library(plotly)
library(shiny)
ui <- fluidPage(
plotlyOutput("scatter")
)
server <- function(input, output) {
zoom_vals = reactiveValues(xrange=NA,yrange=NA)
# Plot scatter plot
output$scatter <- renderPlotly({
data <- data.frame(x=sample.int(1000,100), y = sample.int(1000,100))
x_axis = list(range = zoom_vals$xrange)
y_axis = list(range = zoom_vals$yrange)
plot_ly(data, x = ~x, y = ~y) %>% layout(xaxis=x_axis, yaxis=y_axis)
})
# Catch plot click
observeEvent(event_data("plotly_click"),{
d<-event_data("plotly_click")
zoom_vals$xrange <- c((d$x- 1),(d$x+ 1))
zoom_vals$yrange <- c((d$y- 1),(d$y- 1))
})
}
shinyApp(ui, server)
You can modify (no re-rendering) an exisiting plotly object in shiny via plotlyProxyInvoke.
To change the axis range we'll need the relayout method:
library(plotly)
library(shiny)
ui <- fluidPage(plotlyOutput("scatter"))
server <- function(input, output) {
# Plot scatter plot
output$scatter <- renderPlotly({
data <- data.frame(x = sample.int(1000, 100), y = sample.int(1000, 100))
plot_ly(data, x = ~ x, y = ~ y, type = "scatter", mode = "markers")
})
scatterProxy <- plotlyProxy("scatter")
# Catch plot click
observeEvent(event_data("plotly_click"), {
d <- event_data("plotly_click")
xrange <- c((d$x - 100), (d$x + 100))
yrange <- c((d$y - 100), (d$y + 100))
plotlyProxyInvoke(scatterProxy, "relayout", list(xaxis = list(range = xrange), yaxis = list(range = yrange)))
})
}
shinyApp(ui, server)
I'm building a Shiny app with a plot_ly scatter plot. I'm using a SharedData object (from the crosstalk package) to share information between the plot and a datatable (from DT).
The problem is when you click a point in the plot it dims the color of all of the other points and adds an entry to the legend for the selected point, and once this happens there doesn't seem to be a way to undo it. I would like to disable these visual changes but still be able to detect plot clicks.
This issue does not occur if I just use a reactive data.frame instead of a SharedData object in the data parameter of the plot_ly call, but then the event_data from the plot doesn't have enough information to select a row in the datatable. (The x and y point coordinates are floating point numeric, so matching by coordinates against the data can have unexpected results.)
Here's a demo using mtcars:
library(shiny)
library(DT)
library(plotly)
library(data.table)
library(crosstalk)
### UI function ---------
ui <- fluidPage(
fluidRow(
plotlyOutput('my_graph', height = '400px')
),
fluidRow(
dataTableOutput('my_table')
)
)
### Server function -------
server <- function(input, output, session) {
### SharedData object ----
filtered_data <- reactive({
data.table(mtcars, keep.rownames = TRUE)
})
shared_data <- reactive({
req(filtered_data())
SharedData$new(filtered_data(), ~rn)
})
### my_graph ----
output$my_graph <- renderPlotly({
p <- plot_ly(shared_data(),
x = ~disp,
y = ~mpg,
color = ~factor(carb),
source = 'm')
p
})
### my_table ---------
output$my_table <- renderDataTable({
datatable(shared_data()$data(),
selection = 'single')
})
observe({
click_detect = plotly::event_data('plotly_hover', source = 'm')
str(click_detect)
dataTableProxy('my_table') %>%
selectRows(match(click_detect$key, shared_data()$data()$rn))
})
}
shinyApp(ui, server)
Why that happens beats me but I can see two possible workarounds.
Force Plotly to set the opacity of all markers to 1.
if (click_detect$curveNumber != 0) {
output$my_graph <- renderPlotly({
p <- plot_ly(shared_data(),
x = ~disp,
y = ~mpg,
color = ~factor(carb),
source = 'm',
marker = list(opacity = 1))
p
})
}
Drawback: The graph flickers.
Change your filterRows statement. I don't know your data but for mtcars you can filter by carb (via curveNumber) and then via pointNumber.
dataTableProxy('my_table') %>% selectRows(
which(mtcars$carb == sort(unique(mtcars$carb))[[click_detect$curveNumber + 1]])[[click_detect$pointNumber + 1]])
I came across the same issue and found an approach using the highlight function. https://www.rdocumentation.org/packages/plotly/versions/4.8.0/topics/highlight
The default setting for non-selected points is opacity=0.2 . This is why the other points dim. So all you need to do is add a pipe %>% highlight(opacityDim = 1)
Use any number between 0 and 1 to reduce the opacity of non-selected traces. If you want to disable it completely, then do 1. Otherwise you can try 0.5 and it worked for me.
In your case, you may try
output$my_graph <- renderPlotly({
p <- plot_ly(shared_data(),
x = ~disp,
y = ~mpg,
color = ~factor(carb),
source = 'm')
p <- highlight(p, opacityDim = 1)
p
})
Hopefully, it helps for whoever need it later.
I'm trying to get the accessibility module to work with highcharter, but I can't seem to figure out how to do it.
I'm trying to integrate it into a shiny app, so here's a very minimal example of where I'm at so far:
library(highcharter)
library(shiny)
x <- c("a", "b", "c", "d")
y <- c(1:4)
z <- c(4:7)
data <- data.frame(x,y,z)
ui <- fluidPage(
fluidRow(
highchartOutput("chart")
)
)
server <- function(input, output, session){
output$chart <- renderHighchart({
hchart(data, "bubble", hcaes(x = x, y = y, size = z))%>%
hc_add_dependency(name = "modules/accessibility.js")
})
}
shinyApp(ui = ui, server = server)
But it still is not allowing me to tab through the bubbles.
Edit:
I can't fix the tab option yet, I will check. sorry.
Previous answer
This was a error from highcharter and it was fixed in the development version. Update and test with:
source("https://install-github.me/jbkunst/highcharter")
Now the accessibility pluging is included by default and you can configure using the hc_accessibility function with options described in the highcharts API documentation.
I tested using the NVDA.
highchart() %>%
hc_add_series(data = 1:3, type = "column") %>%
hc_accessibility(
enabled = TRUE,
keyboardNavigation = list(enabled = FALSE)
)
Following the example at: https://plot.ly/r/shinyapp-linked-click/ I was able to in a blank shiny project get this working (correlation matrix linked to a scatter graph). However, when I do the same in a shiny module the event_data based click action doesnt seem to work (the scatter remains blank no mater what happens, seems like the click is not connecting).
My reproducible example is below, any ideas or solutions would be much appreciated.
library(plotly)
#### Define Modules ####
correlation_matrix_shinyUI <- function(id) {
ns <- NS(id)
mainPanel(
plotlyOutput(ns("corr_matrix"), height = '650px'),
plotlyOutput(ns("scatterplot"), height = '550px')
)
}
correlation_matrix_shiny <- function(input, output, session) {
data_df <- reactive({
mtcars
})
corr_data <- reactive({
if (is.null(data_df()))
return()
corr_data <- cor(data_df())
diag(corr_data) <- NA
corr_data <- round(corr_data, 4)
corr_data
})
corr_names <- reactive({
if (is.null(data_df()))
return()
corr_names <- colnames(data_df())
corr_names
})
output$corr_matrix <- renderPlotly({
if (is.null(corr_names()))
return()
if (is.null(corr_data()))
return()
g <- plot_ly(x = corr_names(), y = corr_names(), z = corr_data(),
key = corr_data(), type = "heatmap", source = "CORR_MATRIX", zmax = 1, zmin = -1)
g
})
output$scatterplot <- renderPlotly({
if (is.null(data_df()))
return()
data_use <- data_df()
s <- event_data("plotly_click", source = "CORR_MATRIX")
if (length(s)) {
vars <- c(s[["x"]], s[["y"]])
d <- setNames(data_use[vars], c("x", "y"))
yhat <- fitted(lm(y ~ x, data = d))
plot_ly(d, x = x, y = y, mode = "markers") %>%
plotly::add_trace(x = x, y = yhat, mode = "lines") %>%
plotly::layout(xaxis = list(title = s[["x"]]),
yaxis = list(title = s[["y"]]),
showlegend = FALSE)
} else {
plot_ly()
}
})
}
############ End Module Definition ######
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
),
correlation_matrix_shinyUI(id = "cor_module")
)
)
server <- function(input, output, session) {
callModule(correlation_matrix_shiny, id = "cor_module")
}
shinyApp(ui = ui, server = server)
Your question really is interesting. I will answer with some text passages from the shiny modules page.
Foremost, your problem is a scoping issue. In more detail:
[...] input, output, and session cannot be used to access inputs/outputs that are outside of the namespace, nor can they directly access reactive expressions and reactive values from elsewhere in the application [...]
In your module, you are trying to access the plotly-owned and therefore server-level variable event_data that is used to store click (or other) events. The plots react normal, as you could see if you'd add
observe({print(event_data("plotly_click", source = "CORR_MATRIX"))})
inside your server function (and outside of the module). But this kind of input was not defined directly within the correlation_matrix_shinyUI namespace and so it remains inaccessible.
These restrictions are by design, and they are important. The goal is not to prevent modules from interacting with their containing apps, but rather, to make these interactions explicit.
This is well meant, but in your case, you weren't really given the chance to assign a name to this variable, since plotly handles everything under its cover. Luckily, there is a way:
If a module needs to access an input that isn’t part of the module, the containing app should pass the input value wrapped in a reactive expression (i.e. reactive(...)):
callModule(myModule, "myModule1", reactive(input$checkbox1))
This of course goes a bit contrary to the whole modularization...
So, the way this can be fixed is to fetch the click event outside of the module and then send it as extra input to the callModule function. This part in the code may look a bit redundant, but I found this to be the only way it worked.
Well, the rest can be best explained by the code itself. Changes have only been made to the server function and inside the correlation_matrix_shiny, where the variable s is defined.
I hope this helps!
Best regards
Code:
library(plotly)
#### Define Modules ####
correlation_matrix_shinyUI <- function(id) {
ns <- NS(id)
mainPanel(
plotlyOutput(ns("corr_matrix"), height = '650px'),
plotlyOutput(ns("scatterplot"), height = '550px')
)
}
correlation_matrix_shiny <- function(input, output, session, plotlyEvent) {
data_df <- reactive({
mtcars
})
corr_data <- reactive({
if (is.null(data_df()))
return()
corr_data <- cor(data_df())
diag(corr_data) <- NA
corr_data <- round(corr_data, 4)
corr_data
})
corr_names <- reactive({
if (is.null(data_df()))
return()
corr_names <- colnames(data_df())
corr_names
})
output$corr_matrix <- renderPlotly({
if (is.null(corr_names()))
return()
if (is.null(corr_data()))
return()
g <- plot_ly(x = corr_names(), y = corr_names(), z = corr_data(),
key = corr_data(), type = "heatmap", source = "CORR_MATRIX", zmax = 1, zmin = -1)
g
})
output$scatterplot <- renderPlotly({
if (is.null(data_df()))
return()
data_use <- data_df()
s <- plotlyEvent()
if (length(s)) {
vars <- c(s[["x"]], s[["y"]])
d <- setNames(data_use[vars], c("x", "y"))
yhat <- fitted(lm(y ~ x, data = d))
plot_ly(d, x = x, y = y, mode = "markers") %>%
plotly::add_trace(x = x, y = yhat, mode = "lines") %>%
plotly::layout(xaxis = list(title = s[["x"]]),
yaxis = list(title = s[["y"]]),
showlegend = FALSE)
} else {
plot_ly()
}
})
}
############ End Module Definition ######
ui <- shinyUI(fluidPage(
sidebarLayout(
sidebarPanel(
),
correlation_matrix_shinyUI(id = "cor_module")
)
))
server <- function(input, output, session) {
plotlyEvent <- reactive(event_data("plotly_click", source = "CORR_MATRIX"))
callModule(correlation_matrix_shiny, id = "cor_module", reactive(plotlyEvent()))
}
shinyApp(ui = ui, server = server)