I am looking to draw simple shapes in R Shiny. (The goal is to draw a static legend using HTML instead of loading a png.)
I can't get the canvas tag to work. It simply does not draw anything.
library(shiny)
ui <- fluidPage(
tags$script(HTML(
"function draw_legend() {",
"var canvas = document.getElementById('simple_legend');",
"const canvas = document.getElementById('canvas');",
"const ctx = canvas.getContext('2d');",
"ctx.fillStyle = 'green';",
"ctx.fillRect(10, 10, 150, 100);",
"}"
)),
sidebarLayout(
sidebarPanel(
),
mainPanel(
tags$body(onload="draw_legend();"),
tags$canvas(id="simple_legend", height = "30"),
tags$div("Some text")
)
)
)
server <- function(input, output, session) {
}
shinyApp(ui = ui, server = server)
Expanding on my comment, here is an app showing two techniques for drawing a circle in Shiny, one using CSS, the other using a data frame. You can make obvious adjustments to size, colour, position etc to get the effect you want.
library(shiny)
library(tidyverse)
ui <- fluidPage(
tags$head(
tags$style("#circleText {color: red; font-size: 20px; }")
),
uiOutput("circleText"),
plotOutput("circleGraph")
)
server <- function(input, output, session) {
output$circleText <- renderUI({
HTML("⬤")
})
output$circleGraph <- renderPlot({
tibble(theta=seq(0, 2*pi, 0.025), x=sin(theta), y=cos(theta)) %>%
ggplot(aes(x, y)) +
geom_path() +
coord_fixed() +
theme_void()
},
height=75,
width=75)
}
shinyApp(ui = ui, server = server)
Giving
You have to
remove the line const canvas = document.getElementById('canvas');
set a width to the canvas element, not only a height
Related
My shiny app generates a number of useful graphs. I would like to allow the user to download the graphs in various formats.
I have done this before for a single graph using How to save plots that are made in a shiny app as a guide. However, I am ending up creating more repeated code for each additional plot. I am not a programmer, but it really seems like I should be able to write one function to do this since I am just passing parameters to downloadHandler and ggsave, but I can't figure it out.
The MRE below represents a page with, say, ten different graphs. Is there a way to write a single function that receives the plot ID from a button (like a tag or something?) and the format from the selectInput to pass those parameters to downloadHandler and ggsave to save each of those graphs in the selected format? The function at the bottom shows my thinking, but I don't know where to go from here or if that is even the right direction.
Thanks!
library(shiny)
library(ggplot2)
# Define UI for application that draws a histogram
ui <- fluidPage(
# Application title
titlePanel("Old Faithful Geyser Data"),
# Sidebar with a slider input for number of bins
sidebarLayout(
sidebarPanel(
sliderInput("bins",
"Number of bins:",
min = 1,
max = 50,
value = 30)
),
# Show plots and download buttons
mainPanel(
plotOutput("distPlot"),
fluidRow(
column(3,
downloadButton("dl_plot1")
),
column(3,
selectInput("plot1_format",label = "Format",choices = c("SVG","PDF","JPEG","PNG"),width = "75px")
)
),
plotOutput("scat_plot"),
column(3,
downloadButton("dl_plot2")
),
column(3,
selectInput("plot2_format",label = "Format",choices = c("SVG","PDF","JPEG","PNG"),width = "75px")
)
)
)
)
# Define server logic required to draw a histogram and scatterplot
server <- function(input, output) {
output$distPlot <- renderPlot({
x <- faithful$waiting
binwidth<-(max(x)-min(x))/input$bins
p<-ggplot(faithful,aes(waiting))+
geom_histogram(binwidth = binwidth)
p
})
output$scat_plot<-renderPlot({
p<-ggplot(faithful,aes(x=waiting,y=eruptions))+
geom_point()
p
})
downloadPlot <- function(plot_name,file_name,file_format){#concept code
downloadHandler(
filename=function() { paste0(file_name,".",file_format)},
content=function(file){
ggsave(file,plot=plot_name,device=file_format)
}
)
}
}
# Run the application
shinyApp(ui = ui, server = server)
To achieve your desired result without duplicating code you could (or have to) use a Shiny module. Basically a module is a pair of an UI function and a server function. For more on modules I would suggest to have a look at e.g. Mastering shiny, ch. 19.
In the code below I use a module to take care of the download part. The job of downloadButtonUI and downloadSelectUI is to add a download button and a selectInput for the file format. The downloadServer does the hard work and saves the plot in the desired format.
Note: Besides the download module I moved the code for the plots to reactives so that the plots could be passed to the downloadHandler or the download module.
EDIT: Added a fix. We have to pass the reactive (e.g. dist_plot without parentheses) to the download server and use plot() inside the downloadServer instead to export the updated plots.
library(shiny)
library(ggplot2)
# Download Module
downloaButtondUI <- function(id) {
downloadButton(NS(id, "dl_plot"))
}
downloadSelectUI <- function(id) {
selectInput(NS(id, "format"), label = "Format", choices = c("SVG", "PDF", "JPEG", "PNG"), width = "75px")
}
downloadServer <- function(id, plot) {
moduleServer(id, function(input, output, session) {
output$dl_plot <- downloadHandler(
filename = function() {
file_format <- tolower(input$format)
paste0(id, ".", file_format)
},
content = function(file) {
ggsave(file, plot = plot())
}
)
})
}
# Define UI for application that draws a histogram
ui <- fluidPage(
# Application title
titlePanel("Old Faithful Geyser Data"),
# Sidebar with a slider input for number of bins
sidebarLayout(
sidebarPanel(
sliderInput("bins",
"Number of bins:",
min = 1,
max = 50,
value = 30
)
),
# Show plots and download buttons
mainPanel(
plotOutput("distPlot"),
fluidRow(
column(3, downloaButtondUI("distPlot")),
column(3, downloadSelectUI("distPlot"))
),
plotOutput("scat_plot"),
fluidRow(
column(3, downloaButtondUI("scatPlot")),
column(3, downloadSelectUI("scatPlot"))
),
)
)
)
server <- function(input, output) {
dist_plot <- reactive({
p <- ggplot(faithful, aes(waiting)) +
geom_histogram(bins = input$bins)
p
})
scat_plot <- reactive({
p <- ggplot(faithful, aes(x = waiting, y = eruptions)) +
geom_point()
p
})
output$distPlot <- renderPlot({
dist_plot()
})
output$scat_plot <- renderPlot({
scat_plot()
})
downloadServer("distPlot", dist_plot)
downloadServer("scatPlot", scat_plot)
}
shinyApp(ui = ui, server = server)
#>
#> Listening on http://127.0.0.1:4092
I want to make a little animation in R shiny.
The core is to put a sample(1:100, 1), in a valueBox, but I'd like to make an animation by printing random numbers for some seconds and and in the end print the sample result.
I've found the following code, which uses JavaScript. The problem is that the code generates an animation from 0 to the random number generated in sample.
library(shiny)
library(shinydashboard)
js <- "
Shiny.addCustomMessageHandler('anim',
function(x){
var $box = $('#' + x.id + ' div.small-box');
var value = x.value;
var $icon = $box.find('i.fa');
var $s = $box.find('div.inner h3');
var o = {value: 0};
$.Animation( o, {
value: value
}, {
duration: 1000
}).progress(function(e) {
$s.text((e.tweens[0].now).toFixed(0));
});
}
);"
# UI
ui <- dashboardPage(
skin = "black",
dashboardHeader(title = "Test"),
dashboardSidebar(disable = TRUE),
dashboardBody(
tags$head(tags$script(HTML(js))),
fluidRow(
tagAppendAttributes(
valueBox("", subtitle = "Número sorteado",
icon = icon("server"),
color = "blue"
),
id = "mybox"
)
),
br(),
actionButton("btn", "Change value")
)
)
# Server response
server <- function(input, output, session) {
rv <- reactiveVal()
observeEvent(input[["btn"]], {
rv(sample(1:100, 1))
})
observeEvent(rv(), {
for(i in 1:30){
session$sendCustomMessage("anim", list(id = "mybox", value = rv()))
}
})
}
shinyApp(ui, server)
I've also found this code that makes exactly what I want in JS, but I couldn't put it in shiny.
There's no need to resort to Javascript. Shiny has a built-in timer.
This MWE creates a value box displaying an integer chosen at random in the range 1 to 100 that updates once a second.
library(shiny)
library(shinydashboard)
ui <- dashboardPage(
dashboardHeader(),
dashboardSidebar(),
dashboardBody(
valueBoxOutput("random")
)
)
server <- function(input, output, session) {
output$random <- renderValueBox({
invalidateLater(1000, session)
valueBox("Value", sample(1:100, 1))
})
}
shinyApp(ui = ui, server = server)
I am trying to update divs while in a loop, some of which contain images. Using removeUI(..., immediate = TRUE) I can remove them and then replace them by new divs, with insertUI(..., immediate = TRUE). Although the texts appear in real time, the images do not load until we are out of the loop (see example below, you don't even have to load an image, a question mark will appear after the loop ends).
In my browser I can see the img tags are created in HTML, but still no images appear live.
Here is a reproducible example:
ui <- fluidPage(
actionButton("add","")
)
server <- function(input, output, session) {
for(i in 1:3){
Sys.sleep(1.5)
insertUI(
selector = "#add",
where = "afterEnd",
ui = div(style = paste0("width: 75px; height: 75px; background-color: white;"), h5("Text appears live", align = "center"),
div(h6("Text inside a div appears live")),
div(id = "img", img(src = "image.jpg", alt = "Images do not appear live")
)
),
immediate = TRUE
)
}
}
shinyApp(ui, server)
Is this normal behavior for shiny? If so is their a way to bypass it and to see the images appear directly? Another way to do it?
Here's a slightly more self-contained set of code that works for me if I run the app by hitting the "Run App" button in Rstudio.
dir.create("www")
dir.create("www/images")
library(shiny)
library(magick)
green.square <- image_blank(width=50, height =75, color= "green")
grid.total.squares <- 12*8
wordList <- 1:(grid.total.squares*2)
for (i in seq_along(wordList)){
thisImage = image_annotate(green.square, gravity="center", i,
size=30)
image_write(thisImage, format = "png", path = paste0("www/images/",i, ".png"))
}
ui <- fluidPage(
actionButton("add","Add something"),
)
server <- function(input, output, session) {
for(i in 1:3){
Sys.sleep(.5)
insertUI(
selector = "#add",
where = "afterEnd",
ui = div(style = paste0("min-width:75px; min-height: 75px; background-color: white; clear:both;"), h5("Text appears live", align = "center"),
div(h6("Text inside a div appears live")),
div(id = "img",
img(src = paste0("images/",i,".png"),
alt = "Images do not appear live"
),
hr()
)
),
immediate = TRUE
)
}
}
shinyApp(ui, server)
I have an output from plotOutput and when there is a double click on the map, I want to see an output from leafletoutput. In the code below, when there is a double click on the map, the leaflet map shows below the google map. Before double click the the first image shows but after double clicking, I want to see the leaflet map only. Any suggestions on how to do this?
library(shiny)
library(shinydashboard)
library(leaflet)
library(dismo)
library(ggmap)
library(dplyr)
shinyApp(
ui = dashboardPage(
dashboardHeader(title=""),
dashboardSidebar(width = 200 ),
dashboardBody(
fluidRow(
plotOutput("USA_GoogleMap",dblclick='plot_dblclick'),
leafletOutput("leaflet_map")
)
)),
server=function(input, output, session) {
double_clicked <- reactiveValues(
center = NULL
)
# Handle double clicks on the plot
observeEvent(input$plot_dblclick, {
double_clicked$center <- c(input$plot_dblclick$x,input$plot_dblclick$y)
})
output$USA_GoogleMap<-renderPlot({
statesMap = map_data("state")
xy=cbind(statesMap$long,statesMap$lat)
y=c(36.4,41.5,42.25,27.7,32.77)
x=c(-115.5,-100,-75,-81.5,-97.45)
state=c("Nevada","Nebraska","New York","Florida","Texas")
bases=cbind(x,y)
bases_mercator=data_frame(Mercator_X=Mercator(bases)[,1],Mercator_Y=Mercator(bases)[,2],State=state)
g = gmap(xy, type='satellite',zoom=4)
plot(g, inter=TRUE)
points(Mercator(bases) , pch=20,cex=16, col=adjustcolor("white", alpha=0.2))
points(Mercator(bases) , pch=20,cex=16, col=adjustcolor("yellow", alpha=0.4))
text(bases_mercator$Mercator_X,bases_mercator$Mercator_Y,state)
})
output$leaflet_map <- renderLeaflet({
if(!is.null(double_clicked$center)){
leaflet()%>%setView(lng = -71.0589, lat = 42.3601, zoom = 12)%>%addTiles()
}
})
}
)
shinyApp(ui = ui, server = server)
First Image
Second image
Let me first preface this by saying - there's a better way to do this than I'm showing. I just haven't found one yet. I'm sure a much better programmer than I would know of it, but at least I can say this works. Even though it's hideous. The key to hiding the plot was using conditionalPanel (which I was previously unfamiliar with).
I have a text trigger for identifying whether or not the plot was double-clicked, and use that to trigger whether or not to show the panel. However, I couldn't get the text to initialize without calling it with textOutput... so I have a textOutput call with a font size of zero. Again, there must be a much better way of triggering that than I'm doing it... but again, at least it works. Hopefully it will help.
library('shiny')
library('shinydashboard')
library('leaflet')
library('dismo')
library('ggmap')
library('dplyr')
shinyApp(
ui = dashboardPage(
dashboardHeader(title=""),
dashboardSidebar(width = 200 ),
dashboardBody(
fluidRow(
conditionalPanel(
condition = 'output.condition == 0',
plotOutput("USA_GoogleMap",dblclick='plot_dblclick')
),
leafletOutput("leaflet_map"),
textOutput('condition'),
tags$head(tags$style("#condition{font-size: 0px}"))
)
)),
server=function(input, output, session) {
double_clicked <- reactiveValues(
center = NULL
)
# Handle double clicks on the plot
observeEvent(input$plot_dblclick, {
double_clicked$center <- c(input$plot_dblclick$x,input$plot_dblclick$y)
})
output$USA_GoogleMap<-renderPlot({
if(is.null(double_clicked$center)){
statesMap = map_data("state")
xy=cbind(statesMap$long,statesMap$lat)
y=c(36.4,41.5,42.25,27.7,32.77)
x=c(-115.5,-100,-75,-81.5,-97.45)
state=c("Nevada","Nebraska","New York","Florida","Texas")
bases=cbind(x,y)
bases_mercator=data_frame(Mercator_X=Mercator(bases)[,1],Mercator_Y=Mercator(bases)[,2],State=state)
g = gmap(xy, type='satellite',zoom=4)
plot(g, inter=TRUE)
points(Mercator(bases) , pch=20,cex=16, col=adjustcolor("white", alpha=0.2))
points(Mercator(bases) , pch=20,cex=16, col=adjustcolor("yellow", alpha=0.4))
text(bases_mercator$Mercator_X,bases_mercator$Mercator_Y,state)
}
})
output$leaflet_map <- renderLeaflet({
if(!is.null(double_clicked$center)){
leaflet()%>%setView(lng = -71.0589, lat = 42.3601, zoom = 12)%>%addTiles()
}
})
output$condition <- renderText({
ifelse(!is.null(double_clicked$center), 1, 0)
})
}
)
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.