├── .gitignore ├── README.md ├── api └── plumber.R ├── plumber-tutorial.Rproj ├── pres ├── README.md ├── RWithFriends.Rmd ├── assets │ ├── Francis_Crick_Institute.png │ ├── Ministry_of_Defence.png │ ├── Pragmatic.png │ ├── Rlogo.png │ ├── Rlogo_fade.png │ ├── University_of_Manchester.png │ ├── biofilm.jpg │ ├── friends.jpg │ ├── gandalf.jpg │ ├── github-screenshot.png │ ├── hamburg6_full.jpg │ ├── hastings.png │ ├── nhs.png │ ├── rage1.jpg │ ├── rage2.jpg │ ├── rage3.png │ ├── royal_statistical_society.jpg │ ├── shell.png │ ├── sustrans.png │ ├── twitter.gif │ ├── white_logo.png │ ├── white_logo_full.png │ └── yorkshire.png ├── libs │ └── remark-css │ │ └── default.css └── style.css └── serve_local.R /.gitignore: -------------------------------------------------------------------------------- 1 | .Rproj.user 2 | .Rhistory 3 | .RData 4 | .Ruserdata 5 | 6 | # compiled README 7 | *.html 8 | 9 | # cached presentation files 10 | /pres/RWithFriends_cache/* 11 | /pres/RWithFriends_files/* 12 | 13 | # demo scratch directory 14 | /api-demo/* -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Short Tutorial on Plumber 2 | 3 | First make sure you have the `plumber` and `textcat` packages installed in your library (use `install.packages()`). 4 | 5 | To have a play with this you can simly clone this repo but I will also walk through the steps in this readme so you can do it from scratch if needed. 6 | 7 | ## Introduction 8 | 9 | In this tutorial we will go through the following steps to create your plumber API. 10 | 11 | [1. Write your function(s)](#1-write-your-function(s)) 12 | 13 | [2. Add the plumber special comments](#2-add-the-plumber-special-comments) 14 | 15 | [3. Try it out!](#3-try-it-out) 16 | 17 | [4. Record the traffic](#4-record-the-traffic) 18 | 19 | ## Resources 20 | 21 | Plumber exists thanks to the wonderful work of Jeff Allen ([Github](https://github.com/trestletech), [Twitter](https://twitter.com/trestlejeff)). Go on give him a star, you know you want to. It seems that Jeff is now working at Rstudio and there is a fork of plumber on Rstudio. Not sure which is the one to watch but [here](https://github.com/trestletech/plumber) is the original and [here](https://github.com/rstudio/plumber) is Rstudios fork. You shouldn't start using plumber without having a look at the [fantastic documentation](https://www.rplumber.io/). 22 | 23 | When you're done with all of this, check out the section at the bottom for some [further reading](#deployment-and-further-reading). 24 | 25 | ## Tutorial 26 | 27 | ### 1. Write your function(s) 28 | 29 | Start a new directory that will house the API, make a file called `plumber.R`, save it in a directory called `api` and write a function definition, one that you want to expose to the API. For example here I am using the textcat package to guess the language of user text input. 30 | 31 | ```r 32 | library(plumber) 33 | library(textcat) 34 | 35 | guessLanguage = function(txt) { 36 | 37 | input = txt 38 | 39 | output = textcat(txt) 40 | 41 | return(output) 42 | 43 | } 44 | ``` 45 | 46 | This function takes a character (string) value, executes the `textcat()` function and then returns the results of `textcat()`. Run this code and then try something like `guessLanguage("Hallo")` to make sure it works. 47 | 48 | Easy. 49 | 50 | ### 2. Add the plumber special comments 51 | 52 | Next we need to add some special comments (decorations) to the file that will act as instructions to plumber on how to interpret your file and how to serve the contents. At a mimimum we need to name the endpoint and give it an address. All plumber comments start with `#*` rather than `#`, this distinguishes them from actual comments in the script. So to our `plumber.R` file above we would add the following just above the function definition. 53 | 54 | ```r 55 | #* @param txt A string value, the text to categorise. 56 | #* @get /textcat 57 | ``` 58 | 59 | The `@param` tag will define a parameter input for the function and the `@get` tag will define the http method used. So our file would now look like this: 60 | 61 | ```r 62 | library(plumber) 63 | library(textcat) 64 | 65 | #* @param txt A string value, the text to categorise. 66 | #* @get /textcat 67 | guessLanguage = function(txt) { 68 | 69 | input = txt 70 | 71 | output = textcat(txt) 72 | 73 | return(output) 74 | 75 | } 76 | ``` 77 | 78 | This is we're now ready to try out our first API. 79 | 80 | ### 3. Try it out! 81 | 82 | To serve the API locally on our machines, we should now open a new script file, I called mine `serve_local.R` and save it in the parent directory of this project. I use this script to store a few commands that will set up a local server and run the API. The script should use the `plumb()` function to run the server: 83 | 84 | ```r 85 | library(plumber) 86 | 87 | p = plumb(dir = "api") 88 | p$run(port = 8000) 89 | ``` 90 | 91 | We are telling plumber that our plumber files are in the directory called `api`, change this if needed. We then tell plumber to run the api on port `8000`. You will see some output in the console something like this: 92 | 93 | ``` 94 | Starting server to listen on port 8000 95 | Running the swagger UI at http://127.0.0.1:8000/__swagger__/ 96 | ``` 97 | 98 | And you will not get your prompt back, R is still busy. 99 | 100 | Next you should copy the URL `http://127.0.0.1:8000/__swagger__/` into your browser and there you have it, your API is running and you have a nice swagger UI to test it with. If you expand the `/textcat` box and click 'Try it out' you will get a box that you can type into. Type something in and click execute to see some example ouput. 101 | 102 | ### 4. Record the traffic 103 | 104 | That was beautifully simple lets add some more functionality. We're going to add a filter. Filters are used to execute some logic on all incoming requests before they reach their endpoints. We will use a filter example from the plumber website to print some information about the request into the log file. 105 | 106 | This is a good time to mention the request and the result objects. When your R code is running, there will be two objects available in the environment, `req` containing information about the request and `res` containing information about the result. These can be accessed by your functions. For example, we could have a filter that prints some information from the request out as follows: 107 | 108 | ```r 109 | logger = function(req){ 110 | 111 | cat("\n", as.character(Sys.time()), 112 | "\n", req$REQUEST_METHOD, req$PATH_INFO, 113 | "\n", req$HTTP_USER_AGENT, "@", req$REMOTE_ADDR) 114 | 115 | plumber::forward() 116 | 117 | } 118 | ``` 119 | 120 | This function will print out various parts of the `req` object to the console (and probably and nginx log buried on the server if you have deployed your API) but in reality you might want to store this in your own log file somwhere or even better in a database so you can analyse the traffic to your API. Note that (a) we passed the `req` object into the function as an argument and (b) we then used the `forward()` function to pass the request on (either to the next filter or to its endpoint). 121 | 122 | Your final API plumber.R file should now look like this: 123 | 124 | ```r 125 | # plumber.R 126 | library(plumber) 127 | library(textcat) 128 | 129 | #* Print to log 130 | #* @filter logger 131 | logger = function(req){ 132 | 133 | cat("\n", as.character(Sys.time()), 134 | "\n", req$REQUEST_METHOD, req$PATH_INFO, 135 | "\n", req$HTTP_USER_AGENT, "@", req$REMOTE_ADDR) 136 | 137 | plumber::forward() 138 | 139 | } 140 | 141 | #* @param txt A string value, the text to categorise. 142 | #* @get /textcat 143 | #* @description Perform language categorisation 144 | guessLanguage = function(txt) { 145 | 146 | input = txt 147 | 148 | output = textcat(txt) 149 | 150 | cat( 151 | "\n Input: ", input, 152 | "\n Most likely: ", output 153 | ) 154 | 155 | return(output) 156 | 157 | } 158 | ``` 159 | 160 | Notice the decorations above the filter, we used @filter to specify we were creating a filter. 161 | 162 | ## Deployment and Further Reading 163 | 164 | Ok now that you have your API running locally its time to deploy it to a remote server. If you are doing this for the first time, I highly recommend using the plumber + analogsea + Digital Ocean option which is [documented here](https://www.rplumber.io/docs/hosting.html#digitalocean). 165 | 166 | Whats that? You got your plumber system up and running on Digital Ocean already? And now you are thinking about the next increment in scale? Ok the Digital Ocean is running on a single node and getting blocked up by requests. I might have something for you. I recently discovered [this blog and its [accompanying repo](https://github.com/MarkEdmondson1234/serverless-R-API-appengine) by [Mark Edmonson](http://code.markedmondson.me/), who seems to be developing some great cloud utilities for R. I am going to try it out soon but if you get there before me then let me know how you get on. 167 | 168 | 169 | 170 | -------------------------------------------------------------------------------- /api/plumber.R: -------------------------------------------------------------------------------- 1 | # plumber.R 2 | library(plumber) 3 | library(textcat) 4 | 5 | #* Print to log 6 | #* @filter logger 7 | logger = function(req){ 8 | 9 | cat("\n", as.character(Sys.time()), 10 | "\n", req$REQUEST_METHOD, req$PATH_INFO, 11 | "\n", req$HTTP_USER_AGENT, "@", req$REMOTE_ADDR) 12 | 13 | plumber::forward() 14 | 15 | } 16 | 17 | #* @description Perform language categorisation 18 | #* @param txt A string value, the text to categorise. 19 | #* @get /textcat 20 | guessLanguage = function(txt) { 21 | 22 | input = txt 23 | 24 | output = textcat(txt) 25 | 26 | cat( 27 | "\n Input: ", input, 28 | "\n Most likely: ", output 29 | ) 30 | 31 | return(output) 32 | 33 | } 34 | -------------------------------------------------------------------------------- /plumber-tutorial.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: Default 4 | SaveWorkspace: Default 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 2 10 | Encoding: UTF-8 11 | 12 | RnwWeave: Sweave 13 | LaTeX: pdfLaTeX 14 | -------------------------------------------------------------------------------- /pres/README.md: -------------------------------------------------------------------------------- 1 | ## Notes 2 | 3 | * The assets folder should only contain images from the package, not other presentation images. This will allow 4 | us to implement an upgrade script. 5 | * Ditto for the css file. Any changes should be made to `user.css` or as a PR. -------------------------------------------------------------------------------- /pres/RWithFriends.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "R With Friends" 3 | subtitle: "How to integrate with other technologies" 4 | author: '`r jrPresentation::get_author()`' 5 | output: 6 | xaringan::moon_reader: 7 | css: ["default", "style.css"] 8 | lib_dir: libs 9 | nature: 10 | highlightStyle: github 11 | highlightLines: true 12 | countIncrementalSlides: false 13 | editor_options: 14 | chunk_output_type: console 15 | --- 16 | 17 | ```{r setup, include=FALSE, message=FALSE} 18 | options(htmltools.dir.version = FALSE) 19 | library("jrPresentation") 20 | set_presentation_options() 21 | ``` 22 | 23 | layout: true 24 | `r add_border(inverse=TRUE)` 25 | --- 26 | 27 | # github.com/rmnppt/plumber tutorial 28 | 29 | ## roman@jumpingrivers.com / @rmnppt 30 | 31 | ![github-repo](assets/github-screenshot.png) 32 | 33 | --- 34 | 35 | layout: false 36 | `r add_border(inverse = TRUE)` 37 | 38 | --- 39 | 40 | # My Story (relevant I promise!) 41 | 42 | ![biofilms](assets/biofilm.jpg) 43 | 44 | --- 45 | 46 | # Expose R to other people/things 47 | 48 | .pull-left[ 49 | 50 | ## Who needs plumber? 51 | 52 | - Reporting, Automated Reporting 53 | 54 | - Publishing, Web Publishing 55 | 56 | - Building systems/software 57 | 58 | ] 59 | 60 | .pull-right[ 61 | 62 | ![friends](assets/friends.jpg) 63 | 64 | ] 65 | 66 | --- 67 | 68 | # Expose R to other people/things 69 | 70 | .pull-left[ 71 | 72 | ## Who needs plumber? 73 | 74 | - ~~Reporting, Automated Reporting~~ 75 | 76 | - ~~Publishing, Web Publishing~~ 77 | 78 | - Building systems/software 79 | ] 80 | 81 | .pull-right[ 82 | 83 | ![friends](assets/friends.jpg) 84 | 85 | ] 86 | 87 | --- 88 | 89 | # Expose R to other people/things 90 | 91 | 92 | .pull-left[ 93 | 94 | - Web/Dashboard integrations 95 | 96 | - App developers 97 | 98 | - DataBase Developers 99 | 100 | - Data Scientists 101 | 102 | - Other back-end systems 103 | 104 | - And so on... 105 | 106 | ] 107 | 108 | .pull-right[ 109 | 110 | ![friends](assets/friends.jpg) 111 | 112 | ] 113 | 114 | --- 115 | 116 | layout: true 117 | `r add_border(inverse=TRUE)` 118 | 119 | --- 120 | 121 | # One option: A REST API 122 | 123 | `www.some-address.de/some-functionality?with-parameters` 124 | 125 | Examples: 126 | 127 | `https://duckduckgo.com/?q=hamburg+R+user+group` 128 | 129 | `https://www.imdb.com/find?q=batman&s=tt` 130 | 131 | --- 132 | 133 | # `plumber` to the rescue 134 | 135 | - Creates a REST API 136 | 137 | - No specialist knowledge required to get started 138 | 139 | - Very easy to adapt existing code 140 | 141 | - Great documentation: https://www.rplumber.io/ 142 | 143 | Follow development: 144 | 145 | Jeff Allen: https://github.com/trestletech/plumber 146 | 147 | Rstudio: https://github.com/rstudio/plumber 148 | 149 | --- 150 | 151 | class: center, middle inverse 152 | 153 | # Demo 154 | 155 | --- 156 | 157 | # My use cases 158 | 159 | - Providing forecasting interface for disease outbreak detection. 160 | 161 | - Providing recommendation algorithm for business. 162 | 163 | - Exposing SEO algorithms for marketing developers. 164 | 165 | --- 166 | 167 | # Deployment Basic 168 | 169 | - Start with built in digital ocean method* 170 | 171 | - Entry point $5 per month 172 | 173 | - `plumber::do_deploy_api` 174 | 175 | ```{r echo=FALSE, out.width="200px"} 176 | knitr::include_graphics("assets/rage1.jpg") 177 | ``` 178 | 179 | .footnote[[*] Plumber Docs https://www.rplumber.io/docs/hosting.html] 180 | 181 | --- 182 | 183 | # Deployment Advanced 184 | 185 | - install web server manually and configure resources (Docker Compose or pm2)* 186 | 187 | - multiple R processes running concurrently 188 | 189 | - harder to do but more scaleable 190 | 191 | ```{r echo=FALSE, out.width="250px"} 192 | knitr::include_graphics("assets/rage2.jpg") 193 | ``` 194 | 195 | .footnote[[*] Plumber Docs https://www.rplumber.io/docs/hosting.html] 196 | 197 | --- 198 | 199 | # Deployment Experimental 200 | 201 | - Configure app engine on Google Cloud Compute* 202 | 203 | - Harder, less support but most scaleable and fully managed 204 | 205 | ```{r echo=FALSE, out.width="300px"} 206 | knitr::include_graphics("assets/rage3.png") 207 | ``` 208 | 209 | .footnote[[*] Mark Edmonson https://github.com/MarkEdmondson1234/serverless-R-API-appengine] 210 | 211 | --- 212 | 213 | # Appendix 214 | 215 | - Who is https://www.blockspring.com/ ? 216 | 217 | - How could you forget! [R Studio Connect](https://www.rstudio.com/products/connect/) 218 | 219 | - What about [Apple Core ML](https://developer.apple.com/documentation/coreml) or [Google ML Kit](https://developers.google.com/ml-kit/) 220 | 221 | - Others including (Microsoft/Azure, H2o Steam, Fiery, AWS/Lambda) 222 | 223 | --- 224 | 225 | # [jumpingrivers.com/dates](https://www.jumpingrivers.com/dates) 226 | 227 | ## Hamburg 228 | 229 | - Wed Jun 13 2018 - Introduction to R 230 | 231 | - Thu Jun 14 2018 - Mastering the tidyverse 232 | 233 | - Fri Jun 15 2018 - Next Steps in the Tidyverse 234 | 235 | - Wed Jun 20 2018 - Automated Reporting (first steps towards Shiny) 236 | 237 | - Thu Jun 21 2018 - Interactive Graphics with Shiny 238 | 239 | - Wed Jun 27 2018 - Predictive Analytics 240 | 241 | --- 242 | 243 | # Get started 244 | 245 | - github.com/rmnppt/plumber-tutorial 246 | 247 | - www.rplumber.io 248 | 249 | --- 250 | -------------------------------------------------------------------------------- /pres/assets/Francis_Crick_Institute.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rmnppt/plumber-tutorial/062b6c54ecfb9e4b00e69e3e0ea90066e89388ce/pres/assets/Francis_Crick_Institute.png -------------------------------------------------------------------------------- /pres/assets/Ministry_of_Defence.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rmnppt/plumber-tutorial/062b6c54ecfb9e4b00e69e3e0ea90066e89388ce/pres/assets/Ministry_of_Defence.png -------------------------------------------------------------------------------- /pres/assets/Pragmatic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rmnppt/plumber-tutorial/062b6c54ecfb9e4b00e69e3e0ea90066e89388ce/pres/assets/Pragmatic.png -------------------------------------------------------------------------------- /pres/assets/Rlogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rmnppt/plumber-tutorial/062b6c54ecfb9e4b00e69e3e0ea90066e89388ce/pres/assets/Rlogo.png -------------------------------------------------------------------------------- /pres/assets/Rlogo_fade.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rmnppt/plumber-tutorial/062b6c54ecfb9e4b00e69e3e0ea90066e89388ce/pres/assets/Rlogo_fade.png -------------------------------------------------------------------------------- /pres/assets/University_of_Manchester.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rmnppt/plumber-tutorial/062b6c54ecfb9e4b00e69e3e0ea90066e89388ce/pres/assets/University_of_Manchester.png -------------------------------------------------------------------------------- /pres/assets/biofilm.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rmnppt/plumber-tutorial/062b6c54ecfb9e4b00e69e3e0ea90066e89388ce/pres/assets/biofilm.jpg -------------------------------------------------------------------------------- /pres/assets/friends.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rmnppt/plumber-tutorial/062b6c54ecfb9e4b00e69e3e0ea90066e89388ce/pres/assets/friends.jpg -------------------------------------------------------------------------------- /pres/assets/gandalf.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rmnppt/plumber-tutorial/062b6c54ecfb9e4b00e69e3e0ea90066e89388ce/pres/assets/gandalf.jpg -------------------------------------------------------------------------------- /pres/assets/github-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rmnppt/plumber-tutorial/062b6c54ecfb9e4b00e69e3e0ea90066e89388ce/pres/assets/github-screenshot.png -------------------------------------------------------------------------------- /pres/assets/hamburg6_full.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rmnppt/plumber-tutorial/062b6c54ecfb9e4b00e69e3e0ea90066e89388ce/pres/assets/hamburg6_full.jpg -------------------------------------------------------------------------------- /pres/assets/hastings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rmnppt/plumber-tutorial/062b6c54ecfb9e4b00e69e3e0ea90066e89388ce/pres/assets/hastings.png -------------------------------------------------------------------------------- /pres/assets/nhs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rmnppt/plumber-tutorial/062b6c54ecfb9e4b00e69e3e0ea90066e89388ce/pres/assets/nhs.png -------------------------------------------------------------------------------- /pres/assets/rage1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rmnppt/plumber-tutorial/062b6c54ecfb9e4b00e69e3e0ea90066e89388ce/pres/assets/rage1.jpg -------------------------------------------------------------------------------- /pres/assets/rage2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rmnppt/plumber-tutorial/062b6c54ecfb9e4b00e69e3e0ea90066e89388ce/pres/assets/rage2.jpg -------------------------------------------------------------------------------- /pres/assets/rage3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rmnppt/plumber-tutorial/062b6c54ecfb9e4b00e69e3e0ea90066e89388ce/pres/assets/rage3.png -------------------------------------------------------------------------------- /pres/assets/royal_statistical_society.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rmnppt/plumber-tutorial/062b6c54ecfb9e4b00e69e3e0ea90066e89388ce/pres/assets/royal_statistical_society.jpg -------------------------------------------------------------------------------- /pres/assets/shell.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rmnppt/plumber-tutorial/062b6c54ecfb9e4b00e69e3e0ea90066e89388ce/pres/assets/shell.png -------------------------------------------------------------------------------- /pres/assets/sustrans.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rmnppt/plumber-tutorial/062b6c54ecfb9e4b00e69e3e0ea90066e89388ce/pres/assets/sustrans.png -------------------------------------------------------------------------------- /pres/assets/twitter.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rmnppt/plumber-tutorial/062b6c54ecfb9e4b00e69e3e0ea90066e89388ce/pres/assets/twitter.gif -------------------------------------------------------------------------------- /pres/assets/white_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rmnppt/plumber-tutorial/062b6c54ecfb9e4b00e69e3e0ea90066e89388ce/pres/assets/white_logo.png -------------------------------------------------------------------------------- /pres/assets/white_logo_full.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rmnppt/plumber-tutorial/062b6c54ecfb9e4b00e69e3e0ea90066e89388ce/pres/assets/white_logo_full.png -------------------------------------------------------------------------------- /pres/assets/yorkshire.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rmnppt/plumber-tutorial/062b6c54ecfb9e4b00e69e3e0ea90066e89388ce/pres/assets/yorkshire.png -------------------------------------------------------------------------------- /pres/libs/remark-css/default.css: -------------------------------------------------------------------------------- 1 | a, a > code { 2 | color: rgb(249, 38, 114); 3 | text-decoration: none; 4 | } 5 | .footnote { 6 | position: absolute; 7 | bottom: 3em; 8 | padding-right: 4em; 9 | font-size: 90%; 10 | } 11 | .remark-code-line-highlighted { background-color: #ffff88; } 12 | 13 | .inverse { 14 | background-color: #272822; 15 | color: #d6d6d6; 16 | text-shadow: 0 0 20px #333; 17 | } 18 | .inverse h1, .inverse h2, .inverse h3 { 19 | color: #f3f3f3; 20 | } 21 | /* Two-column layout */ 22 | .left-column { 23 | color: #777; 24 | width: 20%; 25 | height: 92%; 26 | float: left; 27 | } 28 | .left-column h2:last-of-type, .left-column h3:last-child { 29 | color: #000; 30 | } 31 | .right-column { 32 | width: 75%; 33 | float: right; 34 | padding-top: 1em; 35 | } 36 | .pull-left { 37 | float: left; 38 | width: 47%; 39 | } 40 | .pull-right { 41 | float: right; 42 | width: 47%; 43 | } 44 | .pull-right ~ * { 45 | clear: both; 46 | } 47 | img, video, iframe { 48 | max-width: 100%; 49 | } 50 | blockquote { 51 | border-left: solid 5px lightgray; 52 | padding-left: 1em; 53 | } 54 | table { 55 | margin: auto; 56 | border-top: 1px solid #666; 57 | border-bottom: 1px solid #666; 58 | } 59 | table thead th { border-bottom: 1px solid #ddd; } 60 | th, td { padding: 5px; } 61 | thead, tfoot, tr:nth-child(even) { background: #eee } 62 | 63 | @page { margin: 0; } 64 | @media print { 65 | .remark-slide-scaler { 66 | width: 100% !important; 67 | height: 100% !important; 68 | transform: scale(1) !important; 69 | top: 0 !important; 70 | left: 0 !important; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /pres/style.css: -------------------------------------------------------------------------------- 1 | @import url(https://fonts.googleapis.com/css?family=Yanone+Kaffeesatz); 2 | @import url(https://fonts.googleapis.com/css?family=Droid+Serif:400,700,400italic); 3 | @import url(https://fonts.googleapis.com/css?family=Source+Code+Pro:400,700); 4 | 5 | .title-slide { 6 | background-image: url(assets/white_logo.png); 7 | background-size: cover; 8 | } 9 | 10 | body { font-family: 'Droid Serif', 'Palatino Linotype', 'Book Antiqua', Palatino, 'Microsoft YaHei', 'Songti SC', serif; } 11 | h1, h2, h3 { 12 | font-family: 'Yanone Kaffeesatz'; 13 | font-weight: normal; 14 | } 15 | a, a > code { 16 | color: rgb(249, 38, 114); 17 | text-decoration: none; 18 | } 19 | .footnote { 20 | position: absolute; 21 | bottom: 3em; 22 | padding-right: 4em; 23 | font-size: 90%; 24 | } 25 | .remark-code, .remark-inline-code { font-family: 'Source Code Pro', 'Lucida Console', Monaco, monospace; } 26 | .remark-code-line-highlighted { background-color: #ffff88; } 27 | 28 | .inverse { 29 | background-color: #272822; 30 | color: #d6d6d6; 31 | text-shadow: 0 0 20px #333; 32 | } 33 | .inverse h1, .inverse h2, .inverse h3 { 34 | color: #f3f3f3; 35 | line-height: 1.0em; 36 | } 37 | /* Two-column layout */ 38 | .left-column { 39 | color: #777; 40 | width: 20%; 41 | height: 92%; 42 | float: left; 43 | } 44 | .left-column h2:last-of-type, .left-column h3:last-child { 45 | color: #000; 46 | } 47 | .right-column { 48 | width: 75%; 49 | float: right; 50 | padding-top: 1em; 51 | } 52 | .pull-left { 53 | float: left; 54 | width: 47%; 55 | } 56 | .pull-right { 57 | float: right; 58 | width: 47%; 59 | } 60 | .pull-right ~ * { 61 | clear: both; 62 | } 63 | img { 64 | max-width: 100%; 65 | } 66 | blockquote { 67 | border-left: solid 5px lightgray; 68 | padding-left: 1em; 69 | } 70 | 71 | /* Formatting for tables 72 | * https://github.com/yihui/hugo-xmin/blob/4888812fd8e4589f14f629f40136bae507471089/static/css/style.css#L43-L50 73 | */ 74 | 75 | table { 76 | margin: auto; 77 | border-top: 2px solid #666; 78 | border-bottom: 2px solid #666; 79 | } 80 | table thead th { border-bottom: 1px solid #ddd; } 81 | th, td { padding: 5px; } 82 | tr:nth-child(even) { background: #eee } 83 | 84 | /* Client images */ 85 | 86 | #rlogo img { 87 | width: 500px; 88 | margin-left: 150px; 89 | } 90 | 91 | #clients img { 92 | width: 150px; 93 | border: none; 94 | margin: 0; 95 | } 96 | 97 | img { 98 | box-shadow: none!important; 99 | border: none!important; 100 | } 101 | 102 | /* headers and footers */ 103 | .logo { 104 | float: left; 105 | padding: 5px 20px 5px 20px; 106 | height: 30px; 107 | } 108 | 109 | .social { 110 | font-size: 15px; 111 | float: right; 112 | color: #ebab4f; 113 | padding: 0px 20px 5px 20px; 114 | } 115 | 116 | .social img { 117 | height: 20px; 118 | } 119 | 120 | .social table { 121 | border-bottom: none; 122 | } 123 | 124 | /* Header/Footer stuff standard */ 125 | div.jr-header { 126 | background-color: #546e7a; 127 | color: #fff; 128 | position: fixed; 129 | top: 0px; 130 | left: 0px; 131 | height: 40px; 132 | width: 100%; 133 | text-align: left; 134 | } 135 | 136 | /* Remove border from twitter icon */ 137 | div.jr-header table { 138 | border-top: 0 solid; 139 | border-bottom: 0 solid; 140 | } 141 | 142 | div.jr-footer { 143 | background-color: #546e7a; 144 | color: white; 145 | position: absolute; 146 | bottom: 0px; 147 | left: 0px; 148 | height: 45px; 149 | width: 100%; 150 | } 151 | 152 | div.jr-footer span { 153 | font-size: 10pt; 154 | position: absolute; 155 | left: 15px; 156 | bottom: 15px; 157 | } 158 | 159 | /* Header/Footer stuff inverse */ 160 | div.jr-header-inverse{ 161 | background-color: #272822; 162 | color: #fff; 163 | position: fixed; 164 | top: 0px; 165 | left: 0px; 166 | height: 40px; 167 | width: 100%; 168 | text-align: left; 169 | } 170 | 171 | div.jr-footer-inverse { 172 | background-color: #272822; 173 | color: #d6d6d6; 174 | position: absolute; 175 | bottom: 0px; 176 | left: 0px; 177 | height: 45px; 178 | width: 100%; 179 | } 180 | 181 | div.jr-footer-inverse span { 182 | font-size: 10pt; 183 | position: absolute; 184 | left: 15px; 185 | bottom: 15px; 186 | } 187 | 188 | div.jr-header-inverse table { 189 | border-top: 0 solid; 190 | border-bottom: 0 solid; 191 | } 192 | 193 | /*Slide color */ 194 | .remark-slide-number { 195 | color: #FFF; 196 | } 197 | 198 | /* Change colour of strong */ 199 | strong { 200 | color: #af505a; 201 | } 202 | 203 | /* Remove slide number */ 204 | .remark-slide-number { 205 | display:none; 206 | 207 | } -------------------------------------------------------------------------------- /serve_local.R: -------------------------------------------------------------------------------- 1 | library(plumber) 2 | p = plumb(dir = "api-demo") 3 | p$run(port = 8000) 4 | 5 | --------------------------------------------------------------------------------