Reactive resizing of plotly graph in R Shiny - r

My problem here is, I think, an edge case for this type of problem. With that said, I've got some reproducible code here that might help a lot of people with similar issues they have, particularly the issue of changing a ggplot's plot height in RShiny (which is default set to 400px).
Here is a demo of a shiny app where the height of the ggplot is set equal to the width of the ggplot:
mydf = data.frame(x = 1:5, y = 1:5)
# User Interface
# =================
ui <- fluidPage(theme = shinytheme('united'),
fluidRow(
column(width = 12, align = 'center',
plotOutput('myplot', height = 'auto')
)
))
# ====
# Server Code
# ==============
server <- shinyServer(function(input, output, session) {
# 3. Create the chart
# ===-===-===-===-===-===
output$myplot <- renderPlot({
ggplot(mydf, aes(x = x, y = x)) + geom_point()
}, height = function() { session$clientData$output_myplot_width })
})
# ====
shinyApp(ui, server)
Play around with the width of the window launched by launching this shiny app. By setting height = 'auto' in the UI, and then setting height = function() { session$clientData$output_myplot_width } in the Server, the height is dynamically updated. Very good indeed.
I am working on something similar in plotly, however a significant constraint that I have is that I currently need to pass a parameter for height and width to my call of plotly(), due to the very specific way I am trying to display the markers on my plot. An example of what I am trying to do is as such:
mydf <- data.frame(x = 1:5, y = 1:5)
myplotlyfunction <- function(mydf, myplotheight) {
plot_ly(mydf, height = myplotheight, width = myplotheight * 1.2) %>%
add_trace(x = ~x, y = ~y, type = 'scatter', mode = 'markers')
}
# User Interface
# =================
ui <- fluidPage(theme = shinytheme('united'),
fluidRow(
column(width = 12, align = 'center',
plotlyOutput('myplotly')
)
))
# ====
# Server Code
# ==============
server <- shinyServer(function(input, output, session) {
# 3. Create the chart
# ===-===-===-===-===-===
output$myplotly <- renderPlotly({
this.height <- 600
myplotlyfunction(mydf, myplotheight = this.height)
})
})
# ====
shinyApp(ui, server)
The specific reason why I need to pass a height and width parameter to the plot_ly() call is deserving of a post of its own, and unfortunately I haven't thought of a work around yet. Here is a real example of my plot_ly output -
At a high level, the marker size parameter for plot_ly scatter plots sets the marker sizes based on pixels, and my plot needs very specifically sized markers, and these marker sizes are therefore a function of the plot size.
Here is my attempt to make my plot_ly plots dynamic in Shiny, despite having to initially pass a fixed parameter to the plot_ly height. The idea is motivated on the initial ggplot() dynamic height function:
mydf <- data.frame(x = 1:5, y = 1:5)
myplotlyfunction <- function(mydf, myplotheight) {
plot_ly(mydf, height = myplotheight, width = myplotheight * 1.2) %>%
add_trace(x = ~x, y = ~y, type = 'scatter', mode = 'markers')
}
# User Interface
# =================
ui <- fluidPage(theme = shinytheme('united'),
fluidRow(
column(width = 12, align = 'center',
plotlyOutput('myplotly', height = 'auto')
)
))
# ====
# Server Code
# ==============
server <- shinyServer(function(input, output, session) {
# 3. Create the chart
# ===-===-===-===-===-===
output$myplotly <- renderPlotly({
this.height <- function() { session$clientData$output_myplotly_width }
myplotlyfunction(mydf, myplotheight = this.height)
}, height = function() { session$clientData$output_myplotly_width })
})
# ====
shinyApp(ui, server)
This does not work, and in fact the app crashes right away.
Thank you a ton for anybody who made it to the end of this post. Any help is appreciated regarding how I can dynamically resize a plot_ly() plot in R Shiny, based on the height or width of the app window, given that the plot_ly function itself requires a height parameter. Thoughts on how to solve the underlying problem of needing a height parameter to plot_ly() in the first place are appreciated as well, but that's a separate issue (that I've posted about myself, here - Set marker size based on coordinate values, not pixels, in plotly R - and in other posts as well).
Once again, thanks a ton for any help!

