Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Using shinycssloaders inside an rmarkdown #8

Closed
reshdesu opened this issue Sep 27, 2017 · 27 comments
Closed

Using shinycssloaders inside an rmarkdown #8

reshdesu opened this issue Sep 27, 2017 · 27 comments

Comments

@reshdesu
Copy link

I am using flexidashboard and shinycssloader v0.2.0. When I try to get the loading animation I get the message "Loading..." instead of the animation.

@andrewsali
Copy link
Collaborator

Do you have an example of how you apply the spinner in flexdashboard? As far as I know with Rmarkdown (runtime: shiny) you don't have control over the ui elements, they are automatically created based on the corresponding renderXXX function (which are in the server function in Shiny).

When I will have a bit more time I'll try to investigate if this can be circumvented somehow...

@kent37
Copy link

kent37 commented Nov 1, 2017

I don't know if it is documented anywhere, but you can use the output variable in a RMarkdown Shiny document. This allows use of the plotOutput, etc. For example, the renderPlot portion of the boilerplate RMarkdown Shiny document can be modified to use shinycssloader as below. This example has a 2 second delay added to renderPlot so you can see the "Loading..." message (and not the spinner).

output$plot = renderPlot({
  Sys.sleep(2) # Make it take some time
  hist(faithful$eruptions, probability = TRUE, breaks = as.numeric(input$n_breaks),
       xlab = "Duration (minutes)", main = "Geyser eruption duration")
  
  dens <- density(faithful$eruptions, adjust = input$bw_adjust)
  lines(dens, col = "blue")
})

shinycssloaders::withSpinner(plotOutput('plot'))

@kent37
Copy link

kent37 commented Nov 1, 2017

The CSS for the loader doesn't seem to be inserted into the document.

@andrewsali
Copy link
Collaborator

I have added a new branch called rmarkdown. Installing the package from this branch provides an extra function called rmd_in_header. This can be used to create a static header file that can be included in an Rmd document.

Limitations are the following:

  • You need to explicitly call the relevant output function (e.g. plotOutput) in the Rmarkdown file, the i.e. having renderPlot in the document is not enough (as would be with a simple .Rmd document, where shiny does some more magic to create the output function), as the spinner needs to be applied to the output function directly.
  • You can only have one type of spinner in the document, size and color also have to be the same across spinners (the latter are fixed when you call rmd_in_header).
  • You need to make sure that you use the same type of spinner when generating the header file as you will use in the .Rmd file when calling withSpinner. The color and size arguments of withSpinner will have no effect. I will later create a dedicated withSpinnerRmd function to make this clearer, however wanted to have something working already out there.

A working example is given here.

@pernaletec
Copy link

pernaletec commented Sep 20, 2019

Hi @andrewsali , thank you very much for such a useful library.

I could add shinyccsloaders in my flexdashboard based document. However, I would like to know how to generate a new cssloader_in_header.html file using the rmd_in_header function. As you might suppose, I am using thecssloader_in_header.html example file.

Could you please give me a hint about how to use the rmd_in_header function?

Regards.

@daattali daattali changed the title The loading bar just shows "Loading..." instead of animation Using shinycssloaders inside an rmarkdown May 3, 2020
@melissagwolf
Copy link

Is this possible to use with flexdashboard? Following from the example you linked, I don't understand how to add it to the yaml when I am not outputting an html_document.

---
title: "Dynamic Model Fit"
runtime: shiny
output: 
  flexdashboard::flex_dashboard:
    orientation: columns
    source_code: embed
always_allow_html: yes
---

@daattali
Copy link
Owner

shinycssloaders does not officially support flexdashboards because they are not shiny apps. It's possible that they might work with some special hacking, if someone here is able to get it to work and post their solution here that would be great, but it might not be possible.

@melissagwolf
Copy link

Cool, thank you! I definitely will not be the one to figure that out since I thought a flexdashboard was a shiny app, but hopefully someone else will :)

@daattali
Copy link
Owner

flexdashboard is rmarkdown. The line between rmarkdown and shiny keeps getting fuzzier with time, but it's still not shiny, sorry :)

@melissagwolf
Copy link

Thanks! This explains a lot.

@pernaletec
Copy link

Hi @melissagwolf @daattali . I was able to use it with Flexdashboard. I just did it the same way @andrewsali explained in his example. What is the exact problem you are having?

My yaml looks something like this:

title: "Dynamic Model Fit"
output: 
  flexdashboard::flex_dashboard:
    orientation: columns
    includes:
      in_header: cssloaders_in_header.html
