├── .Rprofile ├── .gitignore ├── README.md ├── draft.Rmd ├── gis-dataviz-workshop.Rproj ├── images ├── Nigeria pop by state.png └── Nigeria pop wiki.png ├── index.Rmd ├── index.html ├── index_files └── figure-html │ ├── unnamed-chunk-1-1.png │ ├── unnamed-chunk-10-1.png │ ├── unnamed-chunk-11-1.png │ ├── unnamed-chunk-12-1.png │ ├── unnamed-chunk-13-1.png │ ├── unnamed-chunk-15-1.png │ ├── unnamed-chunk-18-1.png │ ├── unnamed-chunk-19-1.png │ ├── unnamed-chunk-2-1.png │ ├── unnamed-chunk-24-1.png │ ├── unnamed-chunk-29-1.png │ ├── unnamed-chunk-3-1.png │ ├── unnamed-chunk-31-1.png │ ├── unnamed-chunk-33-1.png │ ├── unnamed-chunk-35-1.png │ ├── unnamed-chunk-39-1.png │ ├── unnamed-chunk-4-1.png │ ├── unnamed-chunk-41-1.png │ ├── unnamed-chunk-43-1.png │ ├── unnamed-chunk-45-1.png │ ├── unnamed-chunk-9-1.png │ └── world_map-1.png ├── libs ├── HomeButton │ ├── LICENSE │ ├── easy-button-src.min.js │ ├── glyphicons-21-home.png │ ├── home-button.css │ └── home-button.js ├── Proj4Leaflet │ └── proj4leaflet.js ├── clipboard │ └── setClipboardText.js ├── crosstalk │ ├── css │ │ └── crosstalk.min.css │ ├── js │ │ ├── crosstalk.js │ │ ├── crosstalk.js.map │ │ ├── crosstalk.min.js │ │ └── crosstalk.min.js.map │ └── scss │ │ └── crosstalk.scss ├── datatables-binding │ └── datatables.js ├── datatables-css │ └── datatables-crosstalk.css ├── dt-core │ ├── css │ │ ├── jquery.dataTables.extra.css │ │ └── jquery.dataTables.min.css │ └── js │ │ └── jquery.dataTables.min.js ├── header-attrs-2.11 │ └── header-attrs.js ├── header-attrs │ └── header-attrs.js ├── htmlwidgets │ └── htmlwidgets.js ├── jquery │ └── jquery.min.js ├── leaflet-binding │ └── leaflet.js ├── leaflet-providers-plugin │ └── leaflet-providers-plugin.js ├── leaflet-providers │ └── leaflet-providers_1.9.0.js ├── leaflet │ ├── images │ │ ├── layers-2x.png │ │ ├── layers.png │ │ ├── marker-icon-2x.png │ │ ├── marker-icon.png │ │ └── marker-shadow.png │ ├── leaflet.css │ └── leaflet.js ├── leafletfix │ └── leafletfix.css ├── mapviewCSS │ ├── mapview-popup.css │ └── mapview.css ├── proj4 │ └── proj4.min.js ├── remark-css-0.0.1 │ ├── default-fonts.css │ └── default.css ├── remark-css │ └── default.css └── rstudio_leaflet │ ├── images │ └── 1px.png │ └── rstudio_leaflet.css ├── mycss.css ├── renv.lock └── renv ├── .gitignore ├── activate.R └── settings.dcf /.Rprofile: -------------------------------------------------------------------------------- 1 | source("renv/activate.R") 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .Rproj.user 2 | .Rhistory 3 | .RData 4 | .Ruserdata 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Introduction to Geospatial Visualization with R 2 | 3 | Materials for R-Ladies Abuja geospatial visualization workshop 4 | 5 | Slides: https://ramikrispin.github.io/gis-dataviz-workshop/#1 6 | 7 | 8 | 9 | ## Workshop scope 10 | 11 | * Introduction to spatial data 12 | * Working with sf objects 13 | * Plot `sf` objects with **mapview**, **tmap**, and **ggplot2** 14 | * Create choropleth maps 15 | 16 | ## Packages 17 | 18 | In this workshop we will use the following packages: 19 | 20 | * GIS data: 21 | - **rnaturalearth** - provides access for the Natural Earth. The Natural Earth is a public domain map dataset including vector country and other administrative boundaries 22 | - **sf** - for setting the map object class and plot it 23 | * Tools for plotting maps: 24 | - **mapview** - A wrapper for the leaflet library 25 | - **tmap** - A package for creating a thematic maps 26 | - **ggplot2** - Is a system for declarative creating graphics 27 | - **viridis** - A package that provide a series of color maps 28 | * Datasets: 29 | - **coronavirus** to pull Covid19 data at different levels (e.g., region, country, etc.) 30 | 31 | ## Resources 32 | 33 | * Books: 34 | - Geocomputation with R - https://geocompr.robinlovelace.net/ 35 | - Geospatial Health Data: Modeling and Visualization with R-INLA and Shiny - https://www.paulamoraga.com/book-geospatial/ 36 | - Introduction to Spatial Data Programming with R - https://geobgu.xyz/r/ 37 | * Package documentation: 38 | - **sf** - https://r-spatial.github.io/sf/ 39 | - **rnaturalearth** - https://docs.ropensci.org/rnaturalearth/ 40 | - **mapview** - https://r-spatial.github.io/mapview/ 41 | - **tmap** - https://r-tmap.github.io/tmap/ 42 | - **ggplot2 (geom_sf)** - https://ggplot2.tidyverse.org/reference/ggsf.html 43 | * Examples 44 | - Geospatial Visualization (**coronavirus** package) - https://ramikrispin.github.io/coronavirus/articles/geospatial_visualization.html 45 | - Geospatial Visualization of the Covid19 Cases (**covid19sf** package) - https://ramikrispin.github.io/covid19sf/articles/geo.html 46 | - Visualization covid19italy with Choropleth Maps (**covid19italy** package) - https://ramikrispin.github.io/covid19Italy/articles/geospatial_visualization.html 47 | 48 | -------------------------------------------------------------------------------- /draft.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Draft" 3 | output: 4 | html_document: 5 | df_print: paged 6 | --- 7 | 8 | In this workshop we will use the following packages: 9 | 10 | - `rnaturalearth` - provides access for the [Natural Earth](http://www.naturalearthdata.com/). The Natural Earth is a public domain map dataset including vector country and other administrative boundaries 11 | 12 | - `sf` for setting the map object class and plot it 13 | 14 | - `mapview`, `tmap`, and `ggplot2` for plotting the map objects 15 | 16 | - `coronavirus`, `covid19italy` to pull Covid19 data at different levels (e.g., region, country, etc.) 17 | 18 | ```{r} 19 | library(sf) 20 | library(rnaturalearth) 21 | library(mapview) 22 | library(tmap) 23 | library(ggplot2) 24 | library(dplyr) 25 | library(tidyr) 26 | library(rvest) 27 | library(coronavirus) 28 | ``` 29 | 30 | ### Polygons 31 | 32 | ```{r} 33 | plot(x = 1, 34 | y = 1, 35 | ylab = "", 36 | xlab = "", 37 | main = "Point") 38 | 39 | ``` 40 | 41 | ```{r} 42 | 43 | plot(x = c(1, 2, 3), y = c(1, 4, 2), type = "l") 44 | 45 | ``` 46 | 47 | ```{r} 48 | plot(x = c(1, 2, 4, 3, 1), y = c(1, 4, 3, 1, 1), type = "l") 49 | ``` 50 | 51 | labels + polygons = map boarders 52 | 53 | labels + polygons + data = choropleth map 54 | 55 | ```{r} 56 | world <- ne_countries(type = 'countries', scale = 'small', returnclass = "sf") 57 | str(world) 58 | head(world) 59 | plot(world$geometry) 60 | ``` 61 | 62 | ```{r} 63 | 64 | africa <- world %>% filter(continent == "Africa") 65 | plot(africa$geometry) 66 | ``` 67 | 68 | ```{r} 69 | w_africa <- world %>% filter(subregion == "Western Africa") 70 | plot(w_africa$geometry, col = sf.colors(12, categorical = TRUE), border = 'grey', 71 | axes = TRUE, main = "Western Africa") 72 | ``` 73 | 74 | ```{r} 75 | nigeria <- ne_states(country = "Nigeria", returnclass = "sf") 76 | 77 | head(nigeria) 78 | plot(nigeria$geometry, col = sf.colors(37, categorical = TRUE), border = 'grey', 79 | axes = TRUE, main = "Nigeria") 80 | ``` 81 | 82 | ```{r} 83 | 84 | url <- "https://en.wikipedia.org/wiki/List_of_Nigerian_states_by_population" 85 | 86 | page <- read_html(url) 87 | tables <- html_node(page, ".wikitable") 88 | pop_table <- html_table(tables, fill = TRUE) %>% 89 | select(state_temp = State, pop_2006_temp = `Population (2006)`, pop_2016_temp = `Population (2016)`) 90 | 91 | 92 | pop_table$pop_2006 <- as.numeric(gsub(x = pop_table$pop_2006_temp,pattern = ",", replacement = "")) 93 | pop_table$pop_2016 <- as.numeric(gsub(x = pop_table$pop_2016_temp,pattern = ",", replacement = "")) 94 | pop_table$state <- gsub(x = pop_table$state_temp,pattern = " State", replacement = "") 95 | pop_table <- pop_table %>% select(-state_temp, -pop_2006_temp, - pop_2016_temp) %>% 96 | select(state, pop_2006, pop_2016) %>% 97 | mutate(state_fix = state) 98 | 99 | pop_table$state_fix[which(pop_table$state_fix == "Nasarawa")] <- "Nassarawa" 100 | 101 | head(pop_table) 102 | ``` 103 | 104 | ```{r} 105 | nigeria_pop <- nigeria %>% left_join(pop_table, by = c("name" = "state_fix")) 106 | 107 | plot(nigeria_pop["pop_2016"], key.pos = 1, axes = TRUE, key.width = lcm(1.3), key.length = 1.0) 108 | 109 | 110 | tm_shape(nigeria_pop) + 111 | tm_polygons(col = "pop_2016", 112 | style = "order", 113 | title = "Population", 114 | palette = "Blues") + 115 | tm_style("cobalt") + 116 | tm_text("state", size = 0.7) + 117 | tm_credits("Source: Wikipedia - List of Nigerian states by population", 118 | position = c("LEFT", "BOTTOM")) + 119 | tm_layout(title= "Nigeria Population by States", 120 | title.position = c('right', 'top') , 121 | inner.margins = c(0.02, .02, .1, .15)) 122 | ``` 123 | 124 | ### Data 125 | 126 | ```{r} 127 | covid19_daily <- refresh_coronavirus_jhu() 128 | 129 | head(covid19_daily) 130 | 131 | df <- covid19_daily %>% 132 | filter(location_type == "country") %>% 133 | group_by(location, data_type) %>% 134 | summarise(cases = sum(value), 135 | .groups = "drop") %>% 136 | pivot_wider(names_from = data_type, values_from = cases) %>% 137 | setNames(c("country", "total_cases", "total_death")) 138 | 139 | 140 | 141 | head(df) 142 | 143 | df1 <- covid19_daily %>% 144 | filter(location_type == "country") %>% 145 | group_by(location, location_code, data_type) %>% 146 | summarise(cases = sum(value), 147 | .groups = "drop") %>% 148 | pivot_wider(names_from = data_type, values_from = cases) %>% 149 | setNames(c("country", "total_cases", "total_death")) 150 | 151 | 152 | 153 | world_covid <- world %>% 154 | select(country = sovereignt, geometry) %>% 155 | left_join(df, by = "country") 156 | 157 | 158 | plot(world_covid) 159 | plot(world_covid[, c("total_cases")]) 160 | ``` 161 | 162 | ### 163 | 164 | ### Data Visualization 165 | 166 | ```{r} 167 | world <- ne_countries(type = 'countries', scale = 'large') 168 | sp::plot(world) 169 | 170 | world <- ne_countries(type = 'countries', scale = 'small', returnclass = "sf") 171 | world$random <- stats::rnorm(n = nrow(world)) 172 | sf::plot_sf(world) 173 | sp::plot(world[, "random"]) 174 | sp::plot(world[which(world$region_un == "Africa"), "random"]) 175 | 176 | x1 <- rnaturalearth::ne_countries(country = 'Nigeria', type='countries') 177 | sp::plot(x1) 178 | 179 | # x2 <- rnaturalearth::ne_states(country = 'Nigeria') 180 | 181 | ``` 182 | 183 | ```{r} 184 | 185 | mapview::mapview(x) 186 | 187 | # tmap:: 188 | ``` 189 | -------------------------------------------------------------------------------- /gis-dataviz-workshop.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 | 15 | BuildType: Website 16 | -------------------------------------------------------------------------------- /images/Nigeria pop by state.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RamiKrispin/gis-dataviz-workshop/cbdee699fcaf68becc652d4efc1599b23761eaad/images/Nigeria pop by state.png -------------------------------------------------------------------------------- /images/Nigeria pop wiki.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RamiKrispin/gis-dataviz-workshop/cbdee699fcaf68becc652d4efc1599b23761eaad/images/Nigeria pop wiki.png -------------------------------------------------------------------------------- /index.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Introduction to Geospatial Visualization with R" 3 | subtitle: "R-Ladies Abuja + Abuja R Users" 4 | author: "Rami Krispin" 5 | date: "2021/11/27 (updated: `r Sys.Date()`)" 6 | output: 7 | xaringan::moon_reader: 8 | css: ["default"] 9 | lib_dir: libs 10 | nature: 11 | highlightStyle: github 12 | highlightLines: true 13 | countIncrementalSlides: false 14 | --- 15 | 16 | 17 | ```{r setup, include=FALSE} 18 | options(htmltools.dir.version = FALSE) 19 | knitr::opts_chunk$set(warning = FALSE, 20 | message = FALSE, 21 | fig.align = "center", 22 | dpi = 400, 23 | fig.height = 4.5) 24 | ``` 25 | 26 | 27 | ## Agenda 28 | 29 | 1. Intro 30 | 1. Spatial Geometries 31 | 1. Data Manipulation 32 | 1. Merging sf objects with data 33 | 1. Choropleth Map 34 | 1. Q&A 35 | 36 | 37 | --- 38 | 39 | ## About Me 40 | 41 | * Data science manager 42 | * Focusing on time series analysis and forecasting 43 | * GIS analysis and maps as a hobby 44 | * Open source contributor - [TSstudio](https://github.com/RamiKrispin/TSstudio), [USgrid](https://github.com/RamiKrispin/USgrid), [USgas](https://github.com/RamiKrispin/USgas), [coronavirus](https://github.com/RamiKrispin/coronavirus), [covid19italy](https://github.com/RamiKrispin/covid19italy) 45 | * Get in touch on [LinkedIn](https://www.linkedin.com/in/rami-krispin/), [Twitter](https://twitter.com/Rami_Krispin), [Github](https://github.com/RamiKrispin) 46 | 47 | --- 48 | 49 | ## Workshop scope 50 | 51 | * Introduction to spatial data 52 | * Working with sf objects 53 | * Plot `sf` objects with **mapview**, **tmap**, and **ggplot2** 54 | * Create choropleth maps 55 | 56 | --- 57 | 58 | ## Packages 59 | 60 | In this workshop we will use the following packages: 61 | 62 | * GIS data: 63 | - **rnaturalearth** - provides access for the Natural Earth. The Natural Earth is a public domain map dataset including vector country and other administrative boundaries 64 | - **sf** - for setting the map object class and plot it 65 | * Tools for plotting maps: 66 | - **mapview** - A wrapper for the leaflet library 67 | - **tmap** - A package for creating a thematic maps 68 | - **ggplot2** - Is a system for declarative creating graphics 69 | - **viridis** - A package that provide a series of color maps 70 | * Datasets: 71 | - **coronavirus** to pull Covid19 data at different levels (e.g., region, country, etc.) 72 | 73 | --- 74 | class: middle, inverse 75 | 76 | ## The Geometry 77 | 78 | --- 79 | 80 | ## Point 81 | 82 | ```{r} 83 | plot(x = 1, y = 1, 84 | ylab = "", xlab = "", 85 | main = "Point") 86 | ``` 87 | 88 | 89 | --- 90 | 91 | ## Line 92 | 93 | ```{r} 94 | plot(x = c(1, 2, 3), y = c(1, 4, 2), 95 | ylab = "", xlab = "", 96 | main = "Line", type = "l") 97 | ``` 98 | 99 | --- 100 | 101 | ## Polygon 102 | 103 | ```{r} 104 | plot(x = c(1, 2, 4, 3, 1), y = c(2, 4, 3, 1, 2), 105 | ylab = "", xlab = "", 106 | main = "Polygon", type = "l") 107 | ``` 108 | 109 | --- 110 | 111 | ## Polygon 112 | 113 | ```{r echo = FALSE} 114 | library(rnaturalearth) 115 | library(sf) 116 | library(dplyr) 117 | 118 | nigeria_df <- ne_countries(type = 'countries', scale = 'small', returnclass = "sf") %>% 119 | filter(name == "Nigeria") 120 | 121 | plot(nigeria_df$geometry, col = rgb(59, 132,86, maxColorValue = 255), border = 'grey', 122 | axes = TRUE, main = "Nigeria") 123 | ``` 124 | 125 | 126 | 127 | --- 128 | 129 | ## Labels + Polygons = Map Boarders 130 | 131 | ```{r world_map, echo= FALSE, fig.height=9, fig.width=9, fig.align = "top"} 132 | library(rnaturalearth) 133 | library(sf) 134 | 135 | world <- ne_countries(type = 'countries', scale = 'small', returnclass = "sf") 136 | 137 | plot(world$geometry) 138 | ``` 139 | 140 | --- 141 | 142 | 143 | ## Labels + Polygons + Data = Choropleth 144 | 145 | 146 | --- 147 | 148 | class: middle, inverse 149 | 150 | ## Working with Spatial Data 151 | 152 | --- 153 | 154 | ## The rnaturalearth package 155 | 156 | The **rnaturalearth** is a great resource to pull GIS data from the Natural Earth API on a different levels (country, region, sub-region, etc.) 157 | 158 | The `ne_countries` will return an `sf` (or `sp`) object with a GIS (polygon) data: 159 | 160 | ```{r} 161 | library(rnaturalearth) 162 | 163 | world <- ne_countries(type = 'countries', 164 | scale = 'small', 165 | returnclass = "sf") 166 | ``` 167 | 168 | One of the main advantages of the `sf` object that it is also a data.frame object. Therefore, it is strightforward to work and manipulate the data with dplyr (or anything similar): 169 | 170 | ```{r} 171 | class(world) 172 | ``` 173 | 174 | 175 | --- 176 | 177 | ```{r} 178 | str(world) 179 | ``` 180 | 181 | --- 182 | 183 | ```{r} 184 | head(world) 185 | ``` 186 | 187 | --- 188 | 189 | ## Working with the sf package 190 | 191 | ```{r} 192 | library(sf) 193 | 194 | plot(world) 195 | ``` 196 | 197 | 198 | --- 199 | 200 | ## Working with the sf package 201 | 202 | * The **sf** package has a plot method for `sf` objects 203 | * By default, it will plot a choropleth map for any numeric variable 204 | 205 | To simply plot the world map by country without data use: 206 | 207 | ```{r} 208 | plot(world$geometry) 209 | ``` 210 | 211 | 212 | --- 213 | 214 | class: middle, inverse 215 | 216 | ## Data Manipulation 217 | 218 | --- 219 | 220 | ## Filtering 221 | 222 | Filtering `sf` object is as simple as filtering a `data.frame` object. Let's say we want to re-plot the the following map without Antarctica: 223 | 224 | 225 | ```{r} 226 | plot(world$geometry) 227 | ``` 228 | 229 | 230 | --- 231 | 232 | ## Filtering 233 | 234 | We will use the continent field to filter Antarctica from the object: 235 | 236 | ```{r} 237 | library(dplyr) 238 | 239 | world1 <- world %>% filter(continent != "Antarctica") 240 | 241 | plot(world1$geometry) 242 | ``` 243 | 244 | --- 245 | 246 | ## Subseting 247 | 248 | Likewise, we can subset a specific element of the sf object, for example let's pull Africa from the `world` object. We will use again the `continent` variable to subset the object: 249 | 250 | ```{r} 251 | africa <- world %>% filter(continent == "Africa") 252 | plot(africa$geometry) 253 | ``` 254 | 255 | --- 256 | 257 | ## Country level 258 | 259 | The `ne_states` function enables you to pull a country/state level data. For example, let's pull Nigeria data: 260 | 261 | ```{r} 262 | nigeria <- ne_states(country = "Nigeria", returnclass = "sf") 263 | 264 | head(nigeria) 265 | ``` 266 | 267 | --- 268 | 269 | ## Country level 270 | 271 | ```{r} 272 | plot(nigeria$geometry, 273 | col = sf.colors(37, categorical = TRUE), 274 | border = 'grey', axes = TRUE, main = "Nigeria") 275 | ``` 276 | 277 | --- 278 | 279 | class: middle, inverse 280 | 281 | ## Merging sf objects with data 282 | 283 | --- 284 | 285 | ### Adding data - COVID19 cases 286 | 287 | In the following example we will pull COVID19 cases by country from the **coronavirus** package and merge it the `world` object we created before. 288 | 289 | ```{r cache=FALSE} 290 | library(coronavirus) 291 | 292 | covid19_daily <- refresh_coronavirus_jhu() 293 | 294 | head(covid19_daily) 295 | ``` 296 | 297 | --- 298 | 299 | ### Adding data - COVID19 cases 300 | 301 | Next step is to prepare the data for merging with the `world` object: 302 | 303 | ```{r} 304 | library(tidyr) 305 | 306 | df <- covid19_daily %>% 307 | filter(location_type == "country") %>% 308 | group_by(location, location_code, data_type) %>% 309 | summarise(cases = sum(value), 310 | .groups = "drop") %>% 311 | pivot_wider(names_from = data_type, values_from = cases) %>% 312 | setNames(c("country", "country_code", "total_cases", "total_death")) 313 | 314 | head(df) 315 | ``` 316 | 317 | --- 318 | 319 | ### Adding data - COVID19 cases 320 | 321 | Once the data is ready we can merge the `world` table with the cases table: 322 | 323 | 324 | ```{r} 325 | world_covid1 <- world %>% 326 | filter(continent != "Antarctica") %>% 327 | select(country = sovereignt, geometry) %>% 328 | left_join(df, by = "country") 329 | 330 | plot(world_covid1[, c("total_cases")]) 331 | ``` 332 | 333 | 334 | 335 | --- 336 | 337 | ### Adding data - COVID19 cases 338 | 339 | We can try to merge the data with the country code (ISO code). 340 | 341 | ```{r} 342 | world_covid2 <- world %>% 343 | filter(continent != "Antarctica") %>% 344 | select(country_code = iso_a2, geometry) %>% 345 | left_join(df, by = "country_code") 346 | 347 | plot(world_covid2[, c("total_cases")]) 348 | ``` 349 | 350 | 351 | --- 352 | 353 | ## Adding data - Nigeria population 354 | 355 | In the next example we will pull Nigeria population by state from Wikipedia and merge it with Nigeria state level `sf` object: 356 | 357 | 358 | 359 | 360 | --- 361 | 362 | ## Adding data - Nigeria population 363 | 364 | We will use the **rvest** package to pull the population data from Wikipedia: 365 | 366 | ```{r, cache=FALSE} 367 | library(rvest) 368 | 369 | url <- "https://en.wikipedia.org/wiki/List_of_Nigerian_states_by_population" 370 | 371 | page <- read_html(url) 372 | tables <- html_node(page, ".wikitable") 373 | pop_table <- html_table(tables, fill = TRUE) %>% 374 | select(state_temp = State, pop_2006_temp = `Population (2006)`, pop_2016_temp = `Population (2016)`) 375 | 376 | 377 | pop_table$pop_2006 <- as.numeric(gsub(x = pop_table$pop_2006_temp,pattern = ",", replacement = "")) 378 | pop_table$pop_2016 <- as.numeric(gsub(x = pop_table$pop_2016_temp,pattern = ",", replacement = "")) 379 | pop_table$state <- gsub(x = pop_table$state_temp,pattern = " State", replacement = "") 380 | pop_table <- pop_table %>% select(-state_temp, -pop_2006_temp, - pop_2016_temp) %>% 381 | select(state, pop_2006, pop_2016) %>% 382 | mutate(state_fix = state) 383 | ``` 384 | 385 | 386 | 387 | --- 388 | 389 | ## Adding data - Nigeria population 390 | 391 | ```{r} 392 | head(pop_table) 393 | ``` 394 | 395 | Before merging let's just modify the one of the labels: 396 | 397 | ```{r} 398 | pop_table$state_fix[which(pop_table$state_fix == "Nasarawa")] <- "Nassarawa" 399 | ``` 400 | 401 | Last but not least let's merge the tables: 402 | 403 | ```{r} 404 | nigeria_pop <- nigeria %>% left_join(pop_table, by = c("name" = "state_fix")) 405 | ``` 406 | 407 | --- 408 | 409 | ## Adding data - Nigeria population 410 | 411 | ```{r fig.height=4} 412 | plot(nigeria_pop["pop_2016"], key.pos = 1, 413 | axes = TRUE, main = "Nigeria Population by State", 414 | key.width = lcm(1.3), key.length = 1.0) 415 | ``` 416 | 417 | --- 418 | 419 | class: middle, inverse 420 | 421 | ## Choropleth Map 422 | 423 | --- 424 | 425 | ## The mapview package 426 | 427 | ```{r} 428 | library(mapview) 429 | 430 | mapview(nigeria_pop, 431 | zcol = "pop_2016", 432 | legend = TRUE, 433 | layer.name = "Population") 434 | 435 | ``` 436 | 437 | --- 438 | 439 | 440 | 441 | ## The mapview package 442 | 443 | More examples available on the coronavirues vignette: 444 | 445 | https://ramikrispin.github.io/coronavirus/articles/geospatial_visualization.html 446 | 447 | --- 448 | 449 | ## The tmap package 450 | 451 | Let's plot COVID19 vaccine data: 452 | 453 | ```{r} 454 | library(coronavirus) 455 | library(tmap) 456 | 457 | sf_use_s2(FALSE) 458 | 459 | data("covid19_vaccine") 460 | 461 | head(covid19_vaccine) 462 | ``` 463 | 464 | --- 465 | 466 | ## The tmap package 467 | 468 | As before, we will merge the data from **rnaturalearth** with the vaccine data: 469 | 470 | ```{r} 471 | map <- ne_countries(returnclass = "sf") %>% 472 | select(name, iso2 = iso_a2, iso3 = iso_a3, geometry) 473 | 474 | 475 | df <- map %>% left_join( 476 | covid19_vaccine %>% 477 | filter(date == max(date), 478 | is.na(province_state)) %>% 479 | mutate(perc = round(100 * people_fully_vaccinated / population, 2)) %>% 480 | select(country_region, iso2, iso3, people_fully_vaccinated, perc, continent_name), 481 | by = c("iso2", "iso3") 482 | ) 483 | ``` 484 | 485 | 486 | --- 487 | 488 | ## The tmap package 489 | 490 | Let's remove underpopulated areas and plot it: 491 | 492 | ```{r} 493 | df1 <- df %>% 494 | filter(!name %in% c("Greenland", "Antarctica")) 495 | 496 | p1 <- tm_shape(df1) + 497 | tm_polygons(col = "perc", 498 | n = 8, 499 | title = "Fully Vaccinated %", 500 | palette = "Blues") 501 | ``` 502 | 503 | --- 504 | 505 | ## The tmap package 506 | 507 | ```{r} 508 | p1 509 | ``` 510 | 511 | 512 | 513 | --- 514 | 515 | ## The tmap package 516 | 517 | The `projection` argument enables you to set different projection: 518 | 519 | ```{r} 520 | 521 | p2 <- tm_shape(df1) + 522 | tm_polygons(col = "perc", 523 | n = 8, 524 | projection = 3857, 525 | title = "Fully Vaccinated %", 526 | palette = "Blues") 527 | ``` 528 | 529 | --- 530 | 531 | ## The tmap package 532 | 533 | ```{r} 534 | p2 535 | ``` 536 | 537 | --- 538 | 539 | ## The tmap package 540 | 541 | The **tmap** package provides different customization options, in the following example we will modify the background and add titles: 542 | 543 | 544 | ```{r} 545 | df2 <- df1 %>% 546 | filter(continent_name == "South America") 547 | 548 | 549 | p3 <- tm_shape(df2) + 550 | tm_polygons(col = "perc", 551 | n = 5, 552 | title = "Perc. Group", 553 | palette = "Blues") 554 | ``` 555 | 556 | --- 557 | 558 | ## The tmap package 559 | 560 | ```{r} 561 | p3 562 | ``` 563 | 564 | --- 565 | 566 | ## The tmap package 567 | 568 | ```{r} 569 | p4 <- tm_shape(df2) + 570 | tm_polygons(col = "perc", 571 | n = 5, 572 | title = "Perc. Group", 573 | palette = "Blues") + 574 | tm_style("cobalt") + 575 | tm_text("iso3", size = 0.7) + 576 | tm_layout( 577 | title= "% of Population Fully Vaccinated", 578 | title.position = c('right', 'top') , 579 | inner.margins = c(0.02, .02, .1, .25)) 580 | ``` 581 | 582 | --- 583 | 584 | ## The tmap package 585 | 586 | ```{r} 587 | p4 588 | ``` 589 | 590 | 591 | --- 592 | 593 | ## The tmap package 594 | 595 | In addition to static plots, the **tmap** package has an interactive version using the leaflet JS library. To get an interactive output use the `tmap_mode` function: 596 | 597 | ```{r} 598 | tmap_mode("view") 599 | 600 | p5 <- tm_shape(df2) + 601 | tm_polygons(col = "perc", 602 | n = 5, 603 | title = "Perc. Group", 604 | palette = "Blues") 605 | ``` 606 | 607 | --- 608 | 609 | ## The tmap package 610 | 611 | ```{r} 612 | p5 613 | ``` 614 | 615 | 616 | 617 | --- 618 | 619 | ## The tmap package 620 | 621 | Let's re-plot the Nigerian States population with the tmap function: 622 | 623 | ```{r} 624 | tmap_mode("plot") 625 | 626 | nigeria_pop_plot <- tm_shape(nigeria_pop) + 627 | tm_polygons(col = "pop_2016", 628 | style = "order", 629 | title = "Population", 630 | palette = "Blues") + 631 | tm_style("cobalt") + 632 | tm_text("state", size = 0.7) + 633 | tm_credits("Source: Wikipedia - List of Nigerian states by population", 634 | position = c("LEFT", "BOTTOM")) + 635 | tm_layout(title= "Nigeria Population by States", 636 | title.position = c('right', 'top') , 637 | inner.margins = c(0.02, .02, .1, .15)) 638 | ``` 639 | 640 | 641 | --- 642 | 643 | ## The tmap package 644 | 645 | 646 | ```{r} 647 | nigeria_pop_plot 648 | ``` 649 | 650 | --- 651 | 652 | ## The tmap package 653 | 654 | You can break down the object polygons with the `tm_facets` function: 655 | 656 | ```{r} 657 | nigeria_pop_plot_facets <- tm_shape(nigeria_pop) + 658 | tm_polygons(col = "pop_2016", 659 | n = 5, 660 | title = "Total Population", 661 | palette = "Greens") + 662 | tmap_options(limits = c(facets.view = 13)) + 663 | tm_facets(by = "name") 664 | ``` 665 | 666 | --- 667 | 668 | ## The tmap package 669 | 670 | ```{r} 671 | nigeria_pop_plot_facets 672 | ``` 673 | 674 | 675 | --- 676 | 677 | ## The ggplot2 package 678 | 679 | Likewise, you can plot `sf` objects with the **ggplot2** package using the `geom_sf` function: 680 | 681 | 682 | ```{r} 683 | library(ggplot2) 684 | library(viridis) 685 | 686 | p1 <- ggplot(data = nigeria_pop, aes(fill = `pop_2016`)) + 687 | geom_sf() + 688 | scale_fill_viridis_b() 689 | ``` 690 | 691 | --- 692 | 693 | ## The ggplot2 package 694 | 695 | ```{r} 696 | p1 697 | ``` 698 | 699 | 700 | --- 701 | 702 | ## The ggplot2 package 703 | 704 | To add label for each polygon use the `geom_sf_label` function: 705 | 706 | ```{r} 707 | p2 <- ggplot(data = nigeria_pop, aes(fill = `pop_2016`)) + 708 | geom_sf(size = 0.1) + 709 | scale_fill_viridis(alpha = 0.9, 710 | begin = 0.01, 711 | discrete = FALSE, 712 | end = 0.9) + 713 | geom_sf_label(aes(label = state)) + 714 | labs(fill = "Population", 715 | title = "Population by State", 716 | caption = "Source: https://en.wikipedia.org/wiki/List_of_Nigerian_states_by_population") + 717 | theme_void() 718 | ``` 719 | 720 | 721 | --- 722 | 723 | ## The ggplot2 package 724 | 725 | ```{r} 726 | p2 727 | ``` 728 | 729 | 730 | --- 731 | 732 | ## Resources 733 | 734 | * Books: 735 | - Geocomputation with R - https://geocompr.robinlovelace.net/ 736 | - Geospatial Health Data: Modeling and Visualization with R-INLA and Shiny - https://www.paulamoraga.com/book-geospatial/ 737 | - Introduction to Spatial Data Programming with R - https://geobgu.xyz/r/ 738 | * Package documentation: 739 | - **sf** - https://r-spatial.github.io/sf/ 740 | - **rnaturalearth** - https://docs.ropensci.org/rnaturalearth/ 741 | - **mapview** - https://r-spatial.github.io/mapview/ 742 | - **tmap** - https://r-tmap.github.io/tmap/ 743 | - **ggplot2 (geom_sf)** - https://ggplot2.tidyverse.org/reference/ggsf.html 744 | * Examples 745 | - Geospatial Visualization (**coronavirus** package) - https://ramikrispin.github.io/coronavirus/articles/geospatial_visualization.html 746 | - Geospatial Visualization of the Covid19 Cases (**covid19sf** package) - https://ramikrispin.github.io/covid19sf/articles/geo.html 747 | - Visualization covid19italy with Choropleth Maps (**covid19italy** package) - https://ramikrispin.github.io/covid19Italy/articles/geospatial_visualization.html 748 | -------------------------------------------------------------------------------- /index_files/figure-html/unnamed-chunk-1-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RamiKrispin/gis-dataviz-workshop/cbdee699fcaf68becc652d4efc1599b23761eaad/index_files/figure-html/unnamed-chunk-1-1.png -------------------------------------------------------------------------------- /index_files/figure-html/unnamed-chunk-10-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RamiKrispin/gis-dataviz-workshop/cbdee699fcaf68becc652d4efc1599b23761eaad/index_files/figure-html/unnamed-chunk-10-1.png -------------------------------------------------------------------------------- /index_files/figure-html/unnamed-chunk-11-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RamiKrispin/gis-dataviz-workshop/cbdee699fcaf68becc652d4efc1599b23761eaad/index_files/figure-html/unnamed-chunk-11-1.png -------------------------------------------------------------------------------- /index_files/figure-html/unnamed-chunk-12-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RamiKrispin/gis-dataviz-workshop/cbdee699fcaf68becc652d4efc1599b23761eaad/index_files/figure-html/unnamed-chunk-12-1.png -------------------------------------------------------------------------------- /index_files/figure-html/unnamed-chunk-13-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RamiKrispin/gis-dataviz-workshop/cbdee699fcaf68becc652d4efc1599b23761eaad/index_files/figure-html/unnamed-chunk-13-1.png -------------------------------------------------------------------------------- /index_files/figure-html/unnamed-chunk-15-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RamiKrispin/gis-dataviz-workshop/cbdee699fcaf68becc652d4efc1599b23761eaad/index_files/figure-html/unnamed-chunk-15-1.png -------------------------------------------------------------------------------- /index_files/figure-html/unnamed-chunk-18-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RamiKrispin/gis-dataviz-workshop/cbdee699fcaf68becc652d4efc1599b23761eaad/index_files/figure-html/unnamed-chunk-18-1.png -------------------------------------------------------------------------------- /index_files/figure-html/unnamed-chunk-19-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RamiKrispin/gis-dataviz-workshop/cbdee699fcaf68becc652d4efc1599b23761eaad/index_files/figure-html/unnamed-chunk-19-1.png -------------------------------------------------------------------------------- /index_files/figure-html/unnamed-chunk-2-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RamiKrispin/gis-dataviz-workshop/cbdee699fcaf68becc652d4efc1599b23761eaad/index_files/figure-html/unnamed-chunk-2-1.png -------------------------------------------------------------------------------- /index_files/figure-html/unnamed-chunk-24-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RamiKrispin/gis-dataviz-workshop/cbdee699fcaf68becc652d4efc1599b23761eaad/index_files/figure-html/unnamed-chunk-24-1.png -------------------------------------------------------------------------------- /index_files/figure-html/unnamed-chunk-29-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RamiKrispin/gis-dataviz-workshop/cbdee699fcaf68becc652d4efc1599b23761eaad/index_files/figure-html/unnamed-chunk-29-1.png -------------------------------------------------------------------------------- /index_files/figure-html/unnamed-chunk-3-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RamiKrispin/gis-dataviz-workshop/cbdee699fcaf68becc652d4efc1599b23761eaad/index_files/figure-html/unnamed-chunk-3-1.png -------------------------------------------------------------------------------- /index_files/figure-html/unnamed-chunk-31-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RamiKrispin/gis-dataviz-workshop/cbdee699fcaf68becc652d4efc1599b23761eaad/index_files/figure-html/unnamed-chunk-31-1.png -------------------------------------------------------------------------------- /index_files/figure-html/unnamed-chunk-33-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RamiKrispin/gis-dataviz-workshop/cbdee699fcaf68becc652d4efc1599b23761eaad/index_files/figure-html/unnamed-chunk-33-1.png -------------------------------------------------------------------------------- /index_files/figure-html/unnamed-chunk-35-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RamiKrispin/gis-dataviz-workshop/cbdee699fcaf68becc652d4efc1599b23761eaad/index_files/figure-html/unnamed-chunk-35-1.png -------------------------------------------------------------------------------- /index_files/figure-html/unnamed-chunk-39-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RamiKrispin/gis-dataviz-workshop/cbdee699fcaf68becc652d4efc1599b23761eaad/index_files/figure-html/unnamed-chunk-39-1.png -------------------------------------------------------------------------------- /index_files/figure-html/unnamed-chunk-4-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RamiKrispin/gis-dataviz-workshop/cbdee699fcaf68becc652d4efc1599b23761eaad/index_files/figure-html/unnamed-chunk-4-1.png -------------------------------------------------------------------------------- /index_files/figure-html/unnamed-chunk-41-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RamiKrispin/gis-dataviz-workshop/cbdee699fcaf68becc652d4efc1599b23761eaad/index_files/figure-html/unnamed-chunk-41-1.png -------------------------------------------------------------------------------- /index_files/figure-html/unnamed-chunk-43-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RamiKrispin/gis-dataviz-workshop/cbdee699fcaf68becc652d4efc1599b23761eaad/index_files/figure-html/unnamed-chunk-43-1.png -------------------------------------------------------------------------------- /index_files/figure-html/unnamed-chunk-45-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RamiKrispin/gis-dataviz-workshop/cbdee699fcaf68becc652d4efc1599b23761eaad/index_files/figure-html/unnamed-chunk-45-1.png -------------------------------------------------------------------------------- /index_files/figure-html/unnamed-chunk-9-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RamiKrispin/gis-dataviz-workshop/cbdee699fcaf68becc652d4efc1599b23761eaad/index_files/figure-html/unnamed-chunk-9-1.png -------------------------------------------------------------------------------- /index_files/figure-html/world_map-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RamiKrispin/gis-dataviz-workshop/cbdee699fcaf68becc652d4efc1599b23761eaad/index_files/figure-html/world_map-1.png -------------------------------------------------------------------------------- /libs/HomeButton/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2014 Daniel Montague 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /libs/HomeButton/easy-button-src.min.js: -------------------------------------------------------------------------------- 1 | !function(){function a(a,c){this.title=a.title,this.stateName=a.stateName?a.stateName:"unnamed-state",this.icon=L.DomUtil.create("span",""),L.DomUtil.addClass(this.icon,"button-state state-"+this.stateName.replace(/(^\s*|\s*$)/g,"")),this.icon.innerHTML=b(a.icon),this.onClick=L.Util.bind(a.onClick?a.onClick:function(){},c)}function b(a){var b;return a.match(/[&;=<>"']/)?b=a:(a=a.replace(/(^\s*|\s*$)/g,""),b=L.DomUtil.create("span",""),0===a.indexOf("fa-")?L.DomUtil.addClass(b,"fa "+a):0===a.indexOf("glyphicon-")?L.DomUtil.addClass(b,"glyphicon "+a):L.DomUtil.addClass(b,a),b=b.outerHTML),b}L.Control.EasyBar=L.Control.extend({options:{position:"bottomright",id:null,leafletClasses:!0},initialize:function(a,b){b&&L.Util.setOptions(this,b),this._buildContainer(),this._buttons=[];for(var c=0;c 3) { 55 | code = urn[urn.length - 3] + ':' + urn[urn.length - 1]; 56 | } 57 | if (proj4.defs[code] === undefined) { 58 | throw 'No projection definition for code ' + code; 59 | } 60 | } 61 | 62 | return proj4(code); 63 | } 64 | }); 65 | 66 | L.Proj.CRS = L.Class.extend({ 67 | includes: L.CRS, 68 | 69 | options: { 70 | transformation: new L.Transformation(1, 0, -1, 0) 71 | }, 72 | 73 | initialize: function(a, b, c) { 74 | var code, 75 | proj, 76 | def, 77 | options; 78 | 79 | if (L.Proj._isProj4Obj(a)) { 80 | proj = a; 81 | code = proj.srsCode; 82 | options = b || {}; 83 | 84 | this.projection = new L.Proj.Projection(proj, options.bounds); 85 | } else { 86 | code = a; 87 | def = b; 88 | options = c || {}; 89 | this.projection = new L.Proj.Projection(code, def, options.bounds); 90 | } 91 | 92 | L.Util.setOptions(this, options); 93 | this.code = code; 94 | this.transformation = this.options.transformation; 95 | 96 | if (this.options.origin) { 97 | this.transformation = 98 | new L.Transformation(1, -this.options.origin[0], 99 | -1, this.options.origin[1]); 100 | } 101 | 102 | if (this.options.scales) { 103 | this._scales = this.options.scales; 104 | } else if (this.options.resolutions) { 105 | this._scales = []; 106 | for (var i = this.options.resolutions.length - 1; i >= 0; i--) { 107 | if (this.options.resolutions[i]) { 108 | this._scales[i] = 1 / this.options.resolutions[i]; 109 | } 110 | } 111 | } 112 | 113 | this.infinite = !this.options.bounds; 114 | 115 | }, 116 | 117 | scale: function(zoom) { 118 | var iZoom = Math.floor(zoom), 119 | baseScale, 120 | nextScale, 121 | scaleDiff, 122 | zDiff; 123 | if (zoom === iZoom) { 124 | return this._scales[zoom]; 125 | } else { 126 | // Non-integer zoom, interpolate 127 | baseScale = this._scales[iZoom]; 128 | nextScale = this._scales[iZoom + 1]; 129 | scaleDiff = nextScale - baseScale; 130 | zDiff = (zoom - iZoom); 131 | return baseScale + scaleDiff * zDiff; 132 | } 133 | }, 134 | 135 | zoom: function(scale) { 136 | // Find closest number in this._scales, down 137 | var downScale = this._closestElement(this._scales, scale), 138 | downZoom = this._scales.indexOf(downScale), 139 | nextScale, 140 | nextZoom, 141 | scaleDiff; 142 | // Check if scale is downScale => return array index 143 | if (scale === downScale) { 144 | return downZoom; 145 | } 146 | if (downScale === undefined) { 147 | return -Infinity; 148 | } 149 | // Interpolate 150 | nextZoom = downZoom + 1; 151 | nextScale = this._scales[nextZoom]; 152 | if (nextScale === undefined) { 153 | return Infinity; 154 | } 155 | scaleDiff = nextScale - downScale; 156 | return (scale - downScale) / scaleDiff + downZoom; 157 | }, 158 | 159 | distance: L.CRS.Earth.distance, 160 | 161 | R: L.CRS.Earth.R, 162 | 163 | /* Get the closest lowest element in an array */ 164 | _closestElement: function(array, element) { 165 | var low; 166 | for (var i = array.length; i--;) { 167 | if (array[i] <= element && (low === undefined || low < array[i])) { 168 | low = array[i]; 169 | } 170 | } 171 | return low; 172 | } 173 | }); 174 | 175 | L.Proj.GeoJSON = L.GeoJSON.extend({ 176 | initialize: function(geojson, options) { 177 | this._callLevel = 0; 178 | L.GeoJSON.prototype.initialize.call(this, geojson, options); 179 | }, 180 | 181 | addData: function(geojson) { 182 | var crs; 183 | 184 | if (geojson) { 185 | if (geojson.crs && geojson.crs.type === 'name') { 186 | crs = new L.Proj.CRS(geojson.crs.properties.name); 187 | } else if (geojson.crs && geojson.crs.type) { 188 | crs = new L.Proj.CRS(geojson.crs.type + ':' + geojson.crs.properties.code); 189 | } 190 | 191 | if (crs !== undefined) { 192 | this.options.coordsToLatLng = function(coords) { 193 | var point = L.point(coords[0], coords[1]); 194 | return crs.projection.unproject(point); 195 | }; 196 | } 197 | } 198 | 199 | // Base class' addData might call us recursively, but 200 | // CRS shouldn't be cleared in that case, since CRS applies 201 | // to the whole GeoJSON, inluding sub-features. 202 | this._callLevel++; 203 | try { 204 | L.GeoJSON.prototype.addData.call(this, geojson); 205 | } finally { 206 | this._callLevel--; 207 | if (this._callLevel === 0) { 208 | delete this.options.coordsToLatLng; 209 | } 210 | } 211 | } 212 | }); 213 | 214 | L.Proj.geoJson = function(geojson, options) { 215 | return new L.Proj.GeoJSON(geojson, options); 216 | }; 217 | 218 | L.Proj.ImageOverlay = L.ImageOverlay.extend({ 219 | initialize: function (url, bounds, options) { 220 | L.ImageOverlay.prototype.initialize.call(this, url, null, options); 221 | this._projectedBounds = bounds; 222 | }, 223 | 224 | // Danger ahead: Overriding internal methods in Leaflet. 225 | // Decided to do this rather than making a copy of L.ImageOverlay 226 | // and doing very tiny modifications to it. 227 | // Future will tell if this was wise or not. 228 | _animateZoom: function (event) { 229 | var scale = this._map.getZoomScale(event.zoom); 230 | var northWest = L.point(this._projectedBounds.min.x, this._projectedBounds.max.y); 231 | var offset = this._projectedToNewLayerPoint(northWest, event.zoom, event.center); 232 | 233 | L.DomUtil.setTransform(this._image, offset, scale); 234 | }, 235 | 236 | _reset: function () { 237 | var zoom = this._map.getZoom(); 238 | var pixelOrigin = this._map.getPixelOrigin(); 239 | var bounds = L.bounds( 240 | this._transform(this._projectedBounds.min, zoom)._subtract(pixelOrigin), 241 | this._transform(this._projectedBounds.max, zoom)._subtract(pixelOrigin) 242 | ); 243 | var size = bounds.getSize(); 244 | 245 | L.DomUtil.setPosition(this._image, bounds.min); 246 | this._image.style.width = size.x + 'px'; 247 | this._image.style.height = size.y + 'px'; 248 | }, 249 | 250 | _projectedToNewLayerPoint: function (point, zoom, center) { 251 | var viewHalf = this._map.getSize()._divideBy(2); 252 | var newTopLeft = this._map.project(center, zoom)._subtract(viewHalf)._round(); 253 | var topLeft = newTopLeft.add(this._map._getMapPanePos()); 254 | 255 | return this._transform(point, zoom)._subtract(topLeft); 256 | }, 257 | 258 | _transform: function (point, zoom) { 259 | var crs = this._map.options.crs; 260 | var transformation = crs.transformation; 261 | var scale = crs.scale(zoom); 262 | 263 | return transformation.transform(point, scale); 264 | } 265 | }); 266 | 267 | L.Proj.imageOverlay = function (url, bounds, options) { 268 | return new L.Proj.ImageOverlay(url, bounds, options); 269 | }; 270 | 271 | return L.Proj; 272 | })); 273 | -------------------------------------------------------------------------------- /libs/clipboard/setClipboardText.js: -------------------------------------------------------------------------------- 1 | // taken from 2 | // https://ourcodeworld.com/articles/read/143/how-to-copy-text-to-clipboard-with-javascript-easily 3 | function setClipboardText(text) { 4 | var id = 'mycustom-clipboard-textarea-hidden-id'; 5 | var existsTextarea = document.getElementById(id); 6 | 7 | if (!existsTextarea) { 8 | console.log('Creating textarea'); 9 | var textarea = document.createElement('textarea'); 10 | textarea.id = id; 11 | // Place in top-left corner of screen regardless of scroll position. 12 | textarea.style.position = 'fixed'; 13 | textarea.style.top = 0; 14 | textarea.style.left = 0; 15 | 16 | // Ensure it has a small width and height. Setting to 1px / 1em 17 | // doesn't work as this gives a negative w/h on some browsers. 18 | textarea.style.width = '1px'; 19 | textarea.style.height = '1px'; 20 | 21 | // We don't need padding, reducing the size if it does flash render. 22 | textarea.style.padding = 0; 23 | 24 | // Clean up any borders. 25 | textarea.style.border = 'none'; 26 | textarea.style.outline = 'none'; 27 | textarea.style.boxShadow = 'none'; 28 | 29 | // Avoid flash of white box if rendered for any reason. 30 | textarea.style.background = 'transparent'; 31 | document.querySelector('body').appendChild(textarea); 32 | console.log('The textarea now exists :)'); 33 | existsTextarea = document.getElementById(id); 34 | } else { 35 | console.log('The textarea already exists :3'); 36 | } 37 | 38 | existsTextarea.value = text; 39 | existsTextarea.select(); 40 | 41 | try { 42 | var status = document.execCommand('copy'); 43 | if (!status) { 44 | console.error('Cannot copy text'); 45 | } else { 46 | console.log('The text is now on the clipboard'); 47 | } 48 | } catch (err) { 49 | console.log('Unable to copy.'); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /libs/crosstalk/css/crosstalk.min.css: -------------------------------------------------------------------------------- 1 | .container-fluid.crosstalk-bscols{margin-left:-30px;margin-right:-30px;white-space:normal}body>.container-fluid.crosstalk-bscols{margin-left:auto;margin-right:auto}.crosstalk-input-checkboxgroup .crosstalk-options-group .crosstalk-options-column{display:inline-block;padding-right:12px;vertical-align:top}@media only screen and (max-width: 480px){.crosstalk-input-checkboxgroup .crosstalk-options-group .crosstalk-options-column{display:block;padding-right:inherit}}.crosstalk-input{margin-bottom:15px}.crosstalk-input .control-label{margin-bottom:0;vertical-align:middle}.crosstalk-input input[type="checkbox"]{margin:4px 0 0;margin-top:1px;line-height:normal}.crosstalk-input .checkbox{position:relative;display:block;margin-top:10px;margin-bottom:10px}.crosstalk-input .checkbox>label{padding-left:20px;margin-bottom:0;font-weight:400;cursor:pointer}.crosstalk-input .checkbox input[type="checkbox"],.crosstalk-input .checkbox-inline input[type="checkbox"]{position:absolute;margin-top:2px;margin-left:-20px}.crosstalk-input .checkbox+.checkbox{margin-top:-5px}.crosstalk-input .checkbox-inline{position:relative;display:inline-block;padding-left:20px;margin-bottom:0;font-weight:400;vertical-align:middle;cursor:pointer}.crosstalk-input .checkbox-inline+.checkbox-inline{margin-top:0;margin-left:10px} 2 | -------------------------------------------------------------------------------- /libs/crosstalk/js/crosstalk.min.js: -------------------------------------------------------------------------------- 1 | !function o(u,a,l){function s(n,e){if(!a[n]){if(!u[n]){var t="function"==typeof require&&require;if(!e&&t)return t(n,!0);if(f)return f(n,!0);var r=new Error("Cannot find module '"+n+"'");throw r.code="MODULE_NOT_FOUND",r}var i=a[n]={exports:{}};u[n][0].call(i.exports,function(e){var t=u[n][1][e];return s(t||e)},i,i.exports,o,u,a,l)}return a[n].exports}for(var f="function"==typeof require&&require,e=0;e?@[\\\]^`{|}~])/g,"\\$1")+"']"),r=JSON.parse(n[0].innerText),i=e.factory(t,r);o(t).data("crosstalk-instance",i),o(t).addClass("crosstalk-input-bound")}if(t.Shiny){var e=new t.Shiny.InputBinding,u=t.jQuery;u.extend(e,{find:function(e){return u(e).find(".crosstalk-input")},initialize:function(e){var t,n;u(e).hasClass("crosstalk-input-bound")||(n=o(t=e),Object.keys(r).forEach(function(e){n.hasClass(e)&&!n.hasClass("crosstalk-input-bound")&&i(r[e],t)}))},getId:function(e){return e.id},getValue:function(e){},setValue:function(e,t){},receiveMessage:function(e,t){},subscribe:function(e,t){u(e).data("crosstalk-instance").resume()},unsubscribe:function(e){u(e).data("crosstalk-instance").suspend()}}),t.Shiny.inputBindings.register(e,"crosstalk.inputBinding")}}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{}],7:[function(r,e,t){(function(e){"use strict";var t=function(e){{if(e&&e.__esModule)return e;var t={};if(null!=e)for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&(t[n]=e[n]);return t.default=e,t}}(r("./input")),n=r("./filter");var a=e.jQuery;t.register({className:"crosstalk-input-checkboxgroup",factory:function(e,r){var i=new n.FilterHandle(r.group),o=void 0,u=a(e);return u.on("change","input[type='checkbox']",function(){var e=u.find("input[type='checkbox']:checked");if(0===e.length)o=null,i.clear();else{var t={};e.each(function(){r.map[this.value].forEach(function(e){t[e]=!0})});var n=Object.keys(t);n.sort(),o=n,i.set(n)}}),{suspend:function(){i.clear()},resume:function(){o&&i.set(o)}}}})}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{"./filter":2,"./input":6}],8:[function(r,e,t){(function(e){"use strict";var t=n(r("./input")),l=n(r("./util")),s=r("./filter");function n(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&(t[n]=e[n]);return t.default=e,t}var f=e.jQuery;t.register({className:"crosstalk-input-select",factory:function(e,n){var t=l.dataframeToD3(n.items),r={options:[{value:"",label:"(All)"}].concat(t),valueField:"value",labelField:"label",searchField:"label"},i=f(e).find("select")[0],o=f(i).selectize(r)[0].selectize,u=new s.FilterHandle(n.group),a=void 0;return o.on("change",function(){if(0===o.items.length)a=null,u.clear();else{var t={};o.items.forEach(function(e){n.map[e].forEach(function(e){t[e]=!0})});var e=Object.keys(t);e.sort(),a=e,u.set(e)}}),{suspend:function(){u.clear()},resume:function(){a&&u.set(a)}}}})}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{"./filter":2,"./input":6,"./util":11}],9:[function(n,e,t){(function(e){"use strict";var d=function(e,t){if(Array.isArray(e))return e;if(Symbol.iterator in Object(e))return function(e,t){var n=[],r=!0,i=!1,o=void 0;try{for(var u,a=e[Symbol.iterator]();!(r=(u=a.next()).done)&&(n.push(u.value),!t||n.length!==t);r=!0);}catch(e){i=!0,o=e}finally{try{!r&&a.return&&a.return()}finally{if(i)throw o}}return n}(e,t);throw new TypeError("Invalid attempt to destructure non-iterable instance")},t=function(e){{if(e&&e.__esModule)return e;var t={};if(null!=e)for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&(t[n]=e[n]);return t.default=e,t}}(n("./input")),a=n("./filter");var v=e.jQuery,p=e.strftime;function y(e,t){for(var n=e.toString();n.length .container-fluid.crosstalk-bscols { 12 | margin-left: auto; 13 | margin-right: auto; 14 | } 15 | 16 | .crosstalk-input-checkboxgroup .crosstalk-options-group .crosstalk-options-column { 17 | display: inline-block; 18 | padding-right: 12px; 19 | vertical-align: top; 20 | } 21 | 22 | @media only screen and (max-width:480px) { 23 | .crosstalk-input-checkboxgroup .crosstalk-options-group .crosstalk-options-column { 24 | display: block; 25 | padding-right: inherit; 26 | } 27 | } 28 | 29 | /* Relevant BS3 styles to make filter_checkbox() look reasonable without Bootstrap */ 30 | .crosstalk-input { 31 | margin-bottom: 15px; /* a la .form-group */ 32 | .control-label { 33 | margin-bottom: 0; 34 | vertical-align: middle; 35 | } 36 | input[type="checkbox"] { 37 | margin: 4px 0 0; 38 | margin-top: 1px; 39 | line-height: normal; 40 | } 41 | .checkbox { 42 | position: relative; 43 | display: block; 44 | margin-top: 10px; 45 | margin-bottom: 10px; 46 | } 47 | .checkbox > label{ 48 | padding-left: 20px; 49 | margin-bottom: 0; 50 | font-weight: 400; 51 | cursor: pointer; 52 | } 53 | .checkbox input[type="checkbox"], 54 | .checkbox-inline input[type="checkbox"] { 55 | position: absolute; 56 | margin-top: 2px; 57 | margin-left: -20px; 58 | } 59 | .checkbox + .checkbox { 60 | margin-top: -5px; 61 | } 62 | .checkbox-inline { 63 | position: relative; 64 | display: inline-block; 65 | padding-left: 20px; 66 | margin-bottom: 0; 67 | font-weight: 400; 68 | vertical-align: middle; 69 | cursor: pointer; 70 | } 71 | .checkbox-inline + .checkbox-inline { 72 | margin-top: 0; 73 | margin-left: 10px; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /libs/datatables-css/datatables-crosstalk.css: -------------------------------------------------------------------------------- 1 | .dt-crosstalk-fade { 2 | opacity: 0.2; 3 | } 4 | 5 | html body div.DTS div.dataTables_scrollBody { 6 | background: none; 7 | } 8 | 9 | 10 | /* 11 | Fix https://github.com/rstudio/DT/issues/563 12 | If the `table.display` is set to "block" (e.g., pkgdown), the browser will display 13 | datatable objects strangely. The search panel and the page buttons will still be 14 | in full-width but the table body will be "compact" and shorter. 15 | In therory, having this attributes will affect `dom="t"` 16 | with `display: block` users. But in reality, there should be no one. 17 | We may remove the below lines in the future if the upstream agree to have this there. 18 | See https://github.com/DataTables/DataTablesSrc/issues/160 19 | */ 20 | 21 | table.dataTable { 22 | display: table; 23 | } 24 | -------------------------------------------------------------------------------- /libs/dt-core/css/jquery.dataTables.extra.css: -------------------------------------------------------------------------------- 1 | /* Selected rows/cells */ 2 | table.dataTable tr.selected td, table.dataTable td.selected { 3 | background-color: #b0bed9 !important; 4 | } 5 | /* In case of scrollX/Y or FixedHeader */ 6 | .dataTables_scrollBody .dataTables_sizing { 7 | visibility: hidden; 8 | } 9 | 10 | /* The datatables' theme CSS file doesn't define 11 | the color but with white background. It leads to an issue that 12 | when the HTML's body color is set to 'white', the user can't 13 | see the text since the background is white. One case happens in the 14 | RStudio's IDE when inline viewing the DT table inside an Rmd file, 15 | if the IDE theme is set to "Cobalt". 16 | 17 | See https://github.com/rstudio/DT/issues/447 for more info 18 | 19 | This fixes should have little side-effects because all the other elements 20 | of the default theme use the #333 font color. 21 | 22 | TODO: The upstream may use relative colors for both the table background 23 | and the color. It means the table can display well without this patch 24 | then. At that time, we need to remove the below CSS attributes. 25 | */ 26 | div.datatables { 27 | color: #333; 28 | } 29 | -------------------------------------------------------------------------------- /libs/dt-core/css/jquery.dataTables.min.css: -------------------------------------------------------------------------------- 1 | td.dt-control{background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAACjElEQVR4Aa2V30uTURjHnVBUgglCaXVTJJVLMdhQm7+1JEvJiKAggmgSQiKJl0V0E13UVf0HQZmBLcvlpqa55tqV0YoMzIhY6js0yDH11X17vuO8Sq5Zgl/48DznPD/G63nOMSmBkoUNwhYhTUgn9NUeY8z5p0wqOVXYKRwQLMJhhYV7Kpaqck2rNdskbBPMdrv9osfjcQaDwW/RaHSR0OceY8xhrqox/a3ZZmGHkO9wOB7ouj6PBGKMOcxlzcqmdDYKGZmZmSWjo6PvIYosRuDWXGj50IwTb4+hxleN5sAVvJh4jvBCGBRzWSO121UPk3EAaUKuz+d7ycTp+Wnc/nwL1d4qHBVidmjZv/npBkJzGiiv19vFWmGrcVDsvKupqeky/056VMf1j9dQOViBKo9Aa/ixdbnYcrQGWsBc1jQ2NtrZQ/VKShHM8kvdEPVN9qJsoBRl/aUoHyiJ+UpcCxJTtmfCDYoHJT2yYyOlPtcaCoUmGGwdbkVRbxGK+2y0gg2GisUvNmJCy/BVUJqm/eBIxXqpgbUtiBise12HQlc+ClwFSKRCiZH6wXpQs7OzETWn6XENa/trYemywuK0IpGsTotgRU3fcVBzouWG6pOnpqY0BhuGGpDXeUjIW7JKcfuX3tjjP9k4FBkZN4PtY+042JGzzNMcGDJ35C7tmcU++tJmjE63OpSUuLEJ62GcfXUO+59kr8rJnlOY0WfAGtYaY/PHYPv9fhdEY7++4nz/BWS17UPWY0Es2avWp3vOYOTnCCheBtaqHskJr95kZBL3AvdR2XkEex5mYbdQ+qwCd97dRTAcXHn1Moyrt16PA2tN//V8jY+Pf4cS/VWerzU9sDay1gd2Xf4F/AZqlpeB9836LwAAAABJRU5ErkJggg==) no-repeat center center;cursor:pointer}tr.dt-hasChild td.dt-control{background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAACdUlEQVR4Aa2V30tTYRjHVSgsoQKhXPUPlKZMlImiIIg/pggiiDeCBAdqyC4Kf9zVhVdeelX33QnCbtYvxZsxxkoqKxiz1SzdFudMb5xO5/bt+fa+sPCw3IVf+PA+532e5ytn53lfK0qoSrggXBauCbUKxtxjjjVnq1IXXxFuCXeEFqGdMOYec6zRtZX/M6sWrgv1hmHcDwQCLxOJxM9CoZAnjLnHHGt0bbXdVG1cEm4KLp/P9yKXyx2jhJhjDWvZc9qUwUWhzuFwdMVisc8QFQ4PcbCygvT0NJLDw0gNDSH96DEyfj8KmQwo1rJHem8oD2VapX/sxlAo9Aqi/N4e9hYWsDMwgARxD2DH7ZZYren5eZxYFqhgMOhnr3BVeSnn216v98Hf3yqXg/X0CX719YFs9/WquJfPGtkzZ2fBWvZ4PB6DHtqrokaol7/0GqLM2hq2enpsxMmpvf3VVVD8UOJxV42Uet1Wy7J+M5mam8P37u6ySM7MgDJNM8mRUl5qYDtOREz+GB3FZlcXNjs7UUrMk/jYGKhsNnuo57TWZhgTw0hHO0EpRdpV/tvICKgjUdFQv/Lu7q7JZHxqCl9dLqGtSJtL4VJ80Wvc8xD2V9YfRUbmLZPp5WVstLTYaSWt+lmt1tISKH5Q/VFqbGOTl6GNTk7iQ3Oz4FSrU8UfuTqdBJHxceQz+2APe/8Zm+Jgh8PhNxBlt7YQNQysNzVivbEJ7wXGiiZEJiZwEI2C4mFgr/aoKnn0jk0TiWfPsTE4iHcN9xBuaMCn/n5sLy7iKJk8ffTqikfvfC4H9laWdX2lUqltaDEu9/o664LtIOVfsOf4L+APb5yaiwyN8+8AAAAASUVORK5CYII=) no-repeat center center}table.dataTable th.dt-left,table.dataTable td.dt-left{text-align:left}table.dataTable th.dt-center,table.dataTable td.dt-center,table.dataTable td.dataTables_empty{text-align:center}table.dataTable th.dt-right,table.dataTable td.dt-right{text-align:right}table.dataTable th.dt-justify,table.dataTable td.dt-justify{text-align:justify}table.dataTable th.dt-nowrap,table.dataTable td.dt-nowrap{white-space:nowrap}table.dataTable thead th.dt-head-left,table.dataTable thead td.dt-head-left,table.dataTable tfoot th.dt-head-left,table.dataTable tfoot td.dt-head-left{text-align:left}table.dataTable thead th.dt-head-center,table.dataTable thead td.dt-head-center,table.dataTable tfoot th.dt-head-center,table.dataTable tfoot td.dt-head-center{text-align:center}table.dataTable thead th.dt-head-right,table.dataTable thead td.dt-head-right,table.dataTable tfoot th.dt-head-right,table.dataTable tfoot td.dt-head-right{text-align:right}table.dataTable thead th.dt-head-justify,table.dataTable thead td.dt-head-justify,table.dataTable tfoot th.dt-head-justify,table.dataTable tfoot td.dt-head-justify{text-align:justify}table.dataTable thead th.dt-head-nowrap,table.dataTable thead td.dt-head-nowrap,table.dataTable tfoot th.dt-head-nowrap,table.dataTable tfoot td.dt-head-nowrap{white-space:nowrap}table.dataTable tbody th.dt-body-left,table.dataTable tbody td.dt-body-left{text-align:left}table.dataTable tbody th.dt-body-center,table.dataTable tbody td.dt-body-center{text-align:center}table.dataTable tbody th.dt-body-right,table.dataTable tbody td.dt-body-right{text-align:right}table.dataTable tbody th.dt-body-justify,table.dataTable tbody td.dt-body-justify{text-align:justify}table.dataTable tbody th.dt-body-nowrap,table.dataTable tbody td.dt-body-nowrap{white-space:nowrap}table.dataTable{width:100%;margin:0 auto;clear:both;border-collapse:separate;border-spacing:0}table.dataTable thead th,table.dataTable tfoot th{font-weight:bold}table.dataTable thead th,table.dataTable thead td{padding:10px 18px;border-bottom:1px solid #111}table.dataTable thead th:active,table.dataTable thead td:active{outline:none}table.dataTable tfoot th,table.dataTable tfoot td{padding:10px 18px 6px 18px;border-top:1px solid #111}table.dataTable thead .sorting,table.dataTable thead .sorting_asc,table.dataTable thead .sorting_desc,table.dataTable thead .sorting_asc_disabled,table.dataTable thead .sorting_desc_disabled{cursor:pointer;*cursor:hand;background-repeat:no-repeat;background-position:center right}table.dataTable thead .sorting{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAATCAQAAADYWf5HAAAAkElEQVQoz7XQMQ5AQBCF4dWQSJxC5wwax1Cq1e7BAdxD5SL+Tq/QCM1oNiJidwox0355mXnG/DrEtIQ6azioNZQxI0ykPhTQIwhCR+BmBYtlK7kLJYwWCcJA9M4qdrZrd8pPjZWPtOqdRQy320YSV17OatFC4euts6z39GYMKRPCTKY9UnPQ6P+GtMRfGtPnBCiqhAeJPmkqAAAAAElFTkSuQmCC)}table.dataTable thead .sorting_asc{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAATCAYAAAByUDbMAAAAZ0lEQVQ4y2NgGLKgquEuFxBPAGI2ahhWCsS/gDibUoO0gPgxEP8H4ttArEyuQYxAPBdqEAxPBImTY5gjEL9DM+wTENuQahAvEO9DMwiGdwAxOymGJQLxTyD+jgWDxCMZRsEoGAVoAADeemwtPcZI2wAAAABJRU5ErkJggg==) !important}table.dataTable thead .sorting_desc{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAATCAYAAAByUDbMAAAAZUlEQVQ4y2NgGAWjYBSggaqGu5FA/BOIv2PBIPFEUgxjB+IdQPwfC94HxLykus4GiD+hGfQOiB3J8SojEE9EM2wuSJzcsFMG4ttQgx4DsRalkZENxL+AuJQaMcsGxBOAmGvopk8AVz1sLZgg0bsAAAAASUVORK5CYII=) !important}table.dataTable thead .sorting_asc_disabled{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAATCAQAAADYWf5HAAAAWUlEQVQoz2NgGAWDCtyJvPPzznc4/HknEbsy9js77vyHw313eHGZZ3PnE1TRuzuOuK1lvDMRqmzuHUZ87lO+cxuo6PEdLUIeyb7z604pYf+y3Zlwh4u2YQoAc7ZCBHH4jigAAAAASUVORK5CYII=)}table.dataTable thead .sorting_desc_disabled{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAATCAQAAADYWf5HAAAAW0lEQVQoz2NgoCm4w3Vnwh02wspK7/y6k01Ikdadx3f+37l9RxmfIsY7c4GKQHDiHUbcyhzvvIMq+3THBpci3jv7oIpAcMcdduzKEu/8vPMdDn/eiWQYBYMKAAC3ykIEuYQJUgAAAABJRU5ErkJggg==)}table.dataTable tbody tr{background-color:#fff}table.dataTable tbody tr.selected{background-color:#b0bed9}table.dataTable tbody th,table.dataTable tbody td{padding:8px 10px}table.dataTable.row-border tbody th,table.dataTable.row-border tbody td,table.dataTable.display tbody th,table.dataTable.display tbody td{border-top:1px solid #ddd}table.dataTable.row-border tbody tr:first-child th,table.dataTable.row-border tbody tr:first-child td,table.dataTable.display tbody tr:first-child th,table.dataTable.display tbody tr:first-child td{border-top:none}table.dataTable.cell-border tbody th,table.dataTable.cell-border tbody td{border-top:1px solid #ddd;border-right:1px solid #ddd}table.dataTable.cell-border tbody tr th:first-child,table.dataTable.cell-border tbody tr td:first-child{border-left:1px solid #ddd}table.dataTable.cell-border tbody tr:first-child th,table.dataTable.cell-border tbody tr:first-child td{border-top:none}table.dataTable.stripe tbody tr.odd,table.dataTable.display tbody tr.odd{background-color:#f9f9f9}table.dataTable.stripe tbody tr.odd.selected,table.dataTable.display tbody tr.odd.selected{background-color:#acbad4}table.dataTable.hover tbody tr:hover,table.dataTable.display tbody tr:hover{background-color:#f6f6f6}table.dataTable.hover tbody tr:hover.selected,table.dataTable.display tbody tr:hover.selected{background-color:#aab7d1}table.dataTable.order-column tbody tr>.sorting_1,table.dataTable.order-column tbody tr>.sorting_2,table.dataTable.order-column tbody tr>.sorting_3,table.dataTable.display tbody tr>.sorting_1,table.dataTable.display tbody tr>.sorting_2,table.dataTable.display tbody tr>.sorting_3{background-color:#fafafa}table.dataTable.order-column tbody tr.selected>.sorting_1,table.dataTable.order-column tbody tr.selected>.sorting_2,table.dataTable.order-column tbody tr.selected>.sorting_3,table.dataTable.display tbody tr.selected>.sorting_1,table.dataTable.display tbody tr.selected>.sorting_2,table.dataTable.display tbody tr.selected>.sorting_3{background-color:#acbad5}table.dataTable.display tbody tr.odd>.sorting_1,table.dataTable.order-column.stripe tbody tr.odd>.sorting_1{background-color:#f1f1f1}table.dataTable.display tbody tr.odd>.sorting_2,table.dataTable.order-column.stripe tbody tr.odd>.sorting_2{background-color:#f3f3f3}table.dataTable.display tbody tr.odd>.sorting_3,table.dataTable.order-column.stripe tbody tr.odd>.sorting_3{background-color:whitesmoke}table.dataTable.display tbody tr.odd.selected>.sorting_1,table.dataTable.order-column.stripe tbody tr.odd.selected>.sorting_1{background-color:#a6b4cd}table.dataTable.display tbody tr.odd.selected>.sorting_2,table.dataTable.order-column.stripe tbody tr.odd.selected>.sorting_2{background-color:#a8b5cf}table.dataTable.display tbody tr.odd.selected>.sorting_3,table.dataTable.order-column.stripe tbody tr.odd.selected>.sorting_3{background-color:#a9b7d1}table.dataTable.display tbody tr.even>.sorting_1,table.dataTable.order-column.stripe tbody tr.even>.sorting_1{background-color:#fafafa}table.dataTable.display tbody tr.even>.sorting_2,table.dataTable.order-column.stripe tbody tr.even>.sorting_2{background-color:#fcfcfc}table.dataTable.display tbody tr.even>.sorting_3,table.dataTable.order-column.stripe tbody tr.even>.sorting_3{background-color:#fefefe}table.dataTable.display tbody tr.even.selected>.sorting_1,table.dataTable.order-column.stripe tbody tr.even.selected>.sorting_1{background-color:#acbad5}table.dataTable.display tbody tr.even.selected>.sorting_2,table.dataTable.order-column.stripe tbody tr.even.selected>.sorting_2{background-color:#aebcd6}table.dataTable.display tbody tr.even.selected>.sorting_3,table.dataTable.order-column.stripe tbody tr.even.selected>.sorting_3{background-color:#afbdd8}table.dataTable.display tbody tr:hover>.sorting_1,table.dataTable.order-column.hover tbody tr:hover>.sorting_1{background-color:#eaeaea}table.dataTable.display tbody tr:hover>.sorting_2,table.dataTable.order-column.hover tbody tr:hover>.sorting_2{background-color:#ececec}table.dataTable.display tbody tr:hover>.sorting_3,table.dataTable.order-column.hover tbody tr:hover>.sorting_3{background-color:#efefef}table.dataTable.display tbody tr:hover.selected>.sorting_1,table.dataTable.order-column.hover tbody tr:hover.selected>.sorting_1{background-color:#a2aec7}table.dataTable.display tbody tr:hover.selected>.sorting_2,table.dataTable.order-column.hover tbody tr:hover.selected>.sorting_2{background-color:#a3b0c9}table.dataTable.display tbody tr:hover.selected>.sorting_3,table.dataTable.order-column.hover tbody tr:hover.selected>.sorting_3{background-color:#a5b2cb}table.dataTable.no-footer{border-bottom:1px solid #111}table.dataTable.nowrap th,table.dataTable.nowrap td{white-space:nowrap}table.dataTable.compact thead th,table.dataTable.compact thead td{padding:4px 17px}table.dataTable.compact tfoot th,table.dataTable.compact tfoot td{padding:4px}table.dataTable.compact tbody th,table.dataTable.compact tbody td{padding:4px}table.dataTable th,table.dataTable td{box-sizing:content-box}.dataTables_wrapper{position:relative;clear:both}.dataTables_wrapper .dataTables_length{float:left}.dataTables_wrapper .dataTables_length select{border:1px solid #aaa;border-radius:3px;padding:5px;background-color:transparent;padding:4px}.dataTables_wrapper .dataTables_filter{float:right;text-align:right}.dataTables_wrapper .dataTables_filter input{border:1px solid #aaa;border-radius:3px;padding:5px;background-color:transparent;margin-left:3px}.dataTables_wrapper .dataTables_info{clear:both;float:left;padding-top:.755em}.dataTables_wrapper .dataTables_paginate{float:right;text-align:right;padding-top:.25em}.dataTables_wrapper .dataTables_paginate .paginate_button{box-sizing:border-box;display:inline-block;min-width:1.5em;padding:.5em 1em;margin-left:2px;text-align:center;text-decoration:none !important;cursor:pointer;*cursor:hand;color:#333 !important;border:1px solid transparent;border-radius:2px}.dataTables_wrapper .dataTables_paginate .paginate_button.current,.dataTables_wrapper .dataTables_paginate .paginate_button.current:hover{color:#333 !important;border:1px solid #979797;background-color:white;background:-webkit-gradient(linear, left top, left bottom, color-stop(0%, white), color-stop(100%, #dcdcdc));background:-webkit-linear-gradient(top, white 0%, #dcdcdc 100%);background:-moz-linear-gradient(top, white 0%, #dcdcdc 100%);background:-ms-linear-gradient(top, white 0%, #dcdcdc 100%);background:-o-linear-gradient(top, white 0%, #dcdcdc 100%);background:linear-gradient(to bottom, white 0%, #dcdcdc 100%)}.dataTables_wrapper .dataTables_paginate .paginate_button.disabled,.dataTables_wrapper .dataTables_paginate .paginate_button.disabled:hover,.dataTables_wrapper .dataTables_paginate .paginate_button.disabled:active{cursor:default;color:#666 !important;border:1px solid transparent;background:transparent;box-shadow:none}.dataTables_wrapper .dataTables_paginate .paginate_button:hover{color:white !important;border:1px solid #111;background-color:#585858;background:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #585858), color-stop(100%, #111));background:-webkit-linear-gradient(top, #585858 0%, #111 100%);background:-moz-linear-gradient(top, #585858 0%, #111 100%);background:-ms-linear-gradient(top, #585858 0%, #111 100%);background:-o-linear-gradient(top, #585858 0%, #111 100%);background:linear-gradient(to bottom, #585858 0%, #111 100%)}.dataTables_wrapper .dataTables_paginate .paginate_button:active{outline:none;background-color:#2b2b2b;background:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #2b2b2b), color-stop(100%, #0c0c0c));background:-webkit-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);background:-moz-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);background:-ms-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);background:-o-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);background:linear-gradient(to bottom, #2b2b2b 0%, #0c0c0c 100%);box-shadow:inset 0 0 3px #111}.dataTables_wrapper .dataTables_paginate .ellipsis{padding:0 1em}.dataTables_wrapper .dataTables_processing{position:absolute;top:50%;left:50%;width:100%;height:40px;margin-left:-50%;margin-top:-25px;padding-top:20px;text-align:center;font-size:1.2em;background-color:white;background:-webkit-gradient(linear, left top, right top, color-stop(0%, rgba(255, 255, 255, 0)), color-stop(25%, rgba(255, 255, 255, 0.9)), color-stop(75%, rgba(255, 255, 255, 0.9)), color-stop(100%, rgba(255, 255, 255, 0)));background:-webkit-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%);background:-moz-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%);background:-ms-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%);background:-o-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%);background:linear-gradient(to right, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%)}.dataTables_wrapper .dataTables_length,.dataTables_wrapper .dataTables_filter,.dataTables_wrapper .dataTables_info,.dataTables_wrapper .dataTables_processing,.dataTables_wrapper .dataTables_paginate{color:#333}.dataTables_wrapper .dataTables_scroll{clear:both}.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody{*margin-top:-1px;-webkit-overflow-scrolling:touch}.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>thead>tr>th,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>thead>tr>td,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>tbody>tr>th,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>tbody>tr>td{vertical-align:middle}.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>thead>tr>th>div.dataTables_sizing,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>thead>tr>td>div.dataTables_sizing,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>tbody>tr>th>div.dataTables_sizing,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>tbody>tr>td>div.dataTables_sizing{height:0;overflow:hidden;margin:0 !important;padding:0 !important}.dataTables_wrapper.no-footer .dataTables_scrollBody{border-bottom:1px solid #111}.dataTables_wrapper.no-footer div.dataTables_scrollHead table.dataTable,.dataTables_wrapper.no-footer div.dataTables_scrollBody>table{border-bottom:none}.dataTables_wrapper:after{visibility:hidden;display:block;content:"";clear:both;height:0}@media screen and (max-width: 767px){.dataTables_wrapper .dataTables_info,.dataTables_wrapper .dataTables_paginate{float:none;text-align:center}.dataTables_wrapper .dataTables_paginate{margin-top:.5em}}@media screen and (max-width: 640px){.dataTables_wrapper .dataTables_length,.dataTables_wrapper .dataTables_filter{float:none;text-align:center}.dataTables_wrapper .dataTables_filter{margin-top:.5em}} 2 | -------------------------------------------------------------------------------- /libs/header-attrs-2.11/header-attrs.js: -------------------------------------------------------------------------------- 1 | // Pandoc 2.9 adds attributes on both header and div. We remove the former (to 2 | // be compatible with the behavior of Pandoc < 2.8). 3 | document.addEventListener('DOMContentLoaded', function(e) { 4 | var hs = document.querySelectorAll("div.section[class*='level'] > :first-child"); 5 | var i, h, a; 6 | for (i = 0; i < hs.length; i++) { 7 | h = hs[i]; 8 | if (!/^h[1-6]$/i.test(h.tagName)) continue; // it should be a header h1-h6 9 | a = h.attributes; 10 | while (a.length > 0) h.removeAttribute(a[0].name); 11 | } 12 | }); 13 | -------------------------------------------------------------------------------- /libs/header-attrs/header-attrs.js: -------------------------------------------------------------------------------- 1 | // Pandoc 2.9 adds attributes on both header and div. We remove the former (to 2 | // be compatible with the behavior of Pandoc < 2.8). 3 | document.addEventListener('DOMContentLoaded', function(e) { 4 | var hs = document.querySelectorAll("div.section[class*='level'] > :first-child"); 5 | var i, h, a; 6 | for (i = 0; i < hs.length; i++) { 7 | h = hs[i]; 8 | if (!/^h[1-6]$/i.test(h.tagName)) continue; // it should be a header h1-h6 9 | a = h.attributes; 10 | while (a.length > 0) h.removeAttribute(a[0].name); 11 | } 12 | }); 13 | -------------------------------------------------------------------------------- /libs/htmlwidgets/htmlwidgets.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | // If window.HTMLWidgets is already defined, then use it; otherwise create a 3 | // new object. This allows preceding code to set options that affect the 4 | // initialization process (though none currently exist). 5 | window.HTMLWidgets = window.HTMLWidgets || {}; 6 | 7 | // See if we're running in a viewer pane. If not, we're in a web browser. 8 | var viewerMode = window.HTMLWidgets.viewerMode = 9 | /\bviewer_pane=1\b/.test(window.location); 10 | 11 | // See if we're running in Shiny mode. If not, it's a static document. 12 | // Note that static widgets can appear in both Shiny and static modes, but 13 | // obviously, Shiny widgets can only appear in Shiny apps/documents. 14 | var shinyMode = window.HTMLWidgets.shinyMode = 15 | typeof(window.Shiny) !== "undefined" && !!window.Shiny.outputBindings; 16 | 17 | // We can't count on jQuery being available, so we implement our own 18 | // version if necessary. 19 | function querySelectorAll(scope, selector) { 20 | if (typeof(jQuery) !== "undefined" && scope instanceof jQuery) { 21 | return scope.find(selector); 22 | } 23 | if (scope.querySelectorAll) { 24 | return scope.querySelectorAll(selector); 25 | } 26 | } 27 | 28 | function asArray(value) { 29 | if (value === null) 30 | return []; 31 | if ($.isArray(value)) 32 | return value; 33 | return [value]; 34 | } 35 | 36 | // Implement jQuery's extend 37 | function extend(target /*, ... */) { 38 | if (arguments.length == 1) { 39 | return target; 40 | } 41 | for (var i = 1; i < arguments.length; i++) { 42 | var source = arguments[i]; 43 | for (var prop in source) { 44 | if (source.hasOwnProperty(prop)) { 45 | target[prop] = source[prop]; 46 | } 47 | } 48 | } 49 | return target; 50 | } 51 | 52 | // IE8 doesn't support Array.forEach. 53 | function forEach(values, callback, thisArg) { 54 | if (values.forEach) { 55 | values.forEach(callback, thisArg); 56 | } else { 57 | for (var i = 0; i < values.length; i++) { 58 | callback.call(thisArg, values[i], i, values); 59 | } 60 | } 61 | } 62 | 63 | // Replaces the specified method with the return value of funcSource. 64 | // 65 | // Note that funcSource should not BE the new method, it should be a function 66 | // that RETURNS the new method. funcSource receives a single argument that is 67 | // the overridden method, it can be called from the new method. The overridden 68 | // method can be called like a regular function, it has the target permanently 69 | // bound to it so "this" will work correctly. 70 | function overrideMethod(target, methodName, funcSource) { 71 | var superFunc = target[methodName] || function() {}; 72 | var superFuncBound = function() { 73 | return superFunc.apply(target, arguments); 74 | }; 75 | target[methodName] = funcSource(superFuncBound); 76 | } 77 | 78 | // Add a method to delegator that, when invoked, calls 79 | // delegatee.methodName. If there is no such method on 80 | // the delegatee, but there was one on delegator before 81 | // delegateMethod was called, then the original version 82 | // is invoked instead. 83 | // For example: 84 | // 85 | // var a = { 86 | // method1: function() { console.log('a1'); } 87 | // method2: function() { console.log('a2'); } 88 | // }; 89 | // var b = { 90 | // method1: function() { console.log('b1'); } 91 | // }; 92 | // delegateMethod(a, b, "method1"); 93 | // delegateMethod(a, b, "method2"); 94 | // a.method1(); 95 | // a.method2(); 96 | // 97 | // The output would be "b1", "a2". 98 | function delegateMethod(delegator, delegatee, methodName) { 99 | var inherited = delegator[methodName]; 100 | delegator[methodName] = function() { 101 | var target = delegatee; 102 | var method = delegatee[methodName]; 103 | 104 | // The method doesn't exist on the delegatee. Instead, 105 | // call the method on the delegator, if it exists. 106 | if (!method) { 107 | target = delegator; 108 | method = inherited; 109 | } 110 | 111 | if (method) { 112 | return method.apply(target, arguments); 113 | } 114 | }; 115 | } 116 | 117 | // Implement a vague facsimilie of jQuery's data method 118 | function elementData(el, name, value) { 119 | if (arguments.length == 2) { 120 | return el["htmlwidget_data_" + name]; 121 | } else if (arguments.length == 3) { 122 | el["htmlwidget_data_" + name] = value; 123 | return el; 124 | } else { 125 | throw new Error("Wrong number of arguments for elementData: " + 126 | arguments.length); 127 | } 128 | } 129 | 130 | // http://stackoverflow.com/questions/3446170/escape-string-for-use-in-javascript-regex 131 | function escapeRegExp(str) { 132 | return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); 133 | } 134 | 135 | function hasClass(el, className) { 136 | var re = new RegExp("\\b" + escapeRegExp(className) + "\\b"); 137 | return re.test(el.className); 138 | } 139 | 140 | // elements - array (or array-like object) of HTML elements 141 | // className - class name to test for 142 | // include - if true, only return elements with given className; 143 | // if false, only return elements *without* given className 144 | function filterByClass(elements, className, include) { 145 | var results = []; 146 | for (var i = 0; i < elements.length; i++) { 147 | if (hasClass(elements[i], className) == include) 148 | results.push(elements[i]); 149 | } 150 | return results; 151 | } 152 | 153 | function on(obj, eventName, func) { 154 | if (obj.addEventListener) { 155 | obj.addEventListener(eventName, func, false); 156 | } else if (obj.attachEvent) { 157 | obj.attachEvent(eventName, func); 158 | } 159 | } 160 | 161 | function off(obj, eventName, func) { 162 | if (obj.removeEventListener) 163 | obj.removeEventListener(eventName, func, false); 164 | else if (obj.detachEvent) { 165 | obj.detachEvent(eventName, func); 166 | } 167 | } 168 | 169 | // Translate array of values to top/right/bottom/left, as usual with 170 | // the "padding" CSS property 171 | // https://developer.mozilla.org/en-US/docs/Web/CSS/padding 172 | function unpackPadding(value) { 173 | if (typeof(value) === "number") 174 | value = [value]; 175 | if (value.length === 1) { 176 | return {top: value[0], right: value[0], bottom: value[0], left: value[0]}; 177 | } 178 | if (value.length === 2) { 179 | return {top: value[0], right: value[1], bottom: value[0], left: value[1]}; 180 | } 181 | if (value.length === 3) { 182 | return {top: value[0], right: value[1], bottom: value[2], left: value[1]}; 183 | } 184 | if (value.length === 4) { 185 | return {top: value[0], right: value[1], bottom: value[2], left: value[3]}; 186 | } 187 | } 188 | 189 | // Convert an unpacked padding object to a CSS value 190 | function paddingToCss(paddingObj) { 191 | return paddingObj.top + "px " + paddingObj.right + "px " + paddingObj.bottom + "px " + paddingObj.left + "px"; 192 | } 193 | 194 | // Makes a number suitable for CSS 195 | function px(x) { 196 | if (typeof(x) === "number") 197 | return x + "px"; 198 | else 199 | return x; 200 | } 201 | 202 | // Retrieves runtime widget sizing information for an element. 203 | // The return value is either null, or an object with fill, padding, 204 | // defaultWidth, defaultHeight fields. 205 | function sizingPolicy(el) { 206 | var sizingEl = document.querySelector("script[data-for='" + el.id + "'][type='application/htmlwidget-sizing']"); 207 | if (!sizingEl) 208 | return null; 209 | var sp = JSON.parse(sizingEl.textContent || sizingEl.text || "{}"); 210 | if (viewerMode) { 211 | return sp.viewer; 212 | } else { 213 | return sp.browser; 214 | } 215 | } 216 | 217 | // @param tasks Array of strings (or falsy value, in which case no-op). 218 | // Each element must be a valid JavaScript expression that yields a 219 | // function. Or, can be an array of objects with "code" and "data" 220 | // properties; in this case, the "code" property should be a string 221 | // of JS that's an expr that yields a function, and "data" should be 222 | // an object that will be added as an additional argument when that 223 | // function is called. 224 | // @param target The object that will be "this" for each function 225 | // execution. 226 | // @param args Array of arguments to be passed to the functions. (The 227 | // same arguments will be passed to all functions.) 228 | function evalAndRun(tasks, target, args) { 229 | if (tasks) { 230 | forEach(tasks, function(task) { 231 | var theseArgs = args; 232 | if (typeof(task) === "object") { 233 | theseArgs = theseArgs.concat([task.data]); 234 | task = task.code; 235 | } 236 | var taskFunc = tryEval(task); 237 | if (typeof(taskFunc) !== "function") { 238 | throw new Error("Task must be a function! Source:\n" + task); 239 | } 240 | taskFunc.apply(target, theseArgs); 241 | }); 242 | } 243 | } 244 | 245 | // Attempt eval() both with and without enclosing in parentheses. 246 | // Note that enclosing coerces a function declaration into 247 | // an expression that eval() can parse 248 | // (otherwise, a SyntaxError is thrown) 249 | function tryEval(code) { 250 | var result = null; 251 | try { 252 | result = eval("(" + code + ")"); 253 | } catch(error) { 254 | if (!(error instanceof SyntaxError)) { 255 | throw error; 256 | } 257 | try { 258 | result = eval(code); 259 | } catch(e) { 260 | if (e instanceof SyntaxError) { 261 | throw error; 262 | } else { 263 | throw e; 264 | } 265 | } 266 | } 267 | return result; 268 | } 269 | 270 | function initSizing(el) { 271 | var sizing = sizingPolicy(el); 272 | if (!sizing) 273 | return; 274 | 275 | var cel = document.getElementById("htmlwidget_container"); 276 | if (!cel) 277 | return; 278 | 279 | if (typeof(sizing.padding) !== "undefined") { 280 | document.body.style.margin = "0"; 281 | document.body.style.padding = paddingToCss(unpackPadding(sizing.padding)); 282 | } 283 | 284 | if (sizing.fill) { 285 | document.body.style.overflow = "hidden"; 286 | document.body.style.width = "100%"; 287 | document.body.style.height = "100%"; 288 | document.documentElement.style.width = "100%"; 289 | document.documentElement.style.height = "100%"; 290 | if (cel) { 291 | cel.style.position = "absolute"; 292 | var pad = unpackPadding(sizing.padding); 293 | cel.style.top = pad.top + "px"; 294 | cel.style.right = pad.right + "px"; 295 | cel.style.bottom = pad.bottom + "px"; 296 | cel.style.left = pad.left + "px"; 297 | el.style.width = "100%"; 298 | el.style.height = "100%"; 299 | } 300 | 301 | return { 302 | getWidth: function() { return cel.offsetWidth; }, 303 | getHeight: function() { return cel.offsetHeight; } 304 | }; 305 | 306 | } else { 307 | el.style.width = px(sizing.width); 308 | el.style.height = px(sizing.height); 309 | 310 | return { 311 | getWidth: function() { return el.offsetWidth; }, 312 | getHeight: function() { return el.offsetHeight; } 313 | }; 314 | } 315 | } 316 | 317 | // Default implementations for methods 318 | var defaults = { 319 | find: function(scope) { 320 | return querySelectorAll(scope, "." + this.name); 321 | }, 322 | renderError: function(el, err) { 323 | var $el = $(el); 324 | 325 | this.clearError(el); 326 | 327 | // Add all these error classes, as Shiny does 328 | var errClass = "shiny-output-error"; 329 | if (err.type !== null) { 330 | // use the classes of the error condition as CSS class names 331 | errClass = errClass + " " + $.map(asArray(err.type), function(type) { 332 | return errClass + "-" + type; 333 | }).join(" "); 334 | } 335 | errClass = errClass + " htmlwidgets-error"; 336 | 337 | // Is el inline or block? If inline or inline-block, just display:none it 338 | // and add an inline error. 339 | var display = $el.css("display"); 340 | $el.data("restore-display-mode", display); 341 | 342 | if (display === "inline" || display === "inline-block") { 343 | $el.hide(); 344 | if (err.message !== "") { 345 | var errorSpan = $("").addClass(errClass); 346 | errorSpan.text(err.message); 347 | $el.after(errorSpan); 348 | } 349 | } else if (display === "block") { 350 | // If block, add an error just after the el, set visibility:none on the 351 | // el, and position the error to be on top of the el. 352 | // Mark it with a unique ID and CSS class so we can remove it later. 353 | $el.css("visibility", "hidden"); 354 | if (err.message !== "") { 355 | var errorDiv = $("
").addClass(errClass).css("position", "absolute") 356 | .css("top", el.offsetTop) 357 | .css("left", el.offsetLeft) 358 | // setting width can push out the page size, forcing otherwise 359 | // unnecessary scrollbars to appear and making it impossible for 360 | // the element to shrink; so use max-width instead 361 | .css("maxWidth", el.offsetWidth) 362 | .css("height", el.offsetHeight); 363 | errorDiv.text(err.message); 364 | $el.after(errorDiv); 365 | 366 | // Really dumb way to keep the size/position of the error in sync with 367 | // the parent element as the window is resized or whatever. 368 | var intId = setInterval(function() { 369 | if (!errorDiv[0].parentElement) { 370 | clearInterval(intId); 371 | return; 372 | } 373 | errorDiv 374 | .css("top", el.offsetTop) 375 | .css("left", el.offsetLeft) 376 | .css("maxWidth", el.offsetWidth) 377 | .css("height", el.offsetHeight); 378 | }, 500); 379 | } 380 | } 381 | }, 382 | clearError: function(el) { 383 | var $el = $(el); 384 | var display = $el.data("restore-display-mode"); 385 | $el.data("restore-display-mode", null); 386 | 387 | if (display === "inline" || display === "inline-block") { 388 | if (display) 389 | $el.css("display", display); 390 | $(el.nextSibling).filter(".htmlwidgets-error").remove(); 391 | } else if (display === "block"){ 392 | $el.css("visibility", "inherit"); 393 | $(el.nextSibling).filter(".htmlwidgets-error").remove(); 394 | } 395 | }, 396 | sizing: {} 397 | }; 398 | 399 | // Called by widget bindings to register a new type of widget. The definition 400 | // object can contain the following properties: 401 | // - name (required) - A string indicating the binding name, which will be 402 | // used by default as the CSS classname to look for. 403 | // - initialize (optional) - A function(el) that will be called once per 404 | // widget element; if a value is returned, it will be passed as the third 405 | // value to renderValue. 406 | // - renderValue (required) - A function(el, data, initValue) that will be 407 | // called with data. Static contexts will cause this to be called once per 408 | // element; Shiny apps will cause this to be called multiple times per 409 | // element, as the data changes. 410 | window.HTMLWidgets.widget = function(definition) { 411 | if (!definition.name) { 412 | throw new Error("Widget must have a name"); 413 | } 414 | if (!definition.type) { 415 | throw new Error("Widget must have a type"); 416 | } 417 | // Currently we only support output widgets 418 | if (definition.type !== "output") { 419 | throw new Error("Unrecognized widget type '" + definition.type + "'"); 420 | } 421 | // TODO: Verify that .name is a valid CSS classname 422 | 423 | // Support new-style instance-bound definitions. Old-style class-bound 424 | // definitions have one widget "object" per widget per type/class of 425 | // widget; the renderValue and resize methods on such widget objects 426 | // take el and instance arguments, because the widget object can't 427 | // store them. New-style instance-bound definitions have one widget 428 | // object per widget instance; the definition that's passed in doesn't 429 | // provide renderValue or resize methods at all, just the single method 430 | // factory(el, width, height) 431 | // which returns an object that has renderValue(x) and resize(w, h). 432 | // This enables a far more natural programming style for the widget 433 | // author, who can store per-instance state using either OO-style 434 | // instance fields or functional-style closure variables (I guess this 435 | // is in contrast to what can only be called C-style pseudo-OO which is 436 | // what we required before). 437 | if (definition.factory) { 438 | definition = createLegacyDefinitionAdapter(definition); 439 | } 440 | 441 | if (!definition.renderValue) { 442 | throw new Error("Widget must have a renderValue function"); 443 | } 444 | 445 | // For static rendering (non-Shiny), use a simple widget registration 446 | // scheme. We also use this scheme for Shiny apps/documents that also 447 | // contain static widgets. 448 | window.HTMLWidgets.widgets = window.HTMLWidgets.widgets || []; 449 | // Merge defaults into the definition; don't mutate the original definition. 450 | var staticBinding = extend({}, defaults, definition); 451 | overrideMethod(staticBinding, "find", function(superfunc) { 452 | return function(scope) { 453 | var results = superfunc(scope); 454 | // Filter out Shiny outputs, we only want the static kind 455 | return filterByClass(results, "html-widget-output", false); 456 | }; 457 | }); 458 | window.HTMLWidgets.widgets.push(staticBinding); 459 | 460 | if (shinyMode) { 461 | // Shiny is running. Register the definition with an output binding. 462 | // The definition itself will not be the output binding, instead 463 | // we will make an output binding object that delegates to the 464 | // definition. This is because we foolishly used the same method 465 | // name (renderValue) for htmlwidgets definition and Shiny bindings 466 | // but they actually have quite different semantics (the Shiny 467 | // bindings receive data that includes lots of metadata that it 468 | // strips off before calling htmlwidgets renderValue). We can't 469 | // just ignore the difference because in some widgets it's helpful 470 | // to call this.renderValue() from inside of resize(), and if 471 | // we're not delegating, then that call will go to the Shiny 472 | // version instead of the htmlwidgets version. 473 | 474 | // Merge defaults with definition, without mutating either. 475 | var bindingDef = extend({}, defaults, definition); 476 | 477 | // This object will be our actual Shiny binding. 478 | var shinyBinding = new Shiny.OutputBinding(); 479 | 480 | // With a few exceptions, we'll want to simply use the bindingDef's 481 | // version of methods if they are available, otherwise fall back to 482 | // Shiny's defaults. NOTE: If Shiny's output bindings gain additional 483 | // methods in the future, and we want them to be overrideable by 484 | // HTMLWidget binding definitions, then we'll need to add them to this 485 | // list. 486 | delegateMethod(shinyBinding, bindingDef, "getId"); 487 | delegateMethod(shinyBinding, bindingDef, "onValueChange"); 488 | delegateMethod(shinyBinding, bindingDef, "onValueError"); 489 | delegateMethod(shinyBinding, bindingDef, "renderError"); 490 | delegateMethod(shinyBinding, bindingDef, "clearError"); 491 | delegateMethod(shinyBinding, bindingDef, "showProgress"); 492 | 493 | // The find, renderValue, and resize are handled differently, because we 494 | // want to actually decorate the behavior of the bindingDef methods. 495 | 496 | shinyBinding.find = function(scope) { 497 | var results = bindingDef.find(scope); 498 | 499 | // Only return elements that are Shiny outputs, not static ones 500 | var dynamicResults = results.filter(".html-widget-output"); 501 | 502 | // It's possible that whatever caused Shiny to think there might be 503 | // new dynamic outputs, also caused there to be new static outputs. 504 | // Since there might be lots of different htmlwidgets bindings, we 505 | // schedule execution for later--no need to staticRender multiple 506 | // times. 507 | if (results.length !== dynamicResults.length) 508 | scheduleStaticRender(); 509 | 510 | return dynamicResults; 511 | }; 512 | 513 | // Wrap renderValue to handle initialization, which unfortunately isn't 514 | // supported natively by Shiny at the time of this writing. 515 | 516 | shinyBinding.renderValue = function(el, data) { 517 | Shiny.renderDependencies(data.deps); 518 | // Resolve strings marked as javascript literals to objects 519 | if (!(data.evals instanceof Array)) data.evals = [data.evals]; 520 | for (var i = 0; data.evals && i < data.evals.length; i++) { 521 | window.HTMLWidgets.evaluateStringMember(data.x, data.evals[i]); 522 | } 523 | if (!bindingDef.renderOnNullValue) { 524 | if (data.x === null) { 525 | el.style.visibility = "hidden"; 526 | return; 527 | } else { 528 | el.style.visibility = "inherit"; 529 | } 530 | } 531 | if (!elementData(el, "initialized")) { 532 | initSizing(el); 533 | 534 | elementData(el, "initialized", true); 535 | if (bindingDef.initialize) { 536 | var result = bindingDef.initialize(el, el.offsetWidth, 537 | el.offsetHeight); 538 | elementData(el, "init_result", result); 539 | } 540 | } 541 | bindingDef.renderValue(el, data.x, elementData(el, "init_result")); 542 | evalAndRun(data.jsHooks.render, elementData(el, "init_result"), [el, data.x]); 543 | }; 544 | 545 | // Only override resize if bindingDef implements it 546 | if (bindingDef.resize) { 547 | shinyBinding.resize = function(el, width, height) { 548 | // Shiny can call resize before initialize/renderValue have been 549 | // called, which doesn't make sense for widgets. 550 | if (elementData(el, "initialized")) { 551 | bindingDef.resize(el, width, height, elementData(el, "init_result")); 552 | } 553 | }; 554 | } 555 | 556 | Shiny.outputBindings.register(shinyBinding, bindingDef.name); 557 | } 558 | }; 559 | 560 | var scheduleStaticRenderTimerId = null; 561 | function scheduleStaticRender() { 562 | if (!scheduleStaticRenderTimerId) { 563 | scheduleStaticRenderTimerId = setTimeout(function() { 564 | scheduleStaticRenderTimerId = null; 565 | window.HTMLWidgets.staticRender(); 566 | }, 1); 567 | } 568 | } 569 | 570 | // Render static widgets after the document finishes loading 571 | // Statically render all elements that are of this widget's class 572 | window.HTMLWidgets.staticRender = function() { 573 | var bindings = window.HTMLWidgets.widgets || []; 574 | forEach(bindings, function(binding) { 575 | var matches = binding.find(document.documentElement); 576 | forEach(matches, function(el) { 577 | var sizeObj = initSizing(el, binding); 578 | 579 | if (hasClass(el, "html-widget-static-bound")) 580 | return; 581 | el.className = el.className + " html-widget-static-bound"; 582 | 583 | var initResult; 584 | if (binding.initialize) { 585 | initResult = binding.initialize(el, 586 | sizeObj ? sizeObj.getWidth() : el.offsetWidth, 587 | sizeObj ? sizeObj.getHeight() : el.offsetHeight 588 | ); 589 | elementData(el, "init_result", initResult); 590 | } 591 | 592 | if (binding.resize) { 593 | var lastSize = { 594 | w: sizeObj ? sizeObj.getWidth() : el.offsetWidth, 595 | h: sizeObj ? sizeObj.getHeight() : el.offsetHeight 596 | }; 597 | var resizeHandler = function(e) { 598 | var size = { 599 | w: sizeObj ? sizeObj.getWidth() : el.offsetWidth, 600 | h: sizeObj ? sizeObj.getHeight() : el.offsetHeight 601 | }; 602 | if (size.w === 0 && size.h === 0) 603 | return; 604 | if (size.w === lastSize.w && size.h === lastSize.h) 605 | return; 606 | lastSize = size; 607 | binding.resize(el, size.w, size.h, initResult); 608 | }; 609 | 610 | on(window, "resize", resizeHandler); 611 | 612 | // This is needed for cases where we're running in a Shiny 613 | // app, but the widget itself is not a Shiny output, but 614 | // rather a simple static widget. One example of this is 615 | // an rmarkdown document that has runtime:shiny and widget 616 | // that isn't in a render function. Shiny only knows to 617 | // call resize handlers for Shiny outputs, not for static 618 | // widgets, so we do it ourselves. 619 | if (window.jQuery) { 620 | window.jQuery(document).on( 621 | "shown.htmlwidgets shown.bs.tab.htmlwidgets shown.bs.collapse.htmlwidgets", 622 | resizeHandler 623 | ); 624 | window.jQuery(document).on( 625 | "hidden.htmlwidgets hidden.bs.tab.htmlwidgets hidden.bs.collapse.htmlwidgets", 626 | resizeHandler 627 | ); 628 | } 629 | 630 | // This is needed for the specific case of ioslides, which 631 | // flips slides between display:none and display:block. 632 | // Ideally we would not have to have ioslide-specific code 633 | // here, but rather have ioslides raise a generic event, 634 | // but the rmarkdown package just went to CRAN so the 635 | // window to getting that fixed may be long. 636 | if (window.addEventListener) { 637 | // It's OK to limit this to window.addEventListener 638 | // browsers because ioslides itself only supports 639 | // such browsers. 640 | on(document, "slideenter", resizeHandler); 641 | on(document, "slideleave", resizeHandler); 642 | } 643 | } 644 | 645 | var scriptData = document.querySelector("script[data-for='" + el.id + "'][type='application/json']"); 646 | if (scriptData) { 647 | var data = JSON.parse(scriptData.textContent || scriptData.text); 648 | // Resolve strings marked as javascript literals to objects 649 | if (!(data.evals instanceof Array)) data.evals = [data.evals]; 650 | for (var k = 0; data.evals && k < data.evals.length; k++) { 651 | window.HTMLWidgets.evaluateStringMember(data.x, data.evals[k]); 652 | } 653 | binding.renderValue(el, data.x, initResult); 654 | evalAndRun(data.jsHooks.render, initResult, [el, data.x]); 655 | } 656 | }); 657 | }); 658 | 659 | invokePostRenderHandlers(); 660 | } 661 | 662 | 663 | function has_jQuery3() { 664 | if (!window.jQuery) { 665 | return false; 666 | } 667 | var $version = window.jQuery.fn.jquery; 668 | var $major_version = parseInt($version.split(".")[0]); 669 | return $major_version >= 3; 670 | } 671 | 672 | /* 673 | / Shiny 1.4 bumped jQuery from 1.x to 3.x which means jQuery's 674 | / on-ready handler (i.e., $(fn)) is now asyncronous (i.e., it now 675 | / really means $(setTimeout(fn)). 676 | / https://jquery.com/upgrade-guide/3.0/#breaking-change-document-ready-handlers-are-now-asynchronous 677 | / 678 | / Since Shiny uses $() to schedule initShiny, shiny>=1.4 calls initShiny 679 | / one tick later than it did before, which means staticRender() is 680 | / called renderValue() earlier than (advanced) widget authors might be expecting. 681 | / https://github.com/rstudio/shiny/issues/2630 682 | / 683 | / For a concrete example, leaflet has some methods (e.g., updateBounds) 684 | / which reference Shiny methods registered in initShiny (e.g., setInputValue). 685 | / Since leaflet is privy to this life-cycle, it knows to use setTimeout() to 686 | / delay execution of those methods (until Shiny methods are ready) 687 | / https://github.com/rstudio/leaflet/blob/18ec981/javascript/src/index.js#L266-L268 688 | / 689 | / Ideally widget authors wouldn't need to use this setTimeout() hack that 690 | / leaflet uses to call Shiny methods on a staticRender(). In the long run, 691 | / the logic initShiny should be broken up so that method registration happens 692 | / right away, but binding happens later. 693 | */ 694 | function maybeStaticRenderLater() { 695 | if (shinyMode && has_jQuery3()) { 696 | window.jQuery(window.HTMLWidgets.staticRender); 697 | } else { 698 | window.HTMLWidgets.staticRender(); 699 | } 700 | } 701 | 702 | if (document.addEventListener) { 703 | document.addEventListener("DOMContentLoaded", function() { 704 | document.removeEventListener("DOMContentLoaded", arguments.callee, false); 705 | maybeStaticRenderLater(); 706 | }, false); 707 | } else if (document.attachEvent) { 708 | document.attachEvent("onreadystatechange", function() { 709 | if (document.readyState === "complete") { 710 | document.detachEvent("onreadystatechange", arguments.callee); 711 | maybeStaticRenderLater(); 712 | } 713 | }); 714 | } 715 | 716 | 717 | window.HTMLWidgets.getAttachmentUrl = function(depname, key) { 718 | // If no key, default to the first item 719 | if (typeof(key) === "undefined") 720 | key = 1; 721 | 722 | var link = document.getElementById(depname + "-" + key + "-attachment"); 723 | if (!link) { 724 | throw new Error("Attachment " + depname + "/" + key + " not found in document"); 725 | } 726 | return link.getAttribute("href"); 727 | }; 728 | 729 | window.HTMLWidgets.dataframeToD3 = function(df) { 730 | var names = []; 731 | var length; 732 | for (var name in df) { 733 | if (df.hasOwnProperty(name)) 734 | names.push(name); 735 | if (typeof(df[name]) !== "object" || typeof(df[name].length) === "undefined") { 736 | throw new Error("All fields must be arrays"); 737 | } else if (typeof(length) !== "undefined" && length !== df[name].length) { 738 | throw new Error("All fields must be arrays of the same length"); 739 | } 740 | length = df[name].length; 741 | } 742 | var results = []; 743 | var item; 744 | for (var row = 0; row < length; row++) { 745 | item = {}; 746 | for (var col = 0; col < names.length; col++) { 747 | item[names[col]] = df[names[col]][row]; 748 | } 749 | results.push(item); 750 | } 751 | return results; 752 | }; 753 | 754 | window.HTMLWidgets.transposeArray2D = function(array) { 755 | if (array.length === 0) return array; 756 | var newArray = array[0].map(function(col, i) { 757 | return array.map(function(row) { 758 | return row[i] 759 | }) 760 | }); 761 | return newArray; 762 | }; 763 | // Split value at splitChar, but allow splitChar to be escaped 764 | // using escapeChar. Any other characters escaped by escapeChar 765 | // will be included as usual (including escapeChar itself). 766 | function splitWithEscape(value, splitChar, escapeChar) { 767 | var results = []; 768 | var escapeMode = false; 769 | var currentResult = ""; 770 | for (var pos = 0; pos < value.length; pos++) { 771 | if (!escapeMode) { 772 | if (value[pos] === splitChar) { 773 | results.push(currentResult); 774 | currentResult = ""; 775 | } else if (value[pos] === escapeChar) { 776 | escapeMode = true; 777 | } else { 778 | currentResult += value[pos]; 779 | } 780 | } else { 781 | currentResult += value[pos]; 782 | escapeMode = false; 783 | } 784 | } 785 | if (currentResult !== "") { 786 | results.push(currentResult); 787 | } 788 | return results; 789 | } 790 | // Function authored by Yihui/JJ Allaire 791 | window.HTMLWidgets.evaluateStringMember = function(o, member) { 792 | var parts = splitWithEscape(member, '.', '\\'); 793 | for (var i = 0, l = parts.length; i < l; i++) { 794 | var part = parts[i]; 795 | // part may be a character or 'numeric' member name 796 | if (o !== null && typeof o === "object" && part in o) { 797 | if (i == (l - 1)) { // if we are at the end of the line then evalulate 798 | if (typeof o[part] === "string") 799 | o[part] = tryEval(o[part]); 800 | } else { // otherwise continue to next embedded object 801 | o = o[part]; 802 | } 803 | } 804 | } 805 | }; 806 | 807 | // Retrieve the HTMLWidget instance (i.e. the return value of an 808 | // HTMLWidget binding's initialize() or factory() function) 809 | // associated with an element, or null if none. 810 | window.HTMLWidgets.getInstance = function(el) { 811 | return elementData(el, "init_result"); 812 | }; 813 | 814 | // Finds the first element in the scope that matches the selector, 815 | // and returns the HTMLWidget instance (i.e. the return value of 816 | // an HTMLWidget binding's initialize() or factory() function) 817 | // associated with that element, if any. If no element matches the 818 | // selector, or the first matching element has no HTMLWidget 819 | // instance associated with it, then null is returned. 820 | // 821 | // The scope argument is optional, and defaults to window.document. 822 | window.HTMLWidgets.find = function(scope, selector) { 823 | if (arguments.length == 1) { 824 | selector = scope; 825 | scope = document; 826 | } 827 | 828 | var el = scope.querySelector(selector); 829 | if (el === null) { 830 | return null; 831 | } else { 832 | return window.HTMLWidgets.getInstance(el); 833 | } 834 | }; 835 | 836 | // Finds all elements in the scope that match the selector, and 837 | // returns the HTMLWidget instances (i.e. the return values of 838 | // an HTMLWidget binding's initialize() or factory() function) 839 | // associated with the elements, in an array. If elements that 840 | // match the selector don't have an associated HTMLWidget 841 | // instance, the returned array will contain nulls. 842 | // 843 | // The scope argument is optional, and defaults to window.document. 844 | window.HTMLWidgets.findAll = function(scope, selector) { 845 | if (arguments.length == 1) { 846 | selector = scope; 847 | scope = document; 848 | } 849 | 850 | var nodes = scope.querySelectorAll(selector); 851 | var results = []; 852 | for (var i = 0; i < nodes.length; i++) { 853 | results.push(window.HTMLWidgets.getInstance(nodes[i])); 854 | } 855 | return results; 856 | }; 857 | 858 | var postRenderHandlers = []; 859 | function invokePostRenderHandlers() { 860 | while (postRenderHandlers.length) { 861 | var handler = postRenderHandlers.shift(); 862 | if (handler) { 863 | handler(); 864 | } 865 | } 866 | } 867 | 868 | // Register the given callback function to be invoked after the 869 | // next time static widgets are rendered. 870 | window.HTMLWidgets.addPostRenderHandler = function(callback) { 871 | postRenderHandlers.push(callback); 872 | }; 873 | 874 | // Takes a new-style instance-bound definition, and returns an 875 | // old-style class-bound definition. This saves us from having 876 | // to rewrite all the logic in this file to accomodate both 877 | // types of definitions. 878 | function createLegacyDefinitionAdapter(defn) { 879 | var result = { 880 | name: defn.name, 881 | type: defn.type, 882 | initialize: function(el, width, height) { 883 | return defn.factory(el, width, height); 884 | }, 885 | renderValue: function(el, x, instance) { 886 | return instance.renderValue(x); 887 | }, 888 | resize: function(el, width, height, instance) { 889 | return instance.resize(width, height); 890 | } 891 | }; 892 | 893 | if (defn.find) 894 | result.find = defn.find; 895 | if (defn.renderError) 896 | result.renderError = defn.renderError; 897 | if (defn.clearError) 898 | result.clearError = defn.clearError; 899 | 900 | return result; 901 | } 902 | })(); 903 | 904 | -------------------------------------------------------------------------------- /libs/leaflet-providers-plugin/leaflet-providers-plugin.js: -------------------------------------------------------------------------------- 1 | LeafletWidget.methods.addProviderTiles = function(provider, layerId, group, options) { 2 | this.layerManager.addLayer(L.tileLayer.provider(provider, options), "tile", layerId, group); 3 | }; 4 | -------------------------------------------------------------------------------- /libs/leaflet-providers/leaflet-providers_1.9.0.js: -------------------------------------------------------------------------------- 1 | (function (root, factory) { 2 | if (typeof define === 'function' && define.amd) { 3 | // AMD. Register as an anonymous module. 4 | define(['leaflet'], factory); 5 | } else if (typeof modules === 'object' && module.exports) { 6 | // define a Common JS module that relies on 'leaflet' 7 | module.exports = factory(require('leaflet')); 8 | } else { 9 | // Assume Leaflet is loaded into global object L already 10 | factory(L); 11 | } 12 | }(this, function (L) { 13 | 'use strict'; 14 | 15 | L.TileLayer.Provider = L.TileLayer.extend({ 16 | initialize: function (arg, options) { 17 | var providers = L.TileLayer.Provider.providers; 18 | 19 | var parts = arg.split('.'); 20 | 21 | var providerName = parts[0]; 22 | var variantName = parts[1]; 23 | 24 | if (!providers[providerName]) { 25 | throw 'No such provider (' + providerName + ')'; 26 | } 27 | 28 | var provider = { 29 | url: providers[providerName].url, 30 | options: providers[providerName].options 31 | }; 32 | 33 | // overwrite values in provider from variant. 34 | if (variantName && 'variants' in providers[providerName]) { 35 | if (!(variantName in providers[providerName].variants)) { 36 | throw 'No such variant of ' + providerName + ' (' + variantName + ')'; 37 | } 38 | var variant = providers[providerName].variants[variantName]; 39 | var variantOptions; 40 | if (typeof variant === 'string') { 41 | variantOptions = { 42 | variant: variant 43 | }; 44 | } else { 45 | variantOptions = variant.options; 46 | } 47 | provider = { 48 | url: variant.url || provider.url, 49 | options: L.Util.extend({}, provider.options, variantOptions) 50 | }; 51 | } 52 | 53 | // replace attribution placeholders with their values from toplevel provider attribution, 54 | // recursively 55 | var attributionReplacer = function (attr) { 56 | if (attr.indexOf('{attribution.') === -1) { 57 | return attr; 58 | } 59 | return attr.replace(/\{attribution.(\w*)\}/g, 60 | function (match, attributionName) { 61 | return attributionReplacer(providers[attributionName].options.attribution); 62 | } 63 | ); 64 | }; 65 | provider.options.attribution = attributionReplacer(provider.options.attribution); 66 | 67 | // Compute final options combining provider options with any user overrides 68 | var layerOpts = L.Util.extend({}, provider.options, options); 69 | L.TileLayer.prototype.initialize.call(this, provider.url, layerOpts); 70 | } 71 | }); 72 | 73 | /** 74 | * Definition of providers. 75 | * see http://leafletjs.com/reference.html#tilelayer for options in the options map. 76 | */ 77 | 78 | L.TileLayer.Provider.providers = { 79 | OpenStreetMap: { 80 | url: '//{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', 81 | options: { 82 | maxZoom: 19, 83 | attribution: 84 | '© OpenStreetMap contributors' 85 | }, 86 | variants: { 87 | Mapnik: {}, 88 | DE: { 89 | url: '//{s}.tile.openstreetmap.de/tiles/osmde/{z}/{x}/{y}.png', 90 | options: { 91 | maxZoom: 18 92 | } 93 | }, 94 | CH: { 95 | url: '//tile.osm.ch/switzerland/{z}/{x}/{y}.png', 96 | options: { 97 | maxZoom: 18, 98 | bounds: [[45, 5], [48, 11]] 99 | } 100 | }, 101 | France: { 102 | url: '//{s}.tile.openstreetmap.fr/osmfr/{z}/{x}/{y}.png', 103 | options: { 104 | maxZoom: 20, 105 | attribution: '© Openstreetmap France | {attribution.OpenStreetMap}' 106 | } 107 | }, 108 | HOT: { 109 | url: '//{s}.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png', 110 | options: { 111 | attribution: 112 | '{attribution.OpenStreetMap}, ' + 113 | 'Tiles style by Humanitarian OpenStreetMap Team ' + 114 | 'hosted by OpenStreetMap France' 115 | } 116 | }, 117 | BZH: { 118 | url: '//tile.openstreetmap.bzh/br/{z}/{x}/{y}.png', 119 | options: { 120 | attribution: '{attribution.OpenStreetMap}, Tiles courtesy of Breton OpenStreetMap Team', 121 | bounds: [[46.2, -5.5], [50, 0.7]] 122 | } 123 | } 124 | } 125 | }, 126 | OpenSeaMap: { 127 | url: '//tiles.openseamap.org/seamark/{z}/{x}/{y}.png', 128 | options: { 129 | attribution: 'Map data: © OpenSeaMap contributors' 130 | } 131 | }, 132 | OpenPtMap: { 133 | url: 'http://openptmap.org/tiles/{z}/{x}/{y}.png', 134 | options: { 135 | maxZoom: 17, 136 | attribution: 'Map data: © OpenPtMap contributors' 137 | } 138 | }, 139 | OpenTopoMap: { 140 | url: 'https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png', 141 | options: { 142 | maxZoom: 17, 143 | attribution: 'Map data: {attribution.OpenStreetMap}, SRTM | Map style: © OpenTopoMap (CC-BY-SA)' 144 | } 145 | }, 146 | OpenRailwayMap: { 147 | url: 'https://{s}.tiles.openrailwaymap.org/standard/{z}/{x}/{y}.png', 148 | options: { 149 | maxZoom: 19, 150 | attribution: 'Map data: {attribution.OpenStreetMap} | Map style: © OpenRailwayMap (CC-BY-SA)' 151 | } 152 | }, 153 | OpenFireMap: { 154 | url: 'http://openfiremap.org/hytiles/{z}/{x}/{y}.png', 155 | options: { 156 | maxZoom: 19, 157 | attribution: 'Map data: {attribution.OpenStreetMap} | Map style: © OpenFireMap (CC-BY-SA)' 158 | } 159 | }, 160 | SafeCast: { 161 | url: '//s3.amazonaws.com/te512.safecast.org/{z}/{x}/{y}.png', 162 | options: { 163 | maxZoom: 16, 164 | attribution: 'Map data: {attribution.OpenStreetMap} | Map style: © SafeCast (CC-BY-SA)' 165 | } 166 | }, 167 | Thunderforest: { 168 | url: 'https://{s}.tile.thunderforest.com/{variant}/{z}/{x}/{y}.png?apikey={apikey}', 169 | options: { 170 | attribution: 171 | '© Thunderforest, {attribution.OpenStreetMap}', 172 | variant: 'cycle', 173 | apikey: '', 174 | maxZoom: 22 175 | }, 176 | variants: { 177 | OpenCycleMap: 'cycle', 178 | Transport: { 179 | options: { 180 | variant: 'transport' 181 | } 182 | }, 183 | TransportDark: { 184 | options: { 185 | variant: 'transport-dark' 186 | } 187 | }, 188 | SpinalMap: { 189 | options: { 190 | variant: 'spinal-map' 191 | } 192 | }, 193 | Landscape: 'landscape', 194 | Outdoors: 'outdoors', 195 | Pioneer: 'pioneer', 196 | MobileAtlas: 'mobile-atlas', 197 | Neighbourhood: 'neighbourhood' 198 | } 199 | }, 200 | OpenMapSurfer: { 201 | url: 'https://maps.heigit.org/openmapsurfer/tiles/{variant}/webmercator/{z}/{x}/{y}.png', 202 | options: { 203 | maxZoom: 19, 204 | variant: 'roads', 205 | attribution: 'Imagery from GIScience Research Group @ University of Heidelberg | Map data ' 206 | }, 207 | variants: { 208 | Roads: { 209 | options: { 210 | variant: 'roads', 211 | attribution: '{attribution.OpenMapSurfer}{attribution.OpenStreetMap}' 212 | } 213 | }, 214 | Hybrid: { 215 | options: { 216 | variant: 'hybrid', 217 | attribution: '{attribution.OpenMapSurfer}{attribution.OpenStreetMap}' 218 | } 219 | }, 220 | AdminBounds: { 221 | options: { 222 | variant: 'adminb', 223 | maxZoom: 18, 224 | attribution: '{attribution.OpenMapSurfer}{attribution.OpenStreetMap}' 225 | } 226 | }, 227 | ContourLines: { 228 | options: { 229 | variant: 'asterc', 230 | maxZoom: 18, 231 | minZoom: 13, 232 | attribution: '{attribution.OpenMapSurfer} ASTER GDEM' 233 | } 234 | }, 235 | Hillshade: { 236 | options: { 237 | variant: 'asterh', 238 | maxZoom: 18, 239 | attribution: '{attribution.OpenMapSurfer} ASTER GDEM, SRTM' 240 | } 241 | }, 242 | ElementsAtRisk: { 243 | options: { 244 | variant: 'elements_at_risk', 245 | attribution: '{attribution.OpenMapSurfer}{attribution.OpenStreetMap}' 246 | } 247 | } 248 | } 249 | }, 250 | Hydda: { 251 | url: '//{s}.tile.openstreetmap.se/hydda/{variant}/{z}/{x}/{y}.png', 252 | options: { 253 | maxZoom: 18, 254 | variant: 'full', 255 | attribution: 'Tiles courtesy of OpenStreetMap Sweden — Map data {attribution.OpenStreetMap}' 256 | }, 257 | variants: { 258 | Full: 'full', 259 | Base: 'base', 260 | RoadsAndLabels: 'roads_and_labels' 261 | } 262 | }, 263 | MapBox: { 264 | url: 'https://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}{r}.png?access_token={accessToken}', 265 | options: { 266 | attribution: 267 | '© Mapbox ' + 268 | '{attribution.OpenStreetMap} ' + 269 | 'Improve this map', 270 | subdomains: 'abcd', 271 | id: 'mapbox.streets', 272 | accessToken: '', 273 | } 274 | }, 275 | Stamen: { 276 | url: '//stamen-tiles-{s}.a.ssl.fastly.net/{variant}/{z}/{x}/{y}{r}.{ext}', 277 | options: { 278 | attribution: 279 | 'Map tiles by Stamen Design, ' + 280 | 'CC BY 3.0 — ' + 281 | 'Map data {attribution.OpenStreetMap}', 282 | subdomains: 'abcd', 283 | minZoom: 0, 284 | maxZoom: 20, 285 | variant: 'toner', 286 | ext: 'png' 287 | }, 288 | variants: { 289 | Toner: 'toner', 290 | TonerBackground: 'toner-background', 291 | TonerHybrid: 'toner-hybrid', 292 | TonerLines: 'toner-lines', 293 | TonerLabels: 'toner-labels', 294 | TonerLite: 'toner-lite', 295 | Watercolor: { 296 | url: '//stamen-tiles-{s}.a.ssl.fastly.net/{variant}/{z}/{x}/{y}.{ext}', 297 | options: { 298 | variant: 'watercolor', 299 | ext: 'jpg', 300 | minZoom: 1, 301 | maxZoom: 16 302 | } 303 | }, 304 | Terrain: { 305 | options: { 306 | variant: 'terrain', 307 | minZoom: 0, 308 | maxZoom: 18 309 | } 310 | }, 311 | TerrainBackground: { 312 | options: { 313 | variant: 'terrain-background', 314 | minZoom: 0, 315 | maxZoom: 18 316 | } 317 | }, 318 | TerrainLabels: { 319 | options: { 320 | variant: 'terrain-labels', 321 | minZoom: 0, 322 | maxZoom: 18 323 | } 324 | }, 325 | TopOSMRelief: { 326 | url: '//stamen-tiles-{s}.a.ssl.fastly.net/{variant}/{z}/{x}/{y}.{ext}', 327 | options: { 328 | variant: 'toposm-color-relief', 329 | ext: 'jpg', 330 | bounds: [[22, -132], [51, -56]] 331 | } 332 | }, 333 | TopOSMFeatures: { 334 | options: { 335 | variant: 'toposm-features', 336 | bounds: [[22, -132], [51, -56]], 337 | opacity: 0.9 338 | } 339 | } 340 | } 341 | }, 342 | TomTom: { 343 | url: 'https://{s}.api.tomtom.com/map/1/tile/{variant}/{style}/{z}/{x}/{y}.{ext}?key={apikey}', 344 | options: { 345 | variant: 'basic', 346 | maxZoom: 22, 347 | attribution: 348 | '© 1992 - ' + new Date().getFullYear() + ' TomTom. ', 349 | subdomains: 'abcd', 350 | style: 'main', 351 | ext: 'png', 352 | apikey: '', 353 | }, 354 | variants: { 355 | Basic: 'basic', 356 | Hybrid: 'hybrid', 357 | Labels: 'labels' 358 | } 359 | }, 360 | Esri: { 361 | url: '//server.arcgisonline.com/ArcGIS/rest/services/{variant}/MapServer/tile/{z}/{y}/{x}', 362 | options: { 363 | variant: 'World_Street_Map', 364 | attribution: 'Tiles © Esri' 365 | }, 366 | variants: { 367 | WorldStreetMap: { 368 | options: { 369 | attribution: 370 | '{attribution.Esri} — ' + 371 | 'Source: Esri, DeLorme, NAVTEQ, USGS, Intermap, iPC, NRCAN, Esri Japan, METI, Esri China (Hong Kong), Esri (Thailand), TomTom, 2012' 372 | } 373 | }, 374 | DeLorme: { 375 | options: { 376 | variant: 'Specialty/DeLorme_World_Base_Map', 377 | minZoom: 1, 378 | maxZoom: 11, 379 | attribution: '{attribution.Esri} — Copyright: ©2012 DeLorme' 380 | } 381 | }, 382 | WorldTopoMap: { 383 | options: { 384 | variant: 'World_Topo_Map', 385 | attribution: 386 | '{attribution.Esri} — ' + 387 | 'Esri, DeLorme, NAVTEQ, TomTom, Intermap, iPC, USGS, FAO, NPS, NRCAN, GeoBase, Kadaster NL, Ordnance Survey, Esri Japan, METI, Esri China (Hong Kong), and the GIS User Community' 388 | } 389 | }, 390 | WorldImagery: { 391 | options: { 392 | variant: 'World_Imagery', 393 | attribution: 394 | '{attribution.Esri} — ' + 395 | 'Source: Esri, i-cubed, USDA, USGS, AEX, GeoEye, Getmapping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community' 396 | } 397 | }, 398 | WorldTerrain: { 399 | options: { 400 | variant: 'World_Terrain_Base', 401 | maxZoom: 13, 402 | attribution: 403 | '{attribution.Esri} — ' + 404 | 'Source: USGS, Esri, TANA, DeLorme, and NPS' 405 | } 406 | }, 407 | WorldShadedRelief: { 408 | options: { 409 | variant: 'World_Shaded_Relief', 410 | maxZoom: 13, 411 | attribution: '{attribution.Esri} — Source: Esri' 412 | } 413 | }, 414 | WorldPhysical: { 415 | options: { 416 | variant: 'World_Physical_Map', 417 | maxZoom: 8, 418 | attribution: '{attribution.Esri} — Source: US National Park Service' 419 | } 420 | }, 421 | OceanBasemap: { 422 | options: { 423 | variant: 'Ocean_Basemap', 424 | maxZoom: 13, 425 | attribution: '{attribution.Esri} — Sources: GEBCO, NOAA, CHS, OSU, UNH, CSUMB, National Geographic, DeLorme, NAVTEQ, and Esri' 426 | } 427 | }, 428 | NatGeoWorldMap: { 429 | options: { 430 | variant: 'NatGeo_World_Map', 431 | maxZoom: 16, 432 | attribution: '{attribution.Esri} — National Geographic, Esri, DeLorme, NAVTEQ, UNEP-WCMC, USGS, NASA, ESA, METI, NRCAN, GEBCO, NOAA, iPC' 433 | } 434 | }, 435 | WorldGrayCanvas: { 436 | options: { 437 | variant: 'Canvas/World_Light_Gray_Base', 438 | maxZoom: 16, 439 | attribution: '{attribution.Esri} — Esri, DeLorme, NAVTEQ' 440 | } 441 | } 442 | } 443 | }, 444 | OpenWeatherMap: { 445 | url: 'http://{s}.tile.openweathermap.org/map/{variant}/{z}/{x}/{y}.png?appid={apiKey}', 446 | options: { 447 | maxZoom: 19, 448 | attribution: 'Map data © OpenWeatherMap', 449 | apiKey:'', 450 | opacity: 0.5 451 | }, 452 | variants: { 453 | Clouds: 'clouds', 454 | CloudsClassic: 'clouds_cls', 455 | Precipitation: 'precipitation', 456 | PrecipitationClassic: 'precipitation_cls', 457 | Rain: 'rain', 458 | RainClassic: 'rain_cls', 459 | Pressure: 'pressure', 460 | PressureContour: 'pressure_cntr', 461 | Wind: 'wind', 462 | Temperature: 'temp', 463 | Snow: 'snow' 464 | } 465 | }, 466 | HERE: { 467 | /* 468 | * HERE maps, formerly Nokia maps. 469 | * These basemaps are free, but you need an API key. Please sign up at 470 | * https://developer.here.com/plans 471 | */ 472 | url: 473 | 'https://{s}.{base}.maps.api.here.com/maptile/2.1/' + 474 | '{type}/{mapID}/{variant}/{z}/{x}/{y}/{size}/{format}?' + 475 | 'app_id={app_id}&app_code={app_code}&lg={language}', 476 | options: { 477 | attribution: 478 | 'Map © 1987-' + new Date().getFullYear() + ' HERE', 479 | subdomains: '1234', 480 | mapID: 'newest', 481 | 'app_id': '', 482 | 'app_code': '', 483 | base: 'base', 484 | variant: 'normal.day', 485 | maxZoom: 20, 486 | type: 'maptile', 487 | language: 'eng', 488 | format: 'png8', 489 | size: '256' 490 | }, 491 | variants: { 492 | normalDay: 'normal.day', 493 | normalDayCustom: 'normal.day.custom', 494 | normalDayGrey: 'normal.day.grey', 495 | normalDayMobile: 'normal.day.mobile', 496 | normalDayGreyMobile: 'normal.day.grey.mobile', 497 | normalDayTransit: 'normal.day.transit', 498 | normalDayTransitMobile: 'normal.day.transit.mobile', 499 | normalDayTraffic: { 500 | options: { 501 | variant: 'normal.traffic.day', 502 | base: 'traffic', 503 | type: 'traffictile' 504 | } 505 | }, 506 | normalNight: 'normal.night', 507 | normalNightMobile: 'normal.night.mobile', 508 | normalNightGrey: 'normal.night.grey', 509 | normalNightGreyMobile: 'normal.night.grey.mobile', 510 | normalNightTransit: 'normal.night.transit', 511 | normalNightTransitMobile: 'normal.night.transit.mobile', 512 | reducedDay: 'reduced.day', 513 | reducedNight: 'reduced.night', 514 | basicMap: { 515 | options: { 516 | type: 'basetile' 517 | } 518 | }, 519 | mapLabels: { 520 | options: { 521 | type: 'labeltile', 522 | format: 'png' 523 | } 524 | }, 525 | trafficFlow: { 526 | options: { 527 | base: 'traffic', 528 | type: 'flowtile' 529 | } 530 | }, 531 | carnavDayGrey: 'carnav.day.grey', 532 | hybridDay: { 533 | options: { 534 | base: 'aerial', 535 | variant: 'hybrid.day' 536 | } 537 | }, 538 | hybridDayMobile: { 539 | options: { 540 | base: 'aerial', 541 | variant: 'hybrid.day.mobile' 542 | } 543 | }, 544 | hybridDayTransit: { 545 | options: { 546 | base: 'aerial', 547 | variant: 'hybrid.day.transit' 548 | } 549 | }, 550 | hybridDayGrey: { 551 | options: { 552 | base: 'aerial', 553 | variant: 'hybrid.grey.day' 554 | } 555 | }, 556 | hybridDayTraffic: { 557 | options: { 558 | variant: 'hybrid.traffic.day', 559 | base: 'traffic', 560 | type: 'traffictile' 561 | } 562 | }, 563 | pedestrianDay: 'pedestrian.day', 564 | pedestrianNight: 'pedestrian.night', 565 | satelliteDay: { 566 | options: { 567 | base: 'aerial', 568 | variant: 'satellite.day' 569 | } 570 | }, 571 | terrainDay: { 572 | options: { 573 | base: 'aerial', 574 | variant: 'terrain.day' 575 | } 576 | }, 577 | terrainDayMobile: { 578 | options: { 579 | base: 'aerial', 580 | variant: 'terrain.day.mobile' 581 | } 582 | } 583 | } 584 | }, 585 | FreeMapSK: { 586 | url: 'http://t{s}.freemap.sk/T/{z}/{x}/{y}.jpeg', 587 | options: { 588 | minZoom: 8, 589 | maxZoom: 16, 590 | subdomains: '1234', 591 | bounds: [[47.204642, 15.996093], [49.830896, 22.576904]], 592 | attribution: 593 | '{attribution.OpenStreetMap}, vizualization CC-By-SA 2.0 Freemap.sk' 594 | } 595 | }, 596 | MtbMap: { 597 | url: 'http://tile.mtbmap.cz/mtbmap_tiles/{z}/{x}/{y}.png', 598 | options: { 599 | attribution: 600 | '{attribution.OpenStreetMap} & USGS' 601 | } 602 | }, 603 | CartoDB: { 604 | url: 'https://{s}.basemaps.cartocdn.com/{variant}/{z}/{x}/{y}{r}.png', 605 | options: { 606 | attribution: '{attribution.OpenStreetMap} © CARTO', 607 | subdomains: 'abcd', 608 | maxZoom: 19, 609 | variant: 'light_all' 610 | }, 611 | variants: { 612 | Positron: 'light_all', 613 | PositronNoLabels: 'light_nolabels', 614 | PositronOnlyLabels: 'light_only_labels', 615 | DarkMatter: 'dark_all', 616 | DarkMatterNoLabels: 'dark_nolabels', 617 | DarkMatterOnlyLabels: 'dark_only_labels', 618 | Voyager: 'rastertiles/voyager', 619 | VoyagerNoLabels: 'rastertiles/voyager_nolabels', 620 | VoyagerOnlyLabels: 'rastertiles/voyager_only_labels', 621 | VoyagerLabelsUnder: 'rastertiles/voyager_labels_under' 622 | } 623 | }, 624 | HikeBike: { 625 | url: 'https://tiles.wmflabs.org/{variant}/{z}/{x}/{y}.png', 626 | options: { 627 | maxZoom: 19, 628 | attribution: '{attribution.OpenStreetMap}', 629 | variant: 'hikebike' 630 | }, 631 | variants: { 632 | HikeBike: {}, 633 | HillShading: { 634 | options: { 635 | maxZoom: 15, 636 | variant: 'hillshading' 637 | } 638 | } 639 | } 640 | }, 641 | BasemapAT: { 642 | url: '//maps{s}.wien.gv.at/basemap/{variant}/normal/google3857/{z}/{y}/{x}.{format}', 643 | options: { 644 | maxZoom: 19, 645 | attribution: 'Datenquelle: basemap.at', 646 | subdomains: ['', '1', '2', '3', '4'], 647 | format: 'png', 648 | bounds: [[46.358770, 8.782379], [49.037872, 17.189532]], 649 | variant: 'geolandbasemap' 650 | }, 651 | variants: { 652 | basemap: { 653 | options: { 654 | maxZoom: 20, // currently only in Vienna 655 | variant: 'geolandbasemap' 656 | } 657 | }, 658 | grau: 'bmapgrau', 659 | overlay: 'bmapoverlay', 660 | highdpi: { 661 | options: { 662 | variant: 'bmaphidpi', 663 | format: 'jpeg' 664 | } 665 | }, 666 | orthofoto: { 667 | options: { 668 | maxZoom: 20, // currently only in Vienna 669 | variant: 'bmaporthofoto30cm', 670 | format: 'jpeg' 671 | } 672 | } 673 | } 674 | }, 675 | nlmaps: { 676 | url: '//geodata.nationaalgeoregister.nl/tiles/service/wmts/{variant}/EPSG:3857/{z}/{x}/{y}.png', 677 | options: { 678 | minZoom: 6, 679 | maxZoom: 19, 680 | bounds: [[50.5, 3.25], [54, 7.6]], 681 | attribution: 'Kaartgegevens © Kadaster' 682 | }, 683 | variants: { 684 | 'standaard': 'brtachtergrondkaart', 685 | 'pastel': 'brtachtergrondkaartpastel', 686 | 'grijs': 'brtachtergrondkaartgrijs', 687 | 'luchtfoto': { 688 | 'url': '//geodata.nationaalgeoregister.nl/luchtfoto/rgb/wmts/1.0.0/2016_ortho25/EPSG:3857/{z}/{x}/{y}.png', 689 | } 690 | } 691 | }, 692 | NASAGIBS: { 693 | url: 'https://map1.vis.earthdata.nasa.gov/wmts-webmerc/{variant}/default/{time}/{tilematrixset}{maxZoom}/{z}/{y}/{x}.{format}', 694 | options: { 695 | attribution: 696 | 'Imagery provided by services from the Global Imagery Browse Services (GIBS), operated by the NASA/GSFC/Earth Science Data and Information System ' + 697 | '(ESDIS) with funding provided by NASA/HQ.', 698 | bounds: [[-85.0511287776, -179.999999975], [85.0511287776, 179.999999975]], 699 | minZoom: 1, 700 | maxZoom: 9, 701 | format: 'jpg', 702 | time: '', 703 | tilematrixset: 'GoogleMapsCompatible_Level' 704 | }, 705 | variants: { 706 | ModisTerraTrueColorCR: 'MODIS_Terra_CorrectedReflectance_TrueColor', 707 | ModisTerraBands367CR: 'MODIS_Terra_CorrectedReflectance_Bands367', 708 | ViirsEarthAtNight2012: { 709 | options: { 710 | variant: 'VIIRS_CityLights_2012', 711 | maxZoom: 8 712 | } 713 | }, 714 | ModisTerraLSTDay: { 715 | options: { 716 | variant: 'MODIS_Terra_Land_Surface_Temp_Day', 717 | format: 'png', 718 | maxZoom: 7, 719 | opacity: 0.75 720 | } 721 | }, 722 | ModisTerraSnowCover: { 723 | options: { 724 | variant: 'MODIS_Terra_Snow_Cover', 725 | format: 'png', 726 | maxZoom: 8, 727 | opacity: 0.75 728 | } 729 | }, 730 | ModisTerraAOD: { 731 | options: { 732 | variant: 'MODIS_Terra_Aerosol', 733 | format: 'png', 734 | maxZoom: 6, 735 | opacity: 0.75 736 | } 737 | }, 738 | ModisTerraChlorophyll: { 739 | options: { 740 | variant: 'MODIS_Terra_Chlorophyll_A', 741 | format: 'png', 742 | maxZoom: 7, 743 | opacity: 0.75 744 | } 745 | } 746 | } 747 | }, 748 | NLS: { 749 | // NLS maps are copyright National library of Scotland. 750 | // http://maps.nls.uk/projects/api/index.html 751 | // Please contact NLS for anything other than non-commercial low volume usage 752 | // 753 | // Map sources: Ordnance Survey 1:1m to 1:63K, 1920s-1940s 754 | // z0-9 - 1:1m 755 | // z10-11 - quarter inch (1:253440) 756 | // z12-18 - one inch (1:63360) 757 | url: '//nls-{s}.tileserver.com/nls/{z}/{x}/{y}.jpg', 758 | options: { 759 | attribution: 'National Library of Scotland Historic Maps', 760 | bounds: [[49.6, -12], [61.7, 3]], 761 | minZoom: 1, 762 | maxZoom: 18, 763 | subdomains: '0123', 764 | } 765 | }, 766 | JusticeMap: { 767 | // Justice Map (http://www.justicemap.org/) 768 | // Visualize race and income data for your community, county and country. 769 | // Includes tools for data journalists, bloggers and community activists. 770 | url: 'http://www.justicemap.org/tile/{size}/{variant}/{z}/{x}/{y}.png', 771 | options: { 772 | attribution: 'Justice Map', 773 | // one of 'county', 'tract', 'block' 774 | size: 'county', 775 | // Bounds for USA, including Alaska and Hawaii 776 | bounds: [[14, -180], [72, -56]] 777 | }, 778 | variants: { 779 | income: 'income', 780 | americanIndian: 'indian', 781 | asian: 'asian', 782 | black: 'black', 783 | hispanic: 'hispanic', 784 | multi: 'multi', 785 | nonWhite: 'nonwhite', 786 | white: 'white', 787 | plurality: 'plural' 788 | } 789 | }, 790 | Wikimedia: { 791 | url: 'https://maps.wikimedia.org/osm-intl/{z}/{x}/{y}{r}.png', 792 | options: { 793 | attribution: 'Wikimedia', 794 | minZoom: 1, 795 | maxZoom: 19 796 | } 797 | }, 798 | GeoportailFrance: { 799 | url: 'https://wxs.ign.fr/{apikey}/geoportail/wmts?REQUEST=GetTile&SERVICE=WMTS&VERSION=1.0.0&STYLE={style}&TILEMATRIXSET=PM&FORMAT={format}&LAYER={variant}&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}', 800 | options: { 801 | attribution: 'Geoportail France', 802 | bounds: [[-75, -180], [81, 180]], 803 | minZoom: 2, 804 | maxZoom: 18, 805 | // Get your own geoportail apikey here : http://professionnels.ign.fr/ign/contrats/ 806 | // NB : 'choisirgeoportail' is a demonstration key that comes with no guarantee 807 | apikey: 'choisirgeoportail', 808 | format: 'image/jpeg', 809 | style : 'normal', 810 | variant: 'GEOGRAPHICALGRIDSYSTEMS.MAPS.SCAN-EXPRESS.STANDARD' 811 | }, 812 | variants: { 813 | parcels: { 814 | options : { 815 | variant: 'CADASTRALPARCELS.PARCELS', 816 | maxZoom: 20, 817 | style : 'bdparcellaire', 818 | format: 'image/png' 819 | } 820 | }, 821 | ignMaps: 'GEOGRAPHICALGRIDSYSTEMS.MAPS', 822 | maps: 'GEOGRAPHICALGRIDSYSTEMS.MAPS.SCAN-EXPRESS.STANDARD', 823 | orthos: { 824 | options: { 825 | maxZoom: 19, 826 | variant: 'ORTHOIMAGERY.ORTHOPHOTOS' 827 | } 828 | } 829 | } 830 | }, 831 | OneMapSG: { 832 | url: '//maps-{s}.onemap.sg/v3/{variant}/{z}/{x}/{y}.png', 833 | options: { 834 | variant: 'Default', 835 | minZoom: 11, 836 | maxZoom: 18, 837 | bounds: [[1.56073, 104.11475], [1.16, 103.502]], 838 | attribution: ' New OneMap | Map data © contributors, Singapore Land Authority' 839 | }, 840 | variants: { 841 | Default: 'Default', 842 | Night: 'Night', 843 | Original: 'Original', 844 | Grey: 'Grey', 845 | LandLot: 'LandLot' 846 | } 847 | } 848 | }; 849 | 850 | L.tileLayer.provider = function (provider, options) { 851 | return new L.TileLayer.Provider(provider, options); 852 | }; 853 | 854 | return L; 855 | })); 856 | -------------------------------------------------------------------------------- /libs/leaflet/images/layers-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RamiKrispin/gis-dataviz-workshop/cbdee699fcaf68becc652d4efc1599b23761eaad/libs/leaflet/images/layers-2x.png -------------------------------------------------------------------------------- /libs/leaflet/images/layers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RamiKrispin/gis-dataviz-workshop/cbdee699fcaf68becc652d4efc1599b23761eaad/libs/leaflet/images/layers.png -------------------------------------------------------------------------------- /libs/leaflet/images/marker-icon-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RamiKrispin/gis-dataviz-workshop/cbdee699fcaf68becc652d4efc1599b23761eaad/libs/leaflet/images/marker-icon-2x.png -------------------------------------------------------------------------------- /libs/leaflet/images/marker-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RamiKrispin/gis-dataviz-workshop/cbdee699fcaf68becc652d4efc1599b23761eaad/libs/leaflet/images/marker-icon.png -------------------------------------------------------------------------------- /libs/leaflet/images/marker-shadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RamiKrispin/gis-dataviz-workshop/cbdee699fcaf68becc652d4efc1599b23761eaad/libs/leaflet/images/marker-shadow.png -------------------------------------------------------------------------------- /libs/leaflet/leaflet.css: -------------------------------------------------------------------------------- 1 | /* required styles */ 2 | 3 | .leaflet-pane, 4 | .leaflet-tile, 5 | .leaflet-marker-icon, 6 | .leaflet-marker-shadow, 7 | .leaflet-tile-container, 8 | .leaflet-pane > svg, 9 | .leaflet-pane > canvas, 10 | .leaflet-zoom-box, 11 | .leaflet-image-layer, 12 | .leaflet-layer { 13 | position: absolute; 14 | left: 0; 15 | top: 0; 16 | } 17 | .leaflet-container { 18 | overflow: hidden; 19 | } 20 | .leaflet-tile, 21 | .leaflet-marker-icon, 22 | .leaflet-marker-shadow { 23 | -webkit-user-select: none; 24 | -moz-user-select: none; 25 | user-select: none; 26 | -webkit-user-drag: none; 27 | } 28 | /* Safari renders non-retina tile on retina better with this, but Chrome is worse */ 29 | .leaflet-safari .leaflet-tile { 30 | image-rendering: -webkit-optimize-contrast; 31 | } 32 | /* hack that prevents hw layers "stretching" when loading new tiles */ 33 | .leaflet-safari .leaflet-tile-container { 34 | width: 1600px; 35 | height: 1600px; 36 | -webkit-transform-origin: 0 0; 37 | } 38 | .leaflet-marker-icon, 39 | .leaflet-marker-shadow { 40 | display: block; 41 | } 42 | /* .leaflet-container svg: reset svg max-width decleration shipped in Joomla! (joomla.org) 3.x */ 43 | /* .leaflet-container img: map is broken in FF if you have max-width: 100% on tiles */ 44 | .leaflet-container .leaflet-overlay-pane svg, 45 | .leaflet-container .leaflet-marker-pane img, 46 | .leaflet-container .leaflet-shadow-pane img, 47 | .leaflet-container .leaflet-tile-pane img, 48 | .leaflet-container img.leaflet-image-layer { 49 | max-width: none !important; 50 | max-height: none !important; 51 | } 52 | 53 | .leaflet-container.leaflet-touch-zoom { 54 | -ms-touch-action: pan-x pan-y; 55 | touch-action: pan-x pan-y; 56 | } 57 | .leaflet-container.leaflet-touch-drag { 58 | -ms-touch-action: pinch-zoom; 59 | /* Fallback for FF which doesn't support pinch-zoom */ 60 | touch-action: none; 61 | touch-action: pinch-zoom; 62 | } 63 | .leaflet-container.leaflet-touch-drag.leaflet-touch-zoom { 64 | -ms-touch-action: none; 65 | touch-action: none; 66 | } 67 | .leaflet-container { 68 | -webkit-tap-highlight-color: transparent; 69 | } 70 | .leaflet-container a { 71 | -webkit-tap-highlight-color: rgba(51, 181, 229, 0.4); 72 | } 73 | .leaflet-tile { 74 | filter: inherit; 75 | visibility: hidden; 76 | } 77 | .leaflet-tile-loaded { 78 | visibility: inherit; 79 | } 80 | .leaflet-zoom-box { 81 | width: 0; 82 | height: 0; 83 | -moz-box-sizing: border-box; 84 | box-sizing: border-box; 85 | z-index: 800; 86 | } 87 | /* workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=888319 */ 88 | .leaflet-overlay-pane svg { 89 | -moz-user-select: none; 90 | } 91 | 92 | .leaflet-pane { z-index: 400; } 93 | 94 | .leaflet-tile-pane { z-index: 200; } 95 | .leaflet-overlay-pane { z-index: 400; } 96 | .leaflet-shadow-pane { z-index: 500; } 97 | .leaflet-marker-pane { z-index: 600; } 98 | .leaflet-tooltip-pane { z-index: 650; } 99 | .leaflet-popup-pane { z-index: 700; } 100 | 101 | .leaflet-map-pane canvas { z-index: 100; } 102 | .leaflet-map-pane svg { z-index: 200; } 103 | 104 | .leaflet-vml-shape { 105 | width: 1px; 106 | height: 1px; 107 | } 108 | .lvml { 109 | behavior: url(#default#VML); 110 | display: inline-block; 111 | position: absolute; 112 | } 113 | 114 | 115 | /* control positioning */ 116 | 117 | .leaflet-control { 118 | position: relative; 119 | z-index: 800; 120 | pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */ 121 | pointer-events: auto; 122 | } 123 | .leaflet-top, 124 | .leaflet-bottom { 125 | position: absolute; 126 | z-index: 1000; 127 | pointer-events: none; 128 | } 129 | .leaflet-top { 130 | top: 0; 131 | } 132 | .leaflet-right { 133 | right: 0; 134 | } 135 | .leaflet-bottom { 136 | bottom: 0; 137 | } 138 | .leaflet-left { 139 | left: 0; 140 | } 141 | .leaflet-control { 142 | float: left; 143 | clear: both; 144 | } 145 | .leaflet-right .leaflet-control { 146 | float: right; 147 | } 148 | .leaflet-top .leaflet-control { 149 | margin-top: 10px; 150 | } 151 | .leaflet-bottom .leaflet-control { 152 | margin-bottom: 10px; 153 | } 154 | .leaflet-left .leaflet-control { 155 | margin-left: 10px; 156 | } 157 | .leaflet-right .leaflet-control { 158 | margin-right: 10px; 159 | } 160 | 161 | 162 | /* zoom and fade animations */ 163 | 164 | .leaflet-fade-anim .leaflet-tile { 165 | will-change: opacity; 166 | } 167 | .leaflet-fade-anim .leaflet-popup { 168 | opacity: 0; 169 | -webkit-transition: opacity 0.2s linear; 170 | -moz-transition: opacity 0.2s linear; 171 | -o-transition: opacity 0.2s linear; 172 | transition: opacity 0.2s linear; 173 | } 174 | .leaflet-fade-anim .leaflet-map-pane .leaflet-popup { 175 | opacity: 1; 176 | } 177 | .leaflet-zoom-animated { 178 | -webkit-transform-origin: 0 0; 179 | -ms-transform-origin: 0 0; 180 | transform-origin: 0 0; 181 | } 182 | .leaflet-zoom-anim .leaflet-zoom-animated { 183 | will-change: transform; 184 | } 185 | .leaflet-zoom-anim .leaflet-zoom-animated { 186 | -webkit-transition: -webkit-transform 0.25s cubic-bezier(0,0,0.25,1); 187 | -moz-transition: -moz-transform 0.25s cubic-bezier(0,0,0.25,1); 188 | -o-transition: -o-transform 0.25s cubic-bezier(0,0,0.25,1); 189 | transition: transform 0.25s cubic-bezier(0,0,0.25,1); 190 | } 191 | .leaflet-zoom-anim .leaflet-tile, 192 | .leaflet-pan-anim .leaflet-tile { 193 | -webkit-transition: none; 194 | -moz-transition: none; 195 | -o-transition: none; 196 | transition: none; 197 | } 198 | 199 | .leaflet-zoom-anim .leaflet-zoom-hide { 200 | visibility: hidden; 201 | } 202 | 203 | 204 | /* cursors */ 205 | 206 | .leaflet-interactive { 207 | cursor: pointer; 208 | } 209 | .leaflet-grab { 210 | cursor: -webkit-grab; 211 | cursor: -moz-grab; 212 | } 213 | .leaflet-crosshair, 214 | .leaflet-crosshair .leaflet-interactive { 215 | cursor: crosshair; 216 | } 217 | .leaflet-popup-pane, 218 | .leaflet-control { 219 | cursor: auto; 220 | } 221 | .leaflet-dragging .leaflet-grab, 222 | .leaflet-dragging .leaflet-grab .leaflet-interactive, 223 | .leaflet-dragging .leaflet-marker-draggable { 224 | cursor: move; 225 | cursor: -webkit-grabbing; 226 | cursor: -moz-grabbing; 227 | } 228 | 229 | /* marker & overlays interactivity */ 230 | .leaflet-marker-icon, 231 | .leaflet-marker-shadow, 232 | .leaflet-image-layer, 233 | .leaflet-pane > svg path, 234 | .leaflet-tile-container { 235 | pointer-events: none; 236 | } 237 | 238 | .leaflet-marker-icon.leaflet-interactive, 239 | .leaflet-image-layer.leaflet-interactive, 240 | .leaflet-pane > svg path.leaflet-interactive { 241 | pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */ 242 | pointer-events: auto; 243 | } 244 | 245 | /* visual tweaks */ 246 | 247 | .leaflet-container { 248 | background: #ddd; 249 | outline: 0; 250 | } 251 | .leaflet-container a { 252 | color: #0078A8; 253 | } 254 | .leaflet-container a.leaflet-active { 255 | outline: 2px solid orange; 256 | } 257 | .leaflet-zoom-box { 258 | border: 2px dotted #38f; 259 | background: rgba(255,255,255,0.5); 260 | } 261 | 262 | 263 | /* general typography */ 264 | .leaflet-container { 265 | font: 12px/1.5 "Helvetica Neue", Arial, Helvetica, sans-serif; 266 | } 267 | 268 | 269 | /* general toolbar styles */ 270 | 271 | .leaflet-bar { 272 | box-shadow: 0 1px 5px rgba(0,0,0,0.65); 273 | border-radius: 4px; 274 | } 275 | .leaflet-bar a, 276 | .leaflet-bar a:hover { 277 | background-color: #fff; 278 | border-bottom: 1px solid #ccc; 279 | width: 26px; 280 | height: 26px; 281 | line-height: 26px; 282 | display: block; 283 | text-align: center; 284 | text-decoration: none; 285 | color: black; 286 | } 287 | .leaflet-bar a, 288 | .leaflet-control-layers-toggle { 289 | background-position: 50% 50%; 290 | background-repeat: no-repeat; 291 | display: block; 292 | } 293 | .leaflet-bar a:hover { 294 | background-color: #f4f4f4; 295 | } 296 | .leaflet-bar a:first-child { 297 | border-top-left-radius: 4px; 298 | border-top-right-radius: 4px; 299 | } 300 | .leaflet-bar a:last-child { 301 | border-bottom-left-radius: 4px; 302 | border-bottom-right-radius: 4px; 303 | border-bottom: none; 304 | } 305 | .leaflet-bar a.leaflet-disabled { 306 | cursor: default; 307 | background-color: #f4f4f4; 308 | color: #bbb; 309 | } 310 | 311 | .leaflet-touch .leaflet-bar a { 312 | width: 30px; 313 | height: 30px; 314 | line-height: 30px; 315 | } 316 | .leaflet-touch .leaflet-bar a:first-child { 317 | border-top-left-radius: 2px; 318 | border-top-right-radius: 2px; 319 | } 320 | .leaflet-touch .leaflet-bar a:last-child { 321 | border-bottom-left-radius: 2px; 322 | border-bottom-right-radius: 2px; 323 | } 324 | 325 | /* zoom control */ 326 | 327 | .leaflet-control-zoom-in, 328 | .leaflet-control-zoom-out { 329 | font: bold 18px 'Lucida Console', Monaco, monospace; 330 | text-indent: 1px; 331 | } 332 | 333 | .leaflet-touch .leaflet-control-zoom-in, .leaflet-touch .leaflet-control-zoom-out { 334 | font-size: 22px; 335 | } 336 | 337 | 338 | /* layers control */ 339 | 340 | .leaflet-control-layers { 341 | box-shadow: 0 1px 5px rgba(0,0,0,0.4); 342 | background: #fff; 343 | border-radius: 5px; 344 | } 345 | .leaflet-control-layers-toggle { 346 | background-image: url(images/layers.png); 347 | width: 36px; 348 | height: 36px; 349 | } 350 | .leaflet-retina .leaflet-control-layers-toggle { 351 | background-image: url(images/layers-2x.png); 352 | background-size: 26px 26px; 353 | } 354 | .leaflet-touch .leaflet-control-layers-toggle { 355 | width: 44px; 356 | height: 44px; 357 | } 358 | .leaflet-control-layers .leaflet-control-layers-list, 359 | .leaflet-control-layers-expanded .leaflet-control-layers-toggle { 360 | display: none; 361 | } 362 | .leaflet-control-layers-expanded .leaflet-control-layers-list { 363 | display: block; 364 | position: relative; 365 | } 366 | .leaflet-control-layers-expanded { 367 | padding: 6px 10px 6px 6px; 368 | color: #333; 369 | background: #fff; 370 | } 371 | .leaflet-control-layers-scrollbar { 372 | overflow-y: scroll; 373 | overflow-x: hidden; 374 | padding-right: 5px; 375 | } 376 | .leaflet-control-layers-selector { 377 | margin-top: 2px; 378 | position: relative; 379 | top: 1px; 380 | } 381 | .leaflet-control-layers label { 382 | display: block; 383 | } 384 | .leaflet-control-layers-separator { 385 | height: 0; 386 | border-top: 1px solid #ddd; 387 | margin: 5px -10px 5px -6px; 388 | } 389 | 390 | /* Default icon URLs */ 391 | .leaflet-default-icon-path { 392 | background-image: url(images/marker-icon.png); 393 | } 394 | 395 | 396 | /* attribution and scale controls */ 397 | 398 | .leaflet-container .leaflet-control-attribution { 399 | background: #fff; 400 | background: rgba(255, 255, 255, 0.7); 401 | margin: 0; 402 | } 403 | .leaflet-control-attribution, 404 | .leaflet-control-scale-line { 405 | padding: 0 5px; 406 | color: #333; 407 | } 408 | .leaflet-control-attribution a { 409 | text-decoration: none; 410 | } 411 | .leaflet-control-attribution a:hover { 412 | text-decoration: underline; 413 | } 414 | .leaflet-container .leaflet-control-attribution, 415 | .leaflet-container .leaflet-control-scale { 416 | font-size: 11px; 417 | } 418 | .leaflet-left .leaflet-control-scale { 419 | margin-left: 5px; 420 | } 421 | .leaflet-bottom .leaflet-control-scale { 422 | margin-bottom: 5px; 423 | } 424 | .leaflet-control-scale-line { 425 | border: 2px solid #777; 426 | border-top: none; 427 | line-height: 1.1; 428 | padding: 2px 5px 1px; 429 | font-size: 11px; 430 | white-space: nowrap; 431 | overflow: hidden; 432 | -moz-box-sizing: border-box; 433 | box-sizing: border-box; 434 | 435 | background: #fff; 436 | background: rgba(255, 255, 255, 0.5); 437 | } 438 | .leaflet-control-scale-line:not(:first-child) { 439 | border-top: 2px solid #777; 440 | border-bottom: none; 441 | margin-top: -2px; 442 | } 443 | .leaflet-control-scale-line:not(:first-child):not(:last-child) { 444 | border-bottom: 2px solid #777; 445 | } 446 | 447 | .leaflet-touch .leaflet-control-attribution, 448 | .leaflet-touch .leaflet-control-layers, 449 | .leaflet-touch .leaflet-bar { 450 | box-shadow: none; 451 | } 452 | .leaflet-touch .leaflet-control-layers, 453 | .leaflet-touch .leaflet-bar { 454 | border: 2px solid rgba(0,0,0,0.2); 455 | background-clip: padding-box; 456 | } 457 | 458 | 459 | /* popup */ 460 | 461 | .leaflet-popup { 462 | position: absolute; 463 | text-align: center; 464 | margin-bottom: 20px; 465 | } 466 | .leaflet-popup-content-wrapper { 467 | padding: 1px; 468 | text-align: left; 469 | border-radius: 12px; 470 | } 471 | .leaflet-popup-content { 472 | margin: 13px 19px; 473 | line-height: 1.4; 474 | } 475 | .leaflet-popup-content p { 476 | margin: 18px 0; 477 | } 478 | .leaflet-popup-tip-container { 479 | width: 40px; 480 | height: 20px; 481 | position: absolute; 482 | left: 50%; 483 | margin-left: -20px; 484 | overflow: hidden; 485 | pointer-events: none; 486 | } 487 | .leaflet-popup-tip { 488 | width: 17px; 489 | height: 17px; 490 | padding: 1px; 491 | 492 | margin: -10px auto 0; 493 | 494 | -webkit-transform: rotate(45deg); 495 | -moz-transform: rotate(45deg); 496 | -ms-transform: rotate(45deg); 497 | -o-transform: rotate(45deg); 498 | transform: rotate(45deg); 499 | } 500 | .leaflet-popup-content-wrapper, 501 | .leaflet-popup-tip { 502 | background: white; 503 | color: #333; 504 | box-shadow: 0 3px 14px rgba(0,0,0,0.4); 505 | } 506 | .leaflet-container a.leaflet-popup-close-button { 507 | position: absolute; 508 | top: 0; 509 | right: 0; 510 | padding: 4px 4px 0 0; 511 | border: none; 512 | text-align: center; 513 | width: 18px; 514 | height: 14px; 515 | font: 16px/14px Tahoma, Verdana, sans-serif; 516 | color: #c3c3c3; 517 | text-decoration: none; 518 | font-weight: bold; 519 | background: transparent; 520 | } 521 | .leaflet-container a.leaflet-popup-close-button:hover { 522 | color: #999; 523 | } 524 | .leaflet-popup-scrolled { 525 | overflow: auto; 526 | border-bottom: 1px solid #ddd; 527 | border-top: 1px solid #ddd; 528 | } 529 | 530 | .leaflet-oldie .leaflet-popup-content-wrapper { 531 | zoom: 1; 532 | } 533 | .leaflet-oldie .leaflet-popup-tip { 534 | width: 24px; 535 | margin: 0 auto; 536 | 537 | -ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)"; 538 | filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678); 539 | } 540 | .leaflet-oldie .leaflet-popup-tip-container { 541 | margin-top: -1px; 542 | } 543 | 544 | .leaflet-oldie .leaflet-control-zoom, 545 | .leaflet-oldie .leaflet-control-layers, 546 | .leaflet-oldie .leaflet-popup-content-wrapper, 547 | .leaflet-oldie .leaflet-popup-tip { 548 | border: 1px solid #999; 549 | } 550 | 551 | 552 | /* div icon */ 553 | 554 | .leaflet-div-icon { 555 | background: #fff; 556 | border: 1px solid #666; 557 | } 558 | 559 | 560 | /* Tooltip */ 561 | /* Base styles for the element that has a tooltip */ 562 | .leaflet-tooltip { 563 | position: absolute; 564 | padding: 6px; 565 | background-color: #fff; 566 | border: 1px solid #fff; 567 | border-radius: 3px; 568 | color: #222; 569 | white-space: nowrap; 570 | -webkit-user-select: none; 571 | -moz-user-select: none; 572 | -ms-user-select: none; 573 | user-select: none; 574 | pointer-events: none; 575 | box-shadow: 0 1px 3px rgba(0,0,0,0.4); 576 | } 577 | .leaflet-tooltip.leaflet-clickable { 578 | cursor: pointer; 579 | pointer-events: auto; 580 | } 581 | .leaflet-tooltip-top:before, 582 | .leaflet-tooltip-bottom:before, 583 | .leaflet-tooltip-left:before, 584 | .leaflet-tooltip-right:before { 585 | position: absolute; 586 | pointer-events: none; 587 | border: 6px solid transparent; 588 | background: transparent; 589 | content: ""; 590 | } 591 | 592 | /* Directions */ 593 | 594 | .leaflet-tooltip-bottom { 595 | margin-top: 6px; 596 | } 597 | .leaflet-tooltip-top { 598 | margin-top: -6px; 599 | } 600 | .leaflet-tooltip-bottom:before, 601 | .leaflet-tooltip-top:before { 602 | left: 50%; 603 | margin-left: -6px; 604 | } 605 | .leaflet-tooltip-top:before { 606 | bottom: 0; 607 | margin-bottom: -12px; 608 | border-top-color: #fff; 609 | } 610 | .leaflet-tooltip-bottom:before { 611 | top: 0; 612 | margin-top: -12px; 613 | margin-left: -6px; 614 | border-bottom-color: #fff; 615 | } 616 | .leaflet-tooltip-left { 617 | margin-left: -6px; 618 | } 619 | .leaflet-tooltip-right { 620 | margin-left: 6px; 621 | } 622 | .leaflet-tooltip-left:before, 623 | .leaflet-tooltip-right:before { 624 | top: 50%; 625 | margin-top: -6px; 626 | } 627 | .leaflet-tooltip-left:before { 628 | right: 0; 629 | margin-right: -12px; 630 | border-left-color: #fff; 631 | } 632 | .leaflet-tooltip-right:before { 633 | left: 0; 634 | margin-left: -12px; 635 | border-right-color: #fff; 636 | } 637 | -------------------------------------------------------------------------------- /libs/leafletfix/leafletfix.css: -------------------------------------------------------------------------------- 1 | /* Work around CSS properties introduced on img by bootstrap */ 2 | img.leaflet-tile { 3 | padding: 0; 4 | margin: 0; 5 | border-radius: 0; 6 | border: none; 7 | } 8 | .info { 9 | padding: 6px 8px; 10 | font: 14px/16px Arial, Helvetica, sans-serif; 11 | background: white; 12 | background: rgba(255,255,255,0.8); 13 | box-shadow: 0 0 15px rgba(0,0,0,0.2); 14 | border-radius: 5px; 15 | } 16 | .legend { 17 | line-height: 18px; 18 | color: #555; 19 | } 20 | .legend svg text { 21 | fill: #555; 22 | } 23 | .legend svg line { 24 | stroke: #555; 25 | } 26 | .legend i { 27 | width: 18px; 28 | height: 18px; 29 | margin-right: 4px; 30 | opacity: 0.7; 31 | display: inline-block; 32 | vertical-align: top; 33 | /*For IE 7*/ 34 | zoom: 1; 35 | *display: inline; 36 | } 37 | -------------------------------------------------------------------------------- /libs/mapviewCSS/mapview-popup.css: -------------------------------------------------------------------------------- 1 | /* table class css */ 2 | table.mapview-popup { 3 | overflow: scroll; 4 | width: auto; 5 | height: auto; 6 | border-collapse: collapse; 7 | } 8 | 9 | /* 10 | table.mapview-popup tr:first-child td { 11 | background: #A8E6A8; 12 | } 13 | */ 14 | 15 | table.mapview-popup tr:nth-child(even) { 16 | background: #D1E0FF; 17 | } 18 | 19 | table.mapview-popup tr:nth-child(odd) { 20 | background: #ebf1ff; 21 | } 22 | 23 | table.mapview-popup, th, td { 24 | border-bottom: 1px solid #ffffff; 25 | } 26 | 27 | /* 28 | table.tab tr:hover { 29 | background: #00ffff; 30 | } 31 | */ 32 | 33 | 34 | /* general leaflet popup css '*/ 35 | .leaflet-popup-content { 36 | margin: 1px 1px 1px 1px; 37 | line-height: 1.5; 38 | overflow-y: auto; 39 | overflow-x: scoll; 40 | } 41 | 42 | .leaflet-container a.leaflet-popup-close-button { 43 | position: absolute; 44 | top: 0; 45 | right: -20px; 46 | padding: 3px 0 0 0; 47 | text-align: center; 48 | width: 18px; 49 | height: 14px; 50 | font: 16px/14px Tahoma, Verdana, sans-serif; 51 | font-weight: bold; 52 | color: #c3c3c3; 53 | text-decoration: none; 54 | background: transparent; 55 | } 56 | 57 | .leaflet-container a.leaflet-popup-close-button:hover { 58 | color: #999; 59 | } 60 | 61 | .leaflet-popup-content-wrapper, .leaflet-popup-tip { 62 | padding: 1px; 63 | -webkit-border-radius: 0; 64 | border-radius: 0; 65 | background: #ffffff; /*#4c4c4c;*/ 66 | box-shadow: 0 3px 14px rgba(0,0,0,0.4); 67 | } 68 | 69 | 70 | div::-webkit-scrollbar { 71 | width: 5px; 72 | height: 5px; 73 | } 74 | div::-webkit-scrollbar-button { 75 | width: 0; 76 | height: 0; 77 | } 78 | div::-webkit-scrollbar-thumb { 79 | background: #666666; 80 | border: 0 none #ffffff; 81 | border-radius: 0; 82 | } 83 | div::-webkit-scrollbar-thumb:hover { 84 | background: #333333; 85 | } 86 | div::-webkit-scrollbar-thumb:active { 87 | background: #333333; 88 | } 89 | div::-webkit-scrollbar-track { 90 | background: #e1e1e1; 91 | border: 0 none #ffffff; 92 | border-radius: 50px; 93 | } 94 | div::-webkit-scrollbar-track:hover { 95 | background: #e1e1e1; 96 | } 97 | div::-webkit-scrollbar-track:active { 98 | background: #e1e1e1; 99 | } 100 | div::-webkit-scrollbar-corner { 101 | background: transparent; 102 | } 103 | -------------------------------------------------------------------------------- /libs/mapviewCSS/mapview.css: -------------------------------------------------------------------------------- 1 | .leaflet-control br {clear: both;} 2 | -------------------------------------------------------------------------------- /libs/remark-css-0.0.1/default-fonts.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 | body { font-family: 'Droid Serif', 'Palatino Linotype', 'Book Antiqua', Palatino, 'Microsoft YaHei', 'Songti SC', serif; } 6 | h1, h2, h3 { 7 | font-family: 'Yanone Kaffeesatz'; 8 | font-weight: normal; 9 | } 10 | .remark-code, .remark-inline-code { font-family: 'Source Code Pro', 'Lucida Console', Monaco, monospace; } 11 | -------------------------------------------------------------------------------- /libs/remark-css-0.0.1/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 | .remark-slide table { 55 | margin: auto; 56 | border-top: 1px solid #666; 57 | border-bottom: 1px solid #666; 58 | } 59 | .remark-slide table thead th { border-bottom: 1px solid #ddd; } 60 | th, td { padding: 5px; } 61 | .remark-slide thead, .remark-slide tfoot, .remark-slide 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 | -------------------------------------------------------------------------------- /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 | .remark-slide table { 55 | margin: auto; 56 | border-top: 1px solid #666; 57 | border-bottom: 1px solid #666; 58 | } 59 | .remark-slide table thead th { border-bottom: 1px solid #ddd; } 60 | th, td { padding: 5px; } 61 | .remark-slide thead, .remark-slide tfoot, .remark-slide 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 | -------------------------------------------------------------------------------- /libs/rstudio_leaflet/images/1px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RamiKrispin/gis-dataviz-workshop/cbdee699fcaf68becc652d4efc1599b23761eaad/libs/rstudio_leaflet/images/1px.png -------------------------------------------------------------------------------- /libs/rstudio_leaflet/rstudio_leaflet.css: -------------------------------------------------------------------------------- 1 | .leaflet-tooltip.leaflet-tooltip-text-only, 2 | .leaflet-tooltip.leaflet-tooltip-text-only:before, 3 | .leaflet-tooltip.leaflet-tooltip-text-only:after { 4 | background: none; 5 | border: none; 6 | box-shadow: none; 7 | } 8 | 9 | .leaflet-tooltip.leaflet-tooltip-text-only.leaflet-tooltip-left { 10 | margin-left: 5px; 11 | } 12 | 13 | .leaflet-tooltip.leaflet-tooltip-text-only.leaflet-tooltip-right { 14 | margin-left: -5px; 15 | } 16 | 17 | .leaflet-tooltip:after { 18 | border-right: 6px solid transparent; 19 | /* right: -16px; */ 20 | } 21 | 22 | .leaflet-popup-pane .leaflet-popup-tip-container { 23 | /* when the tooltip container is clicked, it is closed */ 24 | pointer-events: all; 25 | /* tooltips should display the "hand" icon, just like .leaflet-interactive*/ 26 | cursor: pointer; 27 | } 28 | 29 | /* have the widget be displayed in the right 'layer' */ 30 | .leaflet-map-pane { 31 | z-index: auto; 32 | } 33 | -------------------------------------------------------------------------------- /mycss.css: -------------------------------------------------------------------------------- 1 | @import url(https://fonts.googleapis.com/css?family=Lato:400,700,400italic); 2 | @import url(https://fonts.googleapis.com/css?family=Roboto+Condensed); 3 | @import url(https://fonts.googleapis.com/css?family=Roboto+Mono); 4 | 5 | body { 6 | font-family: 'Lato', Arial, sans-serif; 7 | font-size: 26px; 8 | } 9 | h1, h2, h3, h4 { 10 | font-family: 'Roboto Condensed', 'Arial Narrow OS', Arial, sans-serif; 11 | font-weight: normal; 12 | } 13 | .remark-slide-content { font-size: 1em; } 14 | .remark-code, .remark-inline-code { 15 | font-family: 'Roboto Mono', 'Lucida Console', Monaco, Consolas, monospace; 16 | font-size: 1em; 17 | color: #650C92; 18 | } 19 | 20 | /* Make inline code slightly smaller; prevents vertical jumps 21 | from slides with to slides without inline code. */ 22 | .remark-inline-code { font-size: 0.94em; line-height: 1.0; } 23 | 24 | .title-font { 25 | font-family: 'Roboto Condensed', 'Arial Narrow OS', Arial, sans-serif; 26 | font-weight: normal; 27 | font-size: 1.5em; 28 | } 29 | .small-font { font-size: 0.86em; } 30 | .tiny-font { font-size: 0.66em; } 31 | .xtiny-font { font-size: 0.60em; } 32 | 33 | .white { color: white; } 34 | 35 | .highlight { font-weight: bold; color: #91322F; } 36 | 37 | .nogap p {margin-top: 0; margin-bottom: 0} 38 | 39 | a, a > code { 40 | color: #650C92; 41 | } 42 | 43 | td { 44 | padding: 5px 10px; 45 | } 46 | 47 | table { 48 | display: inline-block; 49 | } 50 | 51 | .width-10 { 52 | width: 10% 53 | } 54 | .width-15 { 55 | width: 15% 56 | } 57 | .width-20 { 58 | width: 20% 59 | } 60 | .width-25 { 61 | width: 25% 62 | } 63 | .width-30 { 64 | width: 30% 65 | } 66 | .width-35 { 67 | width: 35% 68 | } 69 | .width-40 { 70 | width: 40% 71 | } 72 | .width-45 { 73 | width: 45% 74 | } 75 | .width-50 { 76 | width: 50% 77 | } 78 | .width-55 { 79 | width: 55% 80 | } 81 | .width-60 { 82 | width: 60% 83 | } 84 | .width-65 { 85 | width: 65% 86 | } 87 | .width-70 { 88 | width: 70% 89 | } 90 | .width-75 { 91 | width: 75% 92 | } 93 | .width-80 { 94 | width: 80% 95 | } 96 | .width-85 { 97 | width: 85% 98 | } 99 | .width-90 { 100 | width: 90% 101 | } 102 | 103 | .move-up-1em { 104 | margin-top: -1em; 105 | } 106 | 107 | .move-up-2em { 108 | margin-top: -2em; 109 | } 110 | 111 | .move-up-3em { 112 | margin-top: -3em; 113 | } 114 | 115 | .move-down-1em { 116 | margin-top: 1em; 117 | } 118 | 119 | .move-down-2em { 120 | margin-top: 2em; 121 | } 122 | 123 | .move-down-3em { 124 | margin-top: 2em; 125 | } 126 | 127 | .absolute-bottom-left { 128 | position: absolute; 129 | bottom: 0; 130 | left: 4em; 131 | } 132 | 133 | .absolute-bottom-right { 134 | position: absolute; 135 | bottom: 0; 136 | right: 4em; 137 | } -------------------------------------------------------------------------------- /renv.lock: -------------------------------------------------------------------------------- 1 | { 2 | "R": { 3 | "Version": "4.1.2", 4 | "Repositories": [ 5 | { 6 | "Name": "CRAN", 7 | "URL": "https://cran.rstudio.com" 8 | } 9 | ] 10 | }, 11 | "Packages": { 12 | "renv": { 13 | "Package": "renv", 14 | "Version": "0.14.0", 15 | "Source": "Repository", 16 | "Repository": "CRAN", 17 | "Hash": "30e5eba91b67f7f4d75d31de14bbfbdc" 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /renv/.gitignore: -------------------------------------------------------------------------------- 1 | library/ 2 | local/ 3 | lock/ 4 | python/ 5 | staging/ 6 | -------------------------------------------------------------------------------- /renv/activate.R: -------------------------------------------------------------------------------- 1 | 2 | local({ 3 | 4 | # the requested version of renv 5 | version <- "0.14.0" 6 | 7 | # the project directory 8 | project <- getwd() 9 | 10 | # allow environment variable to control activation 11 | activate <- Sys.getenv("RENV_ACTIVATE_PROJECT") 12 | if (!nzchar(activate)) { 13 | 14 | # don't auto-activate when R CMD INSTALL is running 15 | if (nzchar(Sys.getenv("R_INSTALL_PKG"))) 16 | return(FALSE) 17 | 18 | } 19 | 20 | # bail if activation was explicitly disabled 21 | if (tolower(activate) %in% c("false", "f", "0")) 22 | return(FALSE) 23 | 24 | # avoid recursion 25 | if (nzchar(Sys.getenv("RENV_R_INITIALIZING"))) 26 | return(invisible(TRUE)) 27 | 28 | # signal that we're loading renv during R startup 29 | Sys.setenv("RENV_R_INITIALIZING" = "true") 30 | on.exit(Sys.unsetenv("RENV_R_INITIALIZING"), add = TRUE) 31 | 32 | # signal that we've consented to use renv 33 | options(renv.consent = TRUE) 34 | 35 | # load the 'utils' package eagerly -- this ensures that renv shims, which 36 | # mask 'utils' packages, will come first on the search path 37 | library(utils, lib.loc = .Library) 38 | 39 | # check to see if renv has already been loaded 40 | if ("renv" %in% loadedNamespaces()) { 41 | 42 | # if renv has already been loaded, and it's the requested version of renv, 43 | # nothing to do 44 | spec <- .getNamespaceInfo(.getNamespace("renv"), "spec") 45 | if (identical(spec[["version"]], version)) 46 | return(invisible(TRUE)) 47 | 48 | # otherwise, unload and attempt to load the correct version of renv 49 | unloadNamespace("renv") 50 | 51 | } 52 | 53 | # load bootstrap tools 54 | bootstrap <- function(version, library) { 55 | 56 | # attempt to download renv 57 | tarball <- tryCatch(renv_bootstrap_download(version), error = identity) 58 | if (inherits(tarball, "error")) 59 | stop("failed to download renv ", version) 60 | 61 | # now attempt to install 62 | status <- tryCatch(renv_bootstrap_install(version, tarball, library), error = identity) 63 | if (inherits(status, "error")) 64 | stop("failed to install renv ", version) 65 | 66 | } 67 | 68 | renv_bootstrap_tests_running <- function() { 69 | getOption("renv.tests.running", default = FALSE) 70 | } 71 | 72 | renv_bootstrap_repos <- function() { 73 | 74 | # check for repos override 75 | repos <- Sys.getenv("RENV_CONFIG_REPOS_OVERRIDE", unset = NA) 76 | if (!is.na(repos)) 77 | return(repos) 78 | 79 | # if we're testing, re-use the test repositories 80 | if (renv_bootstrap_tests_running()) 81 | return(getOption("renv.tests.repos")) 82 | 83 | # retrieve current repos 84 | repos <- getOption("repos") 85 | 86 | # ensure @CRAN@ entries are resolved 87 | repos[repos == "@CRAN@"] <- getOption( 88 | "renv.repos.cran", 89 | "https://cloud.r-project.org" 90 | ) 91 | 92 | # add in renv.bootstrap.repos if set 93 | default <- c(FALLBACK = "https://cloud.r-project.org") 94 | extra <- getOption("renv.bootstrap.repos", default = default) 95 | repos <- c(repos, extra) 96 | 97 | # remove duplicates that might've snuck in 98 | dupes <- duplicated(repos) | duplicated(names(repos)) 99 | repos[!dupes] 100 | 101 | } 102 | 103 | renv_bootstrap_download <- function(version) { 104 | 105 | # if the renv version number has 4 components, assume it must 106 | # be retrieved via github 107 | nv <- numeric_version(version) 108 | components <- unclass(nv)[[1]] 109 | 110 | methods <- if (length(components) == 4L) { 111 | list( 112 | renv_bootstrap_download_github 113 | ) 114 | } else { 115 | list( 116 | renv_bootstrap_download_cran_latest, 117 | renv_bootstrap_download_cran_archive 118 | ) 119 | } 120 | 121 | for (method in methods) { 122 | path <- tryCatch(method(version), error = identity) 123 | if (is.character(path) && file.exists(path)) 124 | return(path) 125 | } 126 | 127 | stop("failed to download renv ", version) 128 | 129 | } 130 | 131 | renv_bootstrap_download_impl <- function(url, destfile) { 132 | 133 | mode <- "wb" 134 | 135 | # https://bugs.r-project.org/bugzilla/show_bug.cgi?id=17715 136 | fixup <- 137 | Sys.info()[["sysname"]] == "Windows" && 138 | substring(url, 1L, 5L) == "file:" 139 | 140 | if (fixup) 141 | mode <- "w+b" 142 | 143 | utils::download.file( 144 | url = url, 145 | destfile = destfile, 146 | mode = mode, 147 | quiet = TRUE 148 | ) 149 | 150 | } 151 | 152 | renv_bootstrap_download_cran_latest <- function(version) { 153 | 154 | spec <- renv_bootstrap_download_cran_latest_find(version) 155 | 156 | message("* Downloading renv ", version, " ... ", appendLF = FALSE) 157 | 158 | type <- spec$type 159 | repos <- spec$repos 160 | 161 | info <- tryCatch( 162 | utils::download.packages( 163 | pkgs = "renv", 164 | destdir = tempdir(), 165 | repos = repos, 166 | type = type, 167 | quiet = TRUE 168 | ), 169 | condition = identity 170 | ) 171 | 172 | if (inherits(info, "condition")) { 173 | message("FAILED") 174 | return(FALSE) 175 | } 176 | 177 | # report success and return 178 | message("OK (downloaded ", type, ")") 179 | info[1, 2] 180 | 181 | } 182 | 183 | renv_bootstrap_download_cran_latest_find <- function(version) { 184 | 185 | # check whether binaries are supported on this system 186 | binary <- 187 | getOption("renv.bootstrap.binary", default = TRUE) && 188 | !identical(.Platform$pkgType, "source") && 189 | !identical(getOption("pkgType"), "source") && 190 | Sys.info()[["sysname"]] %in% c("Darwin", "Windows") 191 | 192 | types <- c(if (binary) "binary", "source") 193 | 194 | # iterate over types + repositories 195 | for (type in types) { 196 | for (repos in renv_bootstrap_repos()) { 197 | 198 | # retrieve package database 199 | db <- tryCatch( 200 | as.data.frame( 201 | utils::available.packages(type = type, repos = repos), 202 | stringsAsFactors = FALSE 203 | ), 204 | error = identity 205 | ) 206 | 207 | if (inherits(db, "error")) 208 | next 209 | 210 | # check for compatible entry 211 | entry <- db[db$Package %in% "renv" & db$Version %in% version, ] 212 | if (nrow(entry) == 0) 213 | next 214 | 215 | # found it; return spec to caller 216 | spec <- list(entry = entry, type = type, repos = repos) 217 | return(spec) 218 | 219 | } 220 | } 221 | 222 | # if we got here, we failed to find renv 223 | fmt <- "renv %s is not available from your declared package repositories" 224 | stop(sprintf(fmt, version)) 225 | 226 | } 227 | 228 | renv_bootstrap_download_cran_archive <- function(version) { 229 | 230 | name <- sprintf("renv_%s.tar.gz", version) 231 | repos <- renv_bootstrap_repos() 232 | urls <- file.path(repos, "src/contrib/Archive/renv", name) 233 | destfile <- file.path(tempdir(), name) 234 | 235 | message("* Downloading renv ", version, " ... ", appendLF = FALSE) 236 | 237 | for (url in urls) { 238 | 239 | status <- tryCatch( 240 | renv_bootstrap_download_impl(url, destfile), 241 | condition = identity 242 | ) 243 | 244 | if (identical(status, 0L)) { 245 | message("OK") 246 | return(destfile) 247 | } 248 | 249 | } 250 | 251 | message("FAILED") 252 | return(FALSE) 253 | 254 | } 255 | 256 | renv_bootstrap_download_github <- function(version) { 257 | 258 | enabled <- Sys.getenv("RENV_BOOTSTRAP_FROM_GITHUB", unset = "TRUE") 259 | if (!identical(enabled, "TRUE")) 260 | return(FALSE) 261 | 262 | # prepare download options 263 | pat <- Sys.getenv("GITHUB_PAT") 264 | if (nzchar(Sys.which("curl")) && nzchar(pat)) { 265 | fmt <- "--location --fail --header \"Authorization: token %s\"" 266 | extra <- sprintf(fmt, pat) 267 | saved <- options("download.file.method", "download.file.extra") 268 | options(download.file.method = "curl", download.file.extra = extra) 269 | on.exit(do.call(base::options, saved), add = TRUE) 270 | } else if (nzchar(Sys.which("wget")) && nzchar(pat)) { 271 | fmt <- "--header=\"Authorization: token %s\"" 272 | extra <- sprintf(fmt, pat) 273 | saved <- options("download.file.method", "download.file.extra") 274 | options(download.file.method = "wget", download.file.extra = extra) 275 | on.exit(do.call(base::options, saved), add = TRUE) 276 | } 277 | 278 | message("* Downloading renv ", version, " from GitHub ... ", appendLF = FALSE) 279 | 280 | url <- file.path("https://api.github.com/repos/rstudio/renv/tarball", version) 281 | name <- sprintf("renv_%s.tar.gz", version) 282 | destfile <- file.path(tempdir(), name) 283 | 284 | status <- tryCatch( 285 | renv_bootstrap_download_impl(url, destfile), 286 | condition = identity 287 | ) 288 | 289 | if (!identical(status, 0L)) { 290 | message("FAILED") 291 | return(FALSE) 292 | } 293 | 294 | message("OK") 295 | return(destfile) 296 | 297 | } 298 | 299 | renv_bootstrap_install <- function(version, tarball, library) { 300 | 301 | # attempt to install it into project library 302 | message("* Installing renv ", version, " ... ", appendLF = FALSE) 303 | dir.create(library, showWarnings = FALSE, recursive = TRUE) 304 | 305 | # invoke using system2 so we can capture and report output 306 | bin <- R.home("bin") 307 | exe <- if (Sys.info()[["sysname"]] == "Windows") "R.exe" else "R" 308 | r <- file.path(bin, exe) 309 | args <- c("--vanilla", "CMD", "INSTALL", "-l", shQuote(library), shQuote(tarball)) 310 | output <- system2(r, args, stdout = TRUE, stderr = TRUE) 311 | message("Done!") 312 | 313 | # check for successful install 314 | status <- attr(output, "status") 315 | if (is.numeric(status) && !identical(status, 0L)) { 316 | header <- "Error installing renv:" 317 | lines <- paste(rep.int("=", nchar(header)), collapse = "") 318 | text <- c(header, lines, output) 319 | writeLines(text, con = stderr()) 320 | } 321 | 322 | status 323 | 324 | } 325 | 326 | renv_bootstrap_platform_prefix <- function() { 327 | 328 | # construct version prefix 329 | version <- paste(R.version$major, R.version$minor, sep = ".") 330 | prefix <- paste("R", numeric_version(version)[1, 1:2], sep = "-") 331 | 332 | # include SVN revision for development versions of R 333 | # (to avoid sharing platform-specific artefacts with released versions of R) 334 | devel <- 335 | identical(R.version[["status"]], "Under development (unstable)") || 336 | identical(R.version[["nickname"]], "Unsuffered Consequences") 337 | 338 | if (devel) 339 | prefix <- paste(prefix, R.version[["svn rev"]], sep = "-r") 340 | 341 | # build list of path components 342 | components <- c(prefix, R.version$platform) 343 | 344 | # include prefix if provided by user 345 | prefix <- renv_bootstrap_platform_prefix_impl() 346 | if (!is.na(prefix) && nzchar(prefix)) 347 | components <- c(prefix, components) 348 | 349 | # build prefix 350 | paste(components, collapse = "/") 351 | 352 | } 353 | 354 | renv_bootstrap_platform_prefix_impl <- function() { 355 | 356 | # if an explicit prefix has been supplied, use it 357 | prefix <- Sys.getenv("RENV_PATHS_PREFIX", unset = NA) 358 | if (!is.na(prefix)) 359 | return(prefix) 360 | 361 | # if the user has requested an automatic prefix, generate it 362 | auto <- Sys.getenv("RENV_PATHS_PREFIX_AUTO", unset = NA) 363 | if (auto %in% c("TRUE", "True", "true", "1")) 364 | return(renv_bootstrap_platform_prefix_auto()) 365 | 366 | # empty string on failure 367 | "" 368 | 369 | } 370 | 371 | renv_bootstrap_platform_prefix_auto <- function() { 372 | 373 | prefix <- tryCatch(renv_bootstrap_platform_os(), error = identity) 374 | if (inherits(prefix, "error") || prefix %in% "unknown") { 375 | 376 | msg <- paste( 377 | "failed to infer current operating system", 378 | "please file a bug report at https://github.com/rstudio/renv/issues", 379 | sep = "; " 380 | ) 381 | 382 | warning(msg) 383 | 384 | } 385 | 386 | prefix 387 | 388 | } 389 | 390 | renv_bootstrap_platform_os <- function() { 391 | 392 | sysinfo <- Sys.info() 393 | sysname <- sysinfo[["sysname"]] 394 | 395 | # handle Windows + macOS up front 396 | if (sysname == "Windows") 397 | return("windows") 398 | else if (sysname == "Darwin") 399 | return("macos") 400 | 401 | # check for os-release files 402 | for (file in c("/etc/os-release", "/usr/lib/os-release")) 403 | if (file.exists(file)) 404 | return(renv_bootstrap_platform_os_via_os_release(file, sysinfo)) 405 | 406 | # check for redhat-release files 407 | if (file.exists("/etc/redhat-release")) 408 | return(renv_bootstrap_platform_os_via_redhat_release()) 409 | 410 | "unknown" 411 | 412 | } 413 | 414 | renv_bootstrap_platform_os_via_os_release <- function(file, sysinfo) { 415 | 416 | # read /etc/os-release 417 | release <- utils::read.table( 418 | file = file, 419 | sep = "=", 420 | quote = c("\"", "'"), 421 | col.names = c("Key", "Value"), 422 | comment.char = "#", 423 | stringsAsFactors = FALSE 424 | ) 425 | 426 | vars <- as.list(release$Value) 427 | names(vars) <- release$Key 428 | 429 | # get os name 430 | os <- tolower(sysinfo[["sysname"]]) 431 | 432 | # read id 433 | id <- "unknown" 434 | for (field in c("ID", "ID_LIKE")) { 435 | if (field %in% names(vars) && nzchar(vars[[field]])) { 436 | id <- vars[[field]] 437 | break 438 | } 439 | } 440 | 441 | # read version 442 | version <- "unknown" 443 | for (field in c("UBUNTU_CODENAME", "VERSION_CODENAME", "VERSION_ID", "BUILD_ID")) { 444 | if (field %in% names(vars) && nzchar(vars[[field]])) { 445 | version <- vars[[field]] 446 | break 447 | } 448 | } 449 | 450 | # join together 451 | paste(c(os, id, version), collapse = "-") 452 | 453 | } 454 | 455 | renv_bootstrap_platform_os_via_redhat_release <- function() { 456 | 457 | # read /etc/redhat-release 458 | contents <- readLines("/etc/redhat-release", warn = FALSE) 459 | 460 | # infer id 461 | id <- if (grepl("centos", contents, ignore.case = TRUE)) 462 | "centos" 463 | else if (grepl("redhat", contents, ignore.case = TRUE)) 464 | "redhat" 465 | else 466 | "unknown" 467 | 468 | # try to find a version component (very hacky) 469 | version <- "unknown" 470 | 471 | parts <- strsplit(contents, "[[:space:]]")[[1L]] 472 | for (part in parts) { 473 | 474 | nv <- tryCatch(numeric_version(part), error = identity) 475 | if (inherits(nv, "error")) 476 | next 477 | 478 | version <- nv[1, 1] 479 | break 480 | 481 | } 482 | 483 | paste(c("linux", id, version), collapse = "-") 484 | 485 | } 486 | 487 | renv_bootstrap_library_root_name <- function(project) { 488 | 489 | # use project name as-is if requested 490 | asis <- Sys.getenv("RENV_PATHS_LIBRARY_ROOT_ASIS", unset = "FALSE") 491 | if (asis) 492 | return(basename(project)) 493 | 494 | # otherwise, disambiguate based on project's path 495 | id <- substring(renv_bootstrap_hash_text(project), 1L, 8L) 496 | paste(basename(project), id, sep = "-") 497 | 498 | } 499 | 500 | renv_bootstrap_library_root <- function(project) { 501 | 502 | path <- Sys.getenv("RENV_PATHS_LIBRARY", unset = NA) 503 | if (!is.na(path)) 504 | return(path) 505 | 506 | path <- Sys.getenv("RENV_PATHS_LIBRARY_ROOT", unset = NA) 507 | if (!is.na(path)) { 508 | name <- renv_bootstrap_library_root_name(project) 509 | return(file.path(path, name)) 510 | } 511 | 512 | prefix <- renv_bootstrap_profile_prefix() 513 | paste(c(project, prefix, "renv/library"), collapse = "/") 514 | 515 | } 516 | 517 | renv_bootstrap_validate_version <- function(version) { 518 | 519 | loadedversion <- utils::packageDescription("renv", fields = "Version") 520 | if (version == loadedversion) 521 | return(TRUE) 522 | 523 | # assume four-component versions are from GitHub; three-component 524 | # versions are from CRAN 525 | components <- strsplit(loadedversion, "[.-]")[[1]] 526 | remote <- if (length(components) == 4L) 527 | paste("rstudio/renv", loadedversion, sep = "@") 528 | else 529 | paste("renv", loadedversion, sep = "@") 530 | 531 | fmt <- paste( 532 | "renv %1$s was loaded from project library, but this project is configured to use renv %2$s.", 533 | "Use `renv::record(\"%3$s\")` to record renv %1$s in the lockfile.", 534 | "Use `renv::restore(packages = \"renv\")` to install renv %2$s into the project library.", 535 | sep = "\n" 536 | ) 537 | 538 | msg <- sprintf(fmt, loadedversion, version, remote) 539 | warning(msg, call. = FALSE) 540 | 541 | FALSE 542 | 543 | } 544 | 545 | renv_bootstrap_hash_text <- function(text) { 546 | 547 | hashfile <- tempfile("renv-hash-") 548 | on.exit(unlink(hashfile), add = TRUE) 549 | 550 | writeLines(text, con = hashfile) 551 | tools::md5sum(hashfile) 552 | 553 | } 554 | 555 | renv_bootstrap_load <- function(project, libpath, version) { 556 | 557 | # try to load renv from the project library 558 | if (!requireNamespace("renv", lib.loc = libpath, quietly = TRUE)) 559 | return(FALSE) 560 | 561 | # warn if the version of renv loaded does not match 562 | renv_bootstrap_validate_version(version) 563 | 564 | # load the project 565 | renv::load(project) 566 | 567 | TRUE 568 | 569 | } 570 | 571 | renv_bootstrap_profile_load <- function(project) { 572 | 573 | # if RENV_PROFILE is already set, just use that 574 | profile <- Sys.getenv("RENV_PROFILE", unset = NA) 575 | if (!is.na(profile) && nzchar(profile)) 576 | return(profile) 577 | 578 | # check for a profile file (nothing to do if it doesn't exist) 579 | path <- file.path(project, "renv/local/profile") 580 | if (!file.exists(path)) 581 | return(NULL) 582 | 583 | # read the profile, and set it if it exists 584 | contents <- readLines(path, warn = FALSE) 585 | if (length(contents) == 0L) 586 | return(NULL) 587 | 588 | # set RENV_PROFILE 589 | profile <- contents[[1L]] 590 | if (nzchar(profile)) 591 | Sys.setenv(RENV_PROFILE = profile) 592 | 593 | profile 594 | 595 | } 596 | 597 | renv_bootstrap_profile_prefix <- function() { 598 | profile <- renv_bootstrap_profile_get() 599 | if (!is.null(profile)) 600 | return(file.path("renv/profiles", profile)) 601 | } 602 | 603 | renv_bootstrap_profile_get <- function() { 604 | profile <- Sys.getenv("RENV_PROFILE", unset = "") 605 | renv_bootstrap_profile_normalize(profile) 606 | } 607 | 608 | renv_bootstrap_profile_set <- function(profile) { 609 | profile <- renv_bootstrap_profile_normalize(profile) 610 | if (is.null(profile)) 611 | Sys.unsetenv("RENV_PROFILE") 612 | else 613 | Sys.setenv(RENV_PROFILE = profile) 614 | } 615 | 616 | renv_bootstrap_profile_normalize <- function(profile) { 617 | 618 | if (is.null(profile) || profile %in% c("", "default")) 619 | return(NULL) 620 | 621 | profile 622 | 623 | } 624 | 625 | # load the renv profile, if any 626 | renv_bootstrap_profile_load(project) 627 | 628 | # construct path to library root 629 | root <- renv_bootstrap_library_root(project) 630 | 631 | # construct library prefix for platform 632 | prefix <- renv_bootstrap_platform_prefix() 633 | 634 | # construct full libpath 635 | libpath <- file.path(root, prefix) 636 | 637 | # attempt to load 638 | if (renv_bootstrap_load(project, libpath, version)) 639 | return(TRUE) 640 | 641 | # load failed; inform user we're about to bootstrap 642 | prefix <- paste("# Bootstrapping renv", version) 643 | postfix <- paste(rep.int("-", 77L - nchar(prefix)), collapse = "") 644 | header <- paste(prefix, postfix) 645 | message(header) 646 | 647 | # perform bootstrap 648 | bootstrap(version, libpath) 649 | 650 | # exit early if we're just testing bootstrap 651 | if (!is.na(Sys.getenv("RENV_BOOTSTRAP_INSTALL_ONLY", unset = NA))) 652 | return(TRUE) 653 | 654 | # try again to load 655 | if (requireNamespace("renv", lib.loc = libpath, quietly = TRUE)) { 656 | message("* Successfully installed and loaded renv ", version, ".") 657 | return(renv::load()) 658 | } 659 | 660 | # failed to download or load renv; warn the user 661 | msg <- c( 662 | "Failed to find an renv installation: the project will not be loaded.", 663 | "Use `renv::activate()` to re-initialize the project." 664 | ) 665 | 666 | warning(paste(msg, collapse = "\n"), call. = FALSE) 667 | 668 | }) 669 | -------------------------------------------------------------------------------- /renv/settings.dcf: -------------------------------------------------------------------------------- 1 | external.libraries: 2 | ignored.packages: 3 | package.dependency.fields: Imports, Depends, LinkingTo 4 | r.version: 5 | snapshot.type: implicit 6 | use.cache: TRUE 7 | vcs.ignore.library: TRUE 8 | vcs.ignore.local: TRUE 9 | --------------------------------------------------------------------------------