Related

adjust plot given screen size with reactive value

I am trying to adjust plots in flexdashboard by taking the pixelratio provided by the user session, this works fine when I am rendering plot with renderPlot but I am struggling to assign dynamic heights / widths to plots that are rendered with renderPlotly
I extract the user pixel ratio as followed
pixelratio <- reactive({
session$clientData$pixelratio
})
attempt 1
output$myplot <- renderPlotly(myplot())
plotlyOutput("myplot", height = function() {900 / pixelratio()}, width = 825)
the first attempt is giving the following error msg :
Error : CSS units must be single element numeric or character vector
attempt 2
output$myplot <- renderPlotly(myplot())
plotlyOutput("myplot", height = 900 / pixelratio(), width = 825)
the second attempt is submitting the following error msg:
Error : Operation not allowed without an active reactive context.
* You tried to do something that can only be done from inside a reactive consumer
Is there a way to get pixelratio in order to autoscale plotlyOutput?
You can set the height in the plotl_ly function and set height = "auto" in plotlyOutput.
library(shiny)
library(plotly)
library(dplyr)
library(tibble)
state <- data.frame(state.x77, state.region, state.abb) %>%
rename(Region = state.region) %>%
rownames_to_column("State")
ui <- fluidPage(
br(),
plotlyOutput("myplotly", width = 825, height = "auto")
)
server <- function(input, output, session){
pixelratio <- reactive({
session$clientData$pixelratio
})
output[["myplotly"]] <- renderPlotly({
plot_ly(
data = state,
x = ~ Income,
y = ~ Murder,
type = "scatter",
mode = "markers",
text = ~ paste(State, "<br>Income: ", Income, '<br>Murder Rate:', Murder),
height = 900 / pixelratio()
)
})
observe({
print(pixelratio())
})
}
shinyApp(ui, server)

Plotly 'Download as png' resizing image when used in RShiny application

I'm using plotly package for R to display some large graphs in my shiny application, the graphs display nicely as intended. However, when I click the "Download as png" button, the downloaded .png has been resized. here is a demo of this behavior.
How can I specify the resolution for the exported plot?
Here is a minimal example that demonstates the issue by having a really long title that gets clipped on download
app.R
library(shiny)
library(ggplot2)
library(plotly)
ui <- fluidPage(titlePanel("Plotly Download demo"),
plotlyOutput("demoPlotly"))
server <- function(input, output) {
output$demoPlotly <- renderPlotly({
#make an arbritrary graph with a long title
p <- iris %>%
ggplot(aes(x = Petal.Length, y = Petal.Width, color = Species)) +
geom_point() +
labs(title = "This is a really long title to demo my problem of plotly resizing my downloaded output")
ggplotly(p)
})
}
# Run the application
shinyApp(ui = ui, server = server)
The downloaded graph looks like this:
Michael,
Take a look at plotly_IMAGE(). [Version 4.7.1]. The function does allow you to specify width and height for the saved image. For example 800 by 600.
Regarding your example, I have provided an example of how you might modify the layout to accommodate the title wrapping. Note the changes the margin and padding. I also inserted a call to plotly_IMAGE to provide a simplified example of its usage - it slows things down as it is in line with the plot generation. I'd recommend you add a button and separate out from image display.
Hope this helps
Take care
T.
Pre-requisite - Plotly Credentials:
To use this plotly example you need to register and obtain a plotly api key. Once obtained you need add these entries to your ~/.Renviron file.
Ref: https://plot.ly/r/getting-started/
Example .Renviron file entries
plotly_username="xxxxx"
plotly_api_key="xxxxxx"
Example Code
library(shiny)
library(ggplot2)
library(plotly)
wrapper <- function(x, ...) {
paste(strwrap(x, ...), collapse = "\n")
}
# Example usage wrapper(my_title, width = 20)
my_title <- "This is a really long title to demo my problem of plotly resizing my downloaded output"
ui <- fluidPage(titlePanel("Plotly Download demo"), plotlyOutput("demoPlotly"))
pal <- c("blue", "red", "green")
pal <- setNames(pal, c("virginica", "setosa", "versicolor"))
layout <- list(title = wrapper(my_title, width = 60),
xaxis = list(title = "Petal Length"),
yaxis = list(title = "Petal Width"),
margin = list(l = 50, r = 50, b = 100, t = 100, pad = 4))
server <- function(input, output) {
output$demoPlotly <- renderPlotly({
# make an arbitrary graph with a long title
p <- iris %>%
plot_ly(x = ~Sepal.Length,
y = ~Petal.Width,
color = ~Species,
colors = pal,
mode = "markers",
type = "scatter") %>%
layout(autosize = F,
title = layout$title,
xaxis = layout$xaxis,
yaxis = layout$yaxis,
margin = layout$margin)
p$elementId <- NULL
# Example usage of plotly image
plotly_IMAGE(p,
width = 800,
height = 600,
format = "png",
scale = 1,
out_file = "output.png")
p
})
}
# Run the application
shinyApp(ui = ui, server = server)
Screen Shot Image - screen capture
Plotly save - screen click
plotly_IMAGE() save image output 800 by 600