runtime: shiny

@daattali
Copy link
Owner

@pernaletec what version of shinycssloaders did you use? The master branch, or the special branch that @andrewsali created?

@pernaletec
Copy link

My main reference was this example: https://github.com/daattali/shinycssloaders/tree/rmarkdown/example/rmarkdown
I strictly followed the work of @andrewsali ... thanks BTW!

@daattali
Copy link
Owner

Thanks for sharing

@melissagwolf
Copy link

@pernaletec @daattali Could one of you help me install the version from the special branch? I'm getting a pandoc error without it. Apologies for the entry level question!

I tried this:

library(devtools)
install_github("daattali/shinycssloaders", subdir="rmarkdown")

Downloading GitHub repo daattali/shinycssloaders@master
Error: Failed to install 'shinycssloaders' from GitHub:
Does not appear to be an R package (no DESCRIPTION)

@daattali
Copy link
Owner

"rmarkdown" is not a subdirectory, it's a branch. So don't use the "subdir" parameter. I forget what the right parameter is for installing a specific branch, it might be "ref".

@melissagwolf
Copy link

Ah. I got it to install, but I had to bypass the update for glue that was requested by the branch (only the binary version is available and my computer won't install it). This causes shinycss to fail when I run @andrewsali 's demo:

pandoc.exe: C:\Users\missg\AppData\Local\Temp\RtmpMBZ78l\cssloaders_in_header.html: openBinaryFile: does not exist (No such file or directory)

Warning: Error in : pandoc document conversion failed with error 1

C'est la vie - I will find another package! Thanks all for your help - I really appreciate it.

@xiangnandang
Copy link

Hi, this package and thread is new to me, but after some trial and error I sort of succeeded in my application:

I largely followed @andrewsali instructions, which worked mostly from the beginning. The only two things didn't work well were the spinner was on the very top edge of the container, and my plotlyOutput(height = "100%") was changed to default 400px.

I did the following to correct the two issues:
in the cssloaders_in_header.html file,
change from:
.shiny-spinner-output-container {
position: relative;
}
to:
.shiny-spinner-output-container {
height: 100%;
}

This worked for my narrow-scoped application, but I didn't test for broader situations.

Best,
Xiangnan

@daattali
Copy link
Owner

daattali commented Jun 4, 2020

Thanks! If you think you got it working very well in a robust way, feel free to add a small section to the README telling future people how to do this :)

@xiangnandang
Copy link

Hi @daattali , thanks for your comment. I did a one-off trouble-shoot around the existing code, and provided the necessary modifications to make it successfully run in my only application. I'd definitely love to contribute more and do more robust testing. However, the fact that this function is not in the master branch (it's only in the rmarkdown branch last updated three years ago), makes it hard to contribute. Do you have plan to merge the rmarkdown branch to master?

Best,
Xiangnan

@daattali
Copy link
Owner

daattali commented Jun 5, 2020

You're right, I forgot this was all dependent on another branch. I suppose the best solution if we wanted to support Rmd would be to create a separate function and separate files, in the master branch, that would work with rmd. If anybody wants to take on that initiative, I would welcome that.

@xiangnandang
Copy link

I just checked, there's no merge conflict between rmarkdown and master branches, do you want me to create a pull request, or it's easier for you to do it. I could take a better look at the rmd_in_header function by @andrewsali and hopefully improve upon.

@daattali
Copy link
Owner

daattali commented Jun 5, 2020

I'll let you take care of that :) Bring the necessary files in and please do some testing to make sure the regular withSpinner isn't affected

@daattali
Copy link
Owner

daattali commented Apr 17, 2023

