I have a general shiny question, rather than a specific call to help with code.
I've just finished creating a branded shiny template using a mix of fresh and some custom CSS. It's branded and looks sharp for the dashboards I'm building for my organisation. However, I've got one eye on how to maintain the look and feel of our dashboards as our analytics team continues to grow and mature. When we have 50 apps deployed and we want to change the theme, we will have to go through and update the code in 50 different shinyapps if we continue with the current method of fresh and custom CSS in www/.
Does anyone have experience in centralising themes? Perhaps serving CSS from somewhere external to Shiny and calling that at the start of app.R? This would likely require me to unify fresh and my additional custom CSS into a single .css file, but that's something I'm willing to do.
Alternatively, perhaps someone clever has already developed a package to solve this problem?
Any general pointers warmly received.
For those who might be interested, I built the theme into a package I was developing. Within my package's root dir, I added an inst folder and, inside that, a www dir. I added the organisation logo and some extra CSS that couldn't be easily configured with {fresh}.
To set the theme, I created a function like this:
set_theme<-function(){
theme<-fresh::create_theme(
fresh::adminlte_color(
light_blue = "#383F48",
aqua = "#094357",
green = "#094357",
blue = "#383F48"
),
fresh::adminlte_global(
box_bg = "#FFFFFF",
info_box_bg = "#D1E0E5"
),
fresh::adminlte_vars(
"sidebar-width" = "275px",
"sidebar-dark-bg" = "#3A3F46",
"sidebar-dark-hover-color" = "#FFB151",
"btn-border-radius" = "1px"
)
)
shiny::addResourcePath('www', system.file("www", package = "myPackage"))
return(theme)
}
I used shiny::addResourcePath to create a resource path to link the package's inst/www folder to the project within which myPackage::set_theme() is called. Then, within my Shiny dashboards:
library(myPackage)
theme <- myPackage::set_theme()
ui<-(
fresh::use_theme(theme)
...
tags$head(tags$link(rel = "stylesheet", type = "text/css", href =
"www/style.css"))
...
)
Make note of the www/ in the href argument. If you were to keep these files 'locally' within your Shiny app, you'd create a www dir in root and forego the www/ prefix. When calling files from your package, simply include the www/
Related
I'm trying to attach a custom css stylesheet to style my shiny app. However I haven't had much success. I know that there is many ways to do it, but according to chapter 4 of Outstanding UI with Shiny (right after figure 4.3) the best way to do so seems to be with htmlDependency() and tagList().
I have created my dependency as shown below:
css_dependency <- function() {
htmlDependency(
name = "css dep",
version = "1.0",
src = "./www",
stylesheet = "styles.css"
)
}
However I am not sure how to attach it and where to attach it. With my browser developer tool, I can see that most dependencies seems to be in the <head> tag. Should I attach it like that?
tagList(head, css_dependency())
Also, I am not quite sure where I should call the tagList() function. Would that be directly in ui?
I can't seem to find many information on that method so any help would be appreciated. Thank you.
I think htmlDependency() is designed to be use with package. It constructs path to the file using passed arguments, but I think it won't work if there is no package. I made some experiments and it looks like it locates library directory and constructs path using name, version etc. So it doesn't make sense (at least because version is included in the path) without package.
Let's now divide the answer if we have a package or not:
1. A package.
You are close, but I would:
css_dependency <- function() {
htmlDependency(
name = "css_dep",
version = utils::packageVersion("name_of_your_package"),
package = "name_of_your_package",
src = "./www",
stylesheet = "styles.css"
)
}
Assuming styles.css file is inside www directory and www directory is inside inst directory (inst directory is use to install elements inside as is when package is installed by user), I have changed:
space from name - replaced by _ (just for safe);
hard-coded version number - now the version number will be the same as package version number (for convenience);
new argument added - package name;
And how to use it?
tagList() makes it possible to return (e.g. by a function) multiple tags - it is e.g. needed in modules, without tagList() the function would return only the last tag. The usage is:
library(shiny)
tagList(tags$h1("h1"), tags$h2("h2"))
And it returns:
<h1>h1</h1>
<h2>h2</h2>
i.e. both tags at once.
I'm talking about this, because your example (tagList(head, css_dependency())) is wrong, inside tagList() you need to use tags in the same way like in the UI part of app directly.
How to include dependency then? It will be just:
tags$head(css_dependency())
Because you are right that dependencies should be inside head tag. And there is no need to use tagList() directly in the UI part (use it if you need to return multiple tags by some function which will be used in the UI part)
2. Not a package.
We should use different approach than htmlDependency(). I would say:
tags$head(tags$link(rel = "stylesheet", type = "text/css", href = "path_to_the_file"))
"path_to_the_file" is:
relative to the directory where is a root of the application (i.e. where app.R or server.R and ui.R lives), so if you made a directory css/my_stylesheet.css, then it will be: tags$head(tags$link(rel = "stylesheet", type = "text/css", href = "css/my_stylesheet.css"));
however if you - the same as in your example - put your css file in www directory and www directory will be in the same directory where is a root of the application (i.e. not in some subdirectories), then you won't include www in path, because Shiny is designed to detect www directory automatically as a directory where will be css and js files., so in this case if my_stylesheet.css will be in www directory, the path will be: tags$head(tags$link(rel = "stylesheet", type = "text/css", href = "my_stylesheet.css"))
So I am following the guide here which indicates the way to access photos is as follows:
flags <- c(
system.file("img", "flag", "au.png", package = "ggpattern"),
system.file("img", "flag", "dk.png", package = "ggpattern")
)
My goal is to now use this code for my own uses, so I saved a few images in a folder. Here is my directory:
"C:/Users/Thom/Docs/Misc/Testy"
And within the Testy folder, there is a folder called image, holding 3 images. But the following doesn't seem to work and idk why...
images <- c(
system.file("image", "image1.png", package = "ggpattern"),
system.file("image", "image2.png", package = "ggpattern")
)
system.file is for use when a file included in a package. Basically, it will look for the file starting its search path to where your R packages are installed (because this can vary from user to user). system.file will return the resolved path to the file locally
If you already know the absolute path on your local computer (i.e. "C:/Users/Thom/Docs/Misc/Testy") you can use that as just the input to a read function, e.g. readBin("C:/Users/Thom/Docs/Misc/Testy")
If you want to get a little fancy or are like me and can't ever remember which direction of a slash to use on which OS, you can also do something like this which will add in the OS specific path separator:
readBin(file.path("C:", "Users", "Thom", "Docs", "Misc", "Testy"))
Is there a better way for programmers within a group, looking to share a common style for shiny apps or rmarkdown docs, to access a css file from a single location rather than manually copying the desired file into the subcontents of each app or document?
The ideal outcome would be to place the file(s) on a github repo, then attach it to any shiny app or rmarkdown file with its web link, is this possible?
Thanks.
It might be easier to include your stylesheets in an R package. This would eliminate the need for external request each time your app loads or doc is opened. Place your css files in a folder in inst and write a single R function that sets the resource paths to your css and loads files accordingly.
Let's say your package has the following structure. (For this example, I'm naming the package mypkg)
mypkg/
R/
use_stylesheets.R
inst/
stylesheets/
styles.min.css
...
...
In use_stylesheets.R, create a function that loads the stylesheets into the head of the document (tags$head) using tags$link.
use_stylesheets <- function() {
shiny::addResourcePath(
"styles",
system.file("stylesheets", package = "mypkg")
)
shiny::tags$head(
shiny::tags$link(rel = "stylesheet", href = "styles.min.css")
)
}
Then in your app or Rmarkdown document, you can load the files into your app using: mypkg::use_stylesheets()
After some tinkering I was able to have success (waiting on coworkers to test on their machines) using these structures for adding css or html from a package into both Shiny Apps and Rmarkdown documents.
# For attaching a css file to an Rmarkdown file
use_package_style_rmd <- function(css_file = "my_css.css"){
# css file
file_name <- paste0("/", css_file)
file_path <- paste0(system.file("stylesheets", package = "my_pkg"), file_name)
shiny::includeCSS(path = file_path)
}
# For placing an HTML headers/footers in place
use_package_header <- function(html_file = "my_header.html"){
# HTML Header
file_name <- paste0("/", html_file)
file_path <- paste0(system.file("stylesheets", package = "my_pkg"), file_name)
shiny::includeHTML(path = file_path)
}
# For attaching css to a shiny app via resourcePath
use_package_style_shiny <- function(stylesheet = "my_css.css"){
# Add Resource Path
shiny::addResourcePath(
prefix = "styles",
directoryPath = system.file("stylesheets", package = "my_pkg"))
# Link to App
shiny::tags$head(
shiny::tags$link(rel = "stylesheet",
href = paste0("styles/", stylesheet)))
}
The use_package_style_rmd function can be placed in any code chunk, whereas the header function will add the html in-place where the function is run.
For the Shiny app use-case the function should be run to establish the resource path to the folder, then in the UI under fluidpage the theme option can be set to the css file using the prefix for the Resource Path styles/my_css.css.
One issue I am still having which may be a separate question, is where to place a supporting image file, and how to add the relative path such that the image can be placed in the header or footer.
I'm making an R Shiny webpage that uses a DT table with many columns, the first of which is an image. Currently, I've found two different ways to get these images to show up and want to know if there are any others as neither is fitting my needs.
METHOD 1 - Point to an existing web hosted iamge
The first, is to have a column in the dataframe used to build to the table that follows this format:
<img src='URL_TO_IMG.png' height='200'></img>
where URL_TO_IMG.png links to a pre-existing, already hosted, web image.
METHOD 2 - encode image file to a data URI
The other is to use download the images locally and instead use the following:
<img src='RESULTS_OF_KNITR_IMAGE_URI' height='200'></img>
Where RESULTS_OF_KNITR_IMAGE_URI are the results of the knitr::image_uri(x) function call where x is the path to my local image. From the knitr documentation this function:
...can encode an image file as a base64 string, which can be used in the img tag in HTML.
Findings:
METHOD 1 works great, but has the problem were I want to post process the web hosted images to be a smaller size. Sometimes they'll be very large and take a long time to load when I really only need a 200 pixel width image, at most. Also, I'd like to have more control over the location of the image files.
METHOD 2's data URI approach allows me to locally pre-process the images to fit nicely into the columns and be smaller. However, since I have 200+ images, this causes the application to have a 10+ second load time as I believe the app loads ALL the images at once this way.
I'm thinking I may want to host my post processed images on an image hosting site and point to the URL like METHOD 1 but am unsure. Any recommendations?
If anyone wants to play with my setup, I made a minimum working example:
library(shiny)
library(DT)
# read in CSV with table information
my_image_df = read.csv('image_test_case_table.csv')
# Define UI
ui <- fluidPage(
fluidRow(
DTOutput("my_table", width = "100%")
)
)
# Define server
server <- function(input, output) {
output$my_table = DT::renderDataTable(
DT::datatable(
{data <- my_image_df},
escape = FALSE,
rownames = FALSE,
options = list(columnDefs = list(list(className = 'dt-center', targets = 0))),
selection = 'single'
)#datatable
)#renderDataTable
}
# Run the application
shinyApp(ui = ui, server = server)
Any ideas?
A little hidden in the Shiny documentation, you will find METHOD 3 that involves supplying the image files inside a www directory (example reference here or in the cheatsheet).
If your folder structure looks as follows
├── image_test_case.R
├── image_test_case_table.csv
├── www/
│ ├── amerikaan.jpg
│ └── tilia.jpg
then you may set the src tag to the image path relative to the www path.
<img src='amerikaan.jpg' height='200'></img>
<img src='tilia.jpg' height='200'></img>
Note that www is used for all sorts of static resources (most common images, javascript and css files).
I'm having some issues displaying an interactive map in R-Shiny that incorporates a KML file. I installed leaflet-plugins and was able to create an HTML file that displays properly by itself in the browser but not within Shiny. This attempt followed an example here - the code is available if you view source.
Because this initial version does not require the HTML itself to change, I attempted to follow the samples here to include the raw HTML in my page but I receive a 404 error with these as well as when I attempted to include it as an iframe within R-Shiny (following this discussion). I then set up an external facing server for both the KML file and the HTML file and got the same result.
I was able to get a map working without the KML file with leaflet-shiny but I'm frankly not sure how to add the KML file and don't see this in the documentation.
Finally, I tried rMaps which does have a "addKML" method, but I can't get it working with various locations of files on my server (
map1 = Leaflet$new()
map1$setView(c(45.5236, -122.675), 13)
map1$tileLayer("http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png")
map1$addKML('http://kml-samples.googlecode.com/svn/trunk/kml/Placemark/placemark.kml')
map1
It works without the $addKML line. It might be worth noting that the tilelayer line on example 1 here also resulted in a blank map.
I actually have some possibly similar issues hosting derived title layers that are still unresolved which is one reason I opted for uses KML layers for this demo version of the application, which is why I tagged networking on here as well. I'm using Digital Ocean.
Thank you for any thoughts or pointers you may have.
I think there may be a small issue in the rMaps library. If you inspect the config.yml file
https://github.com/ramnathv/rCharts/blob/master/inst/libraries/leaflet/config.yml you will see that for
content delevery network (cdn) there is reference to
"http://harrywood.co.uk/maps/examples/leaflet/leaflet-plugins/layer/vector/KML.js". This KML reader is a plugin for leaflet from https://github.com/shramov/leaflet-plugins/blob/master/layer/vector/KML.js. When content is delivered locally:
css: [external/leaflet.css, external/leaflet-rCharts.css, external/legend.css]
jshead:
- external/leaflet.js
- external/leaflet-providers.js
- external/Control.FullScreen.js
there is no reference to this javascript file. We can fix this:
require(yaml)
leafletLib <- file.path(find.package("rMaps"), "libraries", "leaflet")
rMapsConfig <- yaml.load_file(file.path(leafletLib, "config.yml"))
# add a kml library
kmlLib <- readLines("http://harrywood.co.uk/maps/examples/leaflet/leaflet-plugins/layer/vector/KML.js")
write(kmlLib, file.path(leafletLib, "external", "leaflet-kml.js"))
# add the library to config.yml
rMapsConfig$leaflet$jshead <- union(rMapsConfig$leaflet$jshead , "external/leaflet-kml.js")
write(as.yaml(rMapsConfig), file.path(leafletLib, "config.yml"))
Now the config.yml will contain the necessary link to the KML reader and there is a local copy stored now in external/leaflet-kml.js. Our example still wont work however as we will get a Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://kml-samples.googlecode.com/svn/trunk/kml/Placemark/placemark.kml. This can be fixed by moving the resource to the same domain or enabling CORS.
We will need to have this file served locally. We can place it as a temporary measure in the leaflet folder in the rMaps package. When a map is created this folder gets copied to a temporary file:
require(rMaps)
map1 = Leaflet$new()
map1$setView(c(45.5236, -122.675), 13)
map1$tileLayer("http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png")
map1$addKML('leaflet/placemark.kml')
# temp copy http://kml-samples.googlecode.com/svn/trunk/kml/Placemark/placemark.kml
# to rMaps
sampleKml <- readLines('http://kml-samples.googlecode.com/svn/trunk/kml/Placemark/placemark.kml')
write(sampleKml, file.path(leafletLib, 'placemark.kml'))
# finally try the map
map1
# remove the temp file
file.remove(file.path(leafletLib, 'placemark.kml'))
UPDATE:
There is an addAssets method in rCharts which allows you to add .js files. This allows us to simpilfy things and doesnt require us to write a copy of the js file nor edit the config.yml file.
require(rMaps)
map1 = Leaflet$new()
map1$setView(c(45.5236, -122.675), 13)
map1$tileLayer("http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png")
map1$addAssets(css = NULL, jshead = 'http://harrywood.co.uk/maps/examples/leaflet/leaflet-plugins/layer/vector/KML.js')
map1$addKML('leaflet/placemark.kml')
leafletLib <- file.path(find.package("rMaps"), "libraries", "leaflet")
sampleKml <- readLines('http://kml-samples.googlecode.com/svn/trunk/kml/Placemark/placemark.kml')
write(sampleKml, file.path(leafletLib, 'placemark.kml'))
# finally try the map
map1
# remove the temp file
file.remove(file.path(leafletLib, 'placemark.kml'))