Disable visual response of R Plotly click events

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.

R Shiny quantmod zoomChart and fixed coloring of points

I have a problem regarding shiny and the use of sliders, when displaying chart_Series and coloured lines . When I am using the slider from the right side, the color is chosen appropriately (mainly red dots). When I use the slider on left side (e.g. looking at the latest data) the color is not chosen appropriately (should be green). I seek for help and I'm thankful for any advise!
require(quantmod)
require(shiny)
getSymbols("YHOO")
data <- YHOO
data <- data[1:100,]
col_function <- rep("red",50)
col_function <- c(col_function, rep("green",50))
plot <- {
date_range <- index(data)
if (interactive()) {
options(device.ask.default = FALSE)
# Define UI
ui <- fluidPage(
titlePanel("Time:"),
sidebarLayout(
# Sidebar with a slider input
wellPanel(
tags$style(type="text/css", '#leftSlide { width:200px; float:left;}'),
id = "leftSlide",
sliderInput("Range", "Choose Date Range:",
min = first(date_range), max = last(date_range), value = c(first(date_range),last(date_range)))
),
mainPanel(
plotOutput("Plot", width = "150%", height = "500px")
)
)
)
# Server logic
server <- function(input, output) {
output$range <- renderPrint({ input$slider2 })
output$Plot <- renderPlot({
x_ti2 <- xts(rep(1, NROW(data)), order.by = index(data))
x_ti2[1, ] <- 0.5
chart_Series(data)
add_TA(x_ti2, col = col_function, pch = 9, type = 'p', cex = .7)
# Another way below, but the color function is still not working
#chart_Series(data["2017"], TA = 'add_TA(x_ti, col = col_function, pch = 15, type = "p", cex = .7); add_TA(x_ti2, col = "orange", pch = 9, type = "p", cex = .7)')
zoom_Chart(paste(input$Range, collapse = "::"))
})
observe({
print(input$Range)
})
}
shinyApp(ui, server)
}
}
plot
So, I found a workaround for my own problem:
as far as i can tell, shiny will edit the data, but not the coloring-vector (in respective pretty logical...). Therefore you'll have to tell shiny to update the range of the coloring as well.
First of all i transformed the coloring result to an xps-object. Then i added the x_ti2-variable to the original data-matrix and defined a variable with the input$range for your the col_function. The variable needs to part of the Global environment (such that shiny can use it). I hope this will solve some issues, if someone has a similiar problem.