For completion, here is the solution @andrewsali (original author) proposed years ago (I'm copying it here because I want to clean up stale git branches). It includes 3 files:

File 1: R/rmd_in_header.R
#' Generate an html file to be included as `in_header` for Rmarkdown documents
#' @param header_file The path to the html header file to be created
#' @param type
#' @param color
#' @param size
#' @param proxy.height
rmd_in_header <- function(header_file="cssloaders_in_header.html", type=getOption("spinner.type",default=1),color=getOption("spinner.color",default="#0275D8"),size=getOption("spinner.size",default=1),color.background=getOption("spinner.color.background")) {
  file.remove(header_file)

  .shinycssloaders_headers <- file(header_file,"w"); 
  writeLines("<style>",.shinycssloaders_headers)
  writeLines(readLines(system.file(sprintf("css-loaders/css/load%s.css",type),package="shinycssloaders")),.shinycssloaders_headers)
  writeLines(readLines(system.file("assets/spinner.css",package="shinycssloaders"),warn = FALSE),.shinycssloaders_headers); 
  color.alpha <- sprintf("rgba(%s,0)",paste(grDevices::col2rgb(color),collapse=","))

  if (type==1) {
    writeLines(
      glue::glue(".load{type} .loader, .load{type} .loader:before, .load{type} .loader:after {{background: {color}}} .load{type} .loader {{color: {color}}}"),.shinycssloaders_headers
    )
  }

  if (type %in% c(2,3) && is.null(color.background)) {
    stop("For spinner types 2 & 3 you need to specify manually a background color. This should match the background color of the container.")
  }

  if (type == 2) {
    writeLines(
      glue::glue(".load{type} .loader {{color: {color}}} .load{type} .loader:before, .load{type} .loader:after {{background: {color.background};}}"),.shinycssloaders_headers
    )
  }

  if (type == 3) {
    writeLines(
      glue::glue(
        ".load{type} .loader {{
        background: -moz-linear-gradient(left, {color} 10%, {color.alpha} 42%);
        background: -webkit-linear-gradient(left, {color} 10%, {color.alpha} 42%);
        background: -o-linear-gradient(left, {color} 10%, {color.alpha} 42%);
        background: -ms-linear-gradient(left, {color} 10%, {color.alpha} 42%);
        background: linear-gradient(to right, {color} 10%, {color.alpha} 42%);
  }} 
        .load{type} .loader:before {{
        background: {color}
        }}  
        .load{type} .loader:after {{
        background: {color.background};
        }}
        "),.shinycssloaders_headers
    )
  }

  if (type %in% c(4,6,7)) {
    writeLines(
      glue::glue(".load{type} .loader {{color: {color}}}")
    ,.shinycssloaders_headers)
  }


  if (type == 8) {
    writeLines(
      glue::glue("
.load{type} .loader {{
      border-top: 1.1em solid rgba({paste(grDevices::col2rgb(color),collapse=',')}, 0.2);
      border-right: 1.1em solid rgba({paste(grDevices::col2rgb(color),collapse=',')}, 0.2);
      border-bottom: 1.1em solid rgba({paste(grDevices::col2rgb(color),collapse=',')}, 0.2);
      border-left: 1.1em solid {color};
}}
      "),.shinycssloaders_headers
    )
  }

  # get default font-size from css, and cut it by 25%, as for outputs we usually need something smaller
  size <- round(c(11,11,10,20,25,90,10,10)[type] * size * 0.75)
  writeLines(
    glue::glue(".load{type} .loader {{font-size: {size}px}}"),.shinycssloaders_headers
  )
  writeLines("</style>",.shinycssloaders_headers)

  writeLines("<script>",.shinycssloaders_headers)
  writeLines(readLines(system.file("assets/spinner.js",package="shinycssloaders")),.shinycssloaders_headers); 
  writeLines("</script>",.shinycssloaders_headers)
  close(.shinycssloaders_headers); 
}
File 2: cssloaders_in_header.html
<style>
.load1 .loader,
.load1 .loader:before,
.load1 .loader:after {
  background: #ffffff;
  -webkit-animation: load1 1s infinite ease-in-out;
  animation: load1 1s infinite ease-in-out;
  width: 1em;
  height: 4em;
}
.load1 .loader {
  color: #ffffff;
  text-indent: -9999em;
  margin: 88px auto;
  position: relative;
  font-size: 11px;
  -webkit-transform: translateZ(0);
  -ms-transform: translateZ(0);
  transform: translateZ(0);
  -webkit-animation-delay: -0.16s;
  animation-delay: -0.16s;
}
.load1 .loader:before,
.load1 .loader:after {
  position: absolute;
  top: 0;
  content: '';
}
.load1 .loader:before {
  left: -1.5em;
  -webkit-animation-delay: -0.32s;
  animation-delay: -0.32s;
}
.load1 .loader:after {
  left: 1.5em;
}
@-webkit-keyframes load1 {
  0%,
  80%,
  100% {
    box-shadow: 0 0;
    height: 4em;
  }
  40% {
    box-shadow: 0 -2em;
    height: 5em;
  }
}
@keyframes load1 {
  0%,
  80%,
  100% {
    box-shadow: 0 0;
    height: 4em;
  }
  40% {
    box-shadow: 0 -2em;
    height: 5em;
  }
}
.shiny-spinner-output-container {
  position: relative;
}

.load-container {
  position: absolute;
  top: 50%;
  -webkit-transform: translateY(-50%);
  transform: translateY(-50%);
  /* to avoid showing a vertical scrollbar
  http://stackoverflow.com/questions/38251204/horizontal-animation-causes-vertical-scrollbar-in-css */
  overflow:hidden; 
  width: 100%;
}

.shiny-spinner-hidden {
  position: absolute;
  top:0;
  left:0;
  z-index: -1;
  visibility:hidden;
}
.load1 .loader, .load1 .loader:before, .load1 .loader:after {background: #0275D8} .load1 .loader {color: #0275D8}
.load1 .loader {font-size: 8px}
</style>
<script>
(function() {
var output_states = [];

function show_spinner(id) {
    $("#"+id).siblings(".load-container, .shiny-spinner-placeholder").removeClass('shiny-spinner-hidden');
    $("#"+id).siblings(".load-container").siblings('.shiny-bound-output, .shiny-output-error').css('visibility', 'hidden');
    // if there is a proxy div, hide the previous output
    $("#"+id).siblings(".shiny-spinner-placeholder").siblings('.shiny-bound-output, .shiny-output-error').addClass('shiny-spinner-hidden');
}

function hide_spinner(id) {
    $("#"+id).siblings(".load-container, .shiny-spinner-placeholder").addClass('shiny-spinner-hidden');
    $("#"+id).siblings(".load-container").siblings('.shiny-bound-output').css('visibility', 'visible');
    // if there is a proxy div, show the previous output in case it was hidden
    $("#"+id).siblings(".shiny-spinner-placeholder").siblings('.shiny-bound-output').removeClass('shiny-spinner-hidden');
}

function update_spinner(id) {
  if (!(id in output_states)) {
    show_spinner(id);
  }
  if (output_states[id] <= 0) {
    show_spinner(id);
  } else {
    hide_spinner(id);
  }
}

$(document).on('shiny:bound', function(event){ 
  /* if not bound before, then set the value to 0 */
  if (!(event.target.id in output_states)) {
    output_states[event.target.id] = 0;
  }
  update_spinner(event.target.id);
});

/* When recalculating starts, show the spinner container & hide the output */
$(document).on('shiny:outputinvalidated', function(event) {
  output_states[event.target.id] = 0;
  update_spinner(event.target.id);
});

/* When new value or error comes in, hide spinner container (if any) & show the output */
$(document).on('shiny:value shiny:error', function(event) {
  output_states[event.target.id] = 1;
  update_spinner(event.target.id);
});
}());
</script>
File 3: rmarkdown_example.Rmd
---
title: "shinycssloaders test"
author: "Andras Sali"
date: "12/16/2017"
output: 
  html_document:
    includes:
      in_header: cssloaders_in_header.html
runtime: shiny
---

```{r setup, include=FALSE}
knitr::opts_chunk$set(echo = TRUE)
library(tidyverse)
```

## Including Plots

You can also embed plots, for example:

```{r pressure, echo=FALSE}
sliderInput("sleep_time","Sleep time:",1,10,5)
plotOutput("pressure") %>% shinycssloaders::withSpinner(type=1)
output$pressure <- renderPlot({
  Sys.sleep(input$sleep_time)
  plot(1:5,1:5)
})
```

Note that the `echo = FALSE` parameter was added to the code chunk to prevent printing of the R code that generated the plot.

If anyone has a good idea to add rmd support in a clean way, I'd be happy for a PR!

@daattali
Copy link
Owner

@mkinare @kent37 @pernaletec @xiangnandang @melissagwolf @M-Z @intael @robbfitzsimmons @seabbs @lvalnegri @fawda123 @juliasilge @pernaletec @razielar @MarcoFelipeKing

The latest github version of shinycssloaders should now support rmarkdown. Can you please try it and let me know if it works or not for you. Thanks!

@kent37
Copy link

kent37 commented Apr 19, 2023

My simple example works.

@melissagwolf
Copy link

@mkinare @kent37 @pernaletec @xiangnandang @melissagwolf @M-Z @intael @robbfitzsimmons @seabbs @lvalnegri @fawda123 @juliasilge @pernaletec @razielar @MarcoFelipeKing

The latest github version of shinycssloaders should now support rmarkdown. Can you please try it and let me know if it works or not for you. Thanks!

It works well for me! showPageSpinner() and hidePageSpinner() were very easy to implement in my rmarkdown.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

7 participants