Use bsModal in the shinyBS package with plotly R plotly_click to generate new plot in pop up

Here is my code for a basic shiny app using plotly_click event to optionally show another plot. I would like that side box plot to render in a modal pop up instead of on the side within the page.
library(shiny)
library(plotly)
df1 <- data.frame(x = 1:10, y = 1:10)
df2 <- data.frame(x = c(rep('a', 10), rep('b', 10)),
y = c(rnorm(10), rnorm(10, 3, 1)))
ui <- fluidPage(
column(6, plotlyOutput('scatter')),
column(6, plotlyOutput('box'))
)
server <- function(input, output) {
output$scatter <- renderPlotly({
plot_ly(df1, x = x, y = y, mode = 'markers', source = 'scatter')
})
output$box <- renderPlotly({
eventdata <- event_data('plotly_click', source = 'scatter')
validate(need(!is.null(eventdata),
'Hover over the scatter plot to populate this boxplot'))
plot_ly(df2, x = x, y = y, type = 'box')
})
}
shinyApp(ui = ui, server = server)
I was able to follow this question (Shiny: plot results in popup window) and response, and tried to use it with the trigger of plotly_click without success. Any idea how to pull the same thing off with a plotly hover click event?
UPDATE: I can clearly see that a plotly plot can be rendered in a shinyBS modal pop up window as demonstrated by this code.
df1 <- data.frame(x = 1:10, y = 1:10)
ui <- fluidPage(
actionButton('go', 'Click Go'),
bsModal('plotlyPlot', 'Here is a Plot', 'go', plotlyOutput('scatter1'))
)
server <- function(input, output) {
output$scatter1 <- renderPlotly({
plot_ly(df2, x = x, y = y, mode = 'markers', source = 'scatter1')
})
}
shinyApp(ui = ui, server = server)
Instead of an actionButton as the trigger, I want the plotly_click or plotly_hover as there trigger (in the original example).
You can use toggleModal, just add this to your server:
observeEvent(event_data("plotly_click", source = "scatter"), {
toggleModal(session, "boxPopUp", toggle = "toggle")
})
and put the box Plot in an bsModal (Title and trigger is empty):
ui <- fluidPage(
column(6, plotlyOutput('scatter')),
bsModal('boxPopUp', '', '', plotlyOutput('box'))
)
UPDATE: with shiny-build-in Modal functionality (since Shiny 0.14), only the server addition is needed:
observeEvent(event_data("plotly_click", source = "scatter"), {
showModal(modalDialog(
renderPlotly({
plot_ly(df2, x = ~x, y = ~y, type = 'box')
})
))
})
Using CSS
You can use HTML builder to contain the plots and use stylesheet to add dynamic effects.
ui <- fluidPage(
includeCSS(path_to_css_file),
div( class='mainchart',
column(6, plotlyOutput('scatter')),
div( class='popup',
column(6, plotlyOutput('box'))
)
)
)
CSS
div.popup {
display : none;
position: absolute;
}
div.mainchart : focus > div.popup {
display : block;
}
div.mainchart {
position: relative;
}
Using Javascript
You can use the plotly embeded-API to set the visibility of your side box.
shinyBS
Since you want to stick to shinyBS, you can use the bsPopover function with a little trick. I assume you already know how to use bsModel which is similar to the example below.
Pass the following argument to fluidPage
bsTooltip(id, title, placement = "bottom", trigger = "click", content=column(6, plotlyOutput('box')) )
This will create the plot with a Popover wraper. I didn't test it yet. In case of error, you can also try
options = list()
options$content = column(6, plotlyOutput('box'))
options$html = T # otherwise the conent will be converted to text
bsTooltip(id, title, placement = "bottom", trigger = "click", options=options )
Visit this source file of shinyBS and the popover(options) function of bootstrap for more info.

Resources