├── codecov.yml
├── LICENSE
├── .gitignore
├── data
├── eco2mix.rda
├── eco2mixBalance.rda
└── d3.schemeCategory10.rda
├── tests
├── testthat.R
└── testthat
│ ├── test-flows.R
│ ├── test-preprocess_args.R
│ ├── test-minicharts.R
│ └── test-prepare_js_args.R
├── vignettes
├── barcharts.png
├── piecharts.png
├── bubblecharts.png
└── introduction.Rmd
├── shiny-demo
└── leaflet-minicharts-demo
│ ├── regions.rda
│ ├── DESCRIPTION
│ ├── global.R
│ ├── ui.R
│ └── server.R
├── inst
├── font-awesome-4.7.0
│ ├── fonts
│ │ └── fontawesome-webfont.ttf
│ └── css
│ │ └── font-awesome.min.css
└── minicharts.css
├── .Rbuildignore
├── javascript
├── index.js
├── package.json
├── sync_with.js
├── Gruntfile.js
├── flow_bindings.js
├── minichart_bindings.js
├── timeslider.js
└── utils.js
├── R
├── zzz.R
├── minicharts_deps.R
├── preprocess_args.R
├── data-doc.R
├── popup_args.R
├── sync_with.R
├── add_flows.R
├── prepare_js_args.R
└── add_minicharts.R
├── NAMESPACE
├── .travis.yml
├── leaflet.minicharts.Rproj
├── man
├── d3.schemeCategory10.Rd
├── eco2mix.Rd
├── popupArgs.Rd
├── syncWith.Rd
├── addFlows.Rd
└── addMinicharts.Rd
├── appveyor.yml
├── DESCRIPTION
├── cran-comments.md
├── README.md
└── NEWS
/codecov.yml:
--------------------------------------------------------------------------------
1 | comment: false
2 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | COPYRIGHT HOLDER: RTE Réseau de transport d’électricité
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .Rproj.user
2 | .Rhistory
3 | .RData
4 | .Ruserdata
5 | inst/doc
6 | javascript/node_modules
7 |
--------------------------------------------------------------------------------
/data/eco2mix.rda:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rte-antares-rpackage/leaflet.minicharts/HEAD/data/eco2mix.rda
--------------------------------------------------------------------------------
/tests/testthat.R:
--------------------------------------------------------------------------------
1 | library(testthat)
2 | library(leaflet.minicharts)
3 |
4 | test_check("leaflet.minicharts")
5 |
--------------------------------------------------------------------------------
/data/eco2mixBalance.rda:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rte-antares-rpackage/leaflet.minicharts/HEAD/data/eco2mixBalance.rda
--------------------------------------------------------------------------------
/vignettes/barcharts.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rte-antares-rpackage/leaflet.minicharts/HEAD/vignettes/barcharts.png
--------------------------------------------------------------------------------
/vignettes/piecharts.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rte-antares-rpackage/leaflet.minicharts/HEAD/vignettes/piecharts.png
--------------------------------------------------------------------------------
/vignettes/bubblecharts.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rte-antares-rpackage/leaflet.minicharts/HEAD/vignettes/bubblecharts.png
--------------------------------------------------------------------------------
/data/d3.schemeCategory10.rda:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rte-antares-rpackage/leaflet.minicharts/HEAD/data/d3.schemeCategory10.rda
--------------------------------------------------------------------------------
/shiny-demo/leaflet-minicharts-demo/regions.rda:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rte-antares-rpackage/leaflet.minicharts/HEAD/shiny-demo/leaflet-minicharts-demo/regions.rda
--------------------------------------------------------------------------------
/inst/font-awesome-4.7.0/fonts/fontawesome-webfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rte-antares-rpackage/leaflet.minicharts/HEAD/inst/font-awesome-4.7.0/fonts/fontawesome-webfont.ttf
--------------------------------------------------------------------------------
/.Rbuildignore:
--------------------------------------------------------------------------------
1 | ^.*\.Rproj$
2 | ^\.Rproj\.user$
3 | ^\.travis\.yml$
4 | ^appveyor\.yml$
5 | shiny-demo
6 | javascript
7 | ^codecov\.yml$
8 | ^cran-comments\.md$
9 | ^CRAN-RELEASE$
10 |
--------------------------------------------------------------------------------
/shiny-demo/leaflet-minicharts-demo/DESCRIPTION:
--------------------------------------------------------------------------------
1 | Title: Demo of leaflet.minicharts
2 | Author: Francois Guillem
3 | AuthorUrl:
4 | License: GPL-2
5 | DisplayMode: Showcase
6 | Tags: getting-started
7 | Type: Shiny
8 |
--------------------------------------------------------------------------------
/javascript/index.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | require("leaflet.flow");
3 | require("leaflet.minichart");
4 | require("./timeslider");
5 | require("./minichart_bindings");
6 | require("./flow_bindings");
7 | require("./sync_with");
8 | })();
9 |
--------------------------------------------------------------------------------
/R/zzz.R:
--------------------------------------------------------------------------------
1 | # Copyright © 2016 RTE Réseau de transport d’électricité
2 | #' @importFrom leaflet expandLimits invokeMethod %>% addLegend JS
3 | NULL
4 |
5 | globalVariables(c('d3.schemeCategory10', "."))
6 |
7 | .onLoad <- function(libname, pkgname) {
8 | utils::data("d3.schemeCategory10", package=pkgname, envir=parent.env(environment()))
9 | }
10 |
--------------------------------------------------------------------------------
/NAMESPACE:
--------------------------------------------------------------------------------
1 | # Generated by roxygen2: do not edit by hand
2 |
3 | export(addFlows)
4 | export(addMinicharts)
5 | export(clearFlows)
6 | export(clearMinicharts)
7 | export(popupArgs)
8 | export(removeFlows)
9 | export(removeMinicharts)
10 | export(syncWith)
11 | export(updateFlows)
12 | export(updateMinicharts)
13 | importFrom(leaflet,"%>%")
14 | importFrom(leaflet,JS)
15 | importFrom(leaflet,addLegend)
16 | importFrom(leaflet,expandLimits)
17 | importFrom(leaflet,invokeMethod)
18 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | # R for travis: see documentation at https://docs.travis-ci.com/user/languages/r
2 |
3 | language: R
4 | sudo: required
5 | cache: packages
6 |
7 | r_github_packages:
8 | - rstudio/leaflet
9 |
10 | before_install:
11 | - sudo add-apt-repository ppa:ubuntugis/ubuntugis-unstable --yes
12 | - sudo apt-get --yes --force-yes update -qq
13 | - sudo apt-get install --yes libudunits2-dev libproj-dev libgeos-dev libgdal-dev
14 |
15 | r_packages:
16 | - rgeos
17 |
18 | after_success:
19 | - Rscript -e 'covr::codecov()'
20 |
--------------------------------------------------------------------------------
/leaflet.minicharts.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 | AutoAppendNewline: Yes
16 | StripTrailingWhitespace: Yes
17 | LineEndingConversion: Posix
18 |
19 | BuildType: Package
20 | PackageUseDevtools: Yes
21 | PackageInstallArgs: --no-multiarch --with-keep.source
22 | PackageCheckArgs: --as-cran
23 | PackageRoxygenize: rd,collate,namespace
24 |
--------------------------------------------------------------------------------
/R/minicharts_deps.R:
--------------------------------------------------------------------------------
1 | minichartDeps <- function() {
2 | minichartDep <- htmltools::htmlDependency(
3 | "minichart",
4 | "0.2.2",
5 | src = system.file(package = "leaflet.minicharts"),
6 | stylesheet = c("minicharts.css"),
7 | script = c("leaflet.minicharts.min.js")
8 | )
9 |
10 | fontAwesomeDep <- htmltools::htmlDependency(
11 | "font-awesome",
12 | "4.7.0",
13 | src = system.file("font-awesome-4.7.0", package = "leaflet.minicharts"),
14 | stylesheet = "css/font-awesome.min.css"
15 | )
16 |
17 | list(minichartDep, fontAwesomeDep)
18 | }
19 |
--------------------------------------------------------------------------------
/man/d3.schemeCategory10.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/data-doc.R
3 | \docType{data}
4 | \name{d3.schemeCategory10}
5 | \alias{d3.schemeCategory10}
6 | \title{d3 color palette}
7 | \format{
8 | An object of class \code{character} of length 10.
9 | }
10 | \usage{
11 | d3.schemeCategory10
12 | }
13 | \description{
14 | A character vector containing ten colors. These colors are used as the default
15 | color palette
16 | }
17 | \references{
18 | \url{https://github.com/d3/d3-scale}
19 | }
20 | \author{
21 | Francois Guillem
22 | }
23 | \keyword{datasets}
24 |
--------------------------------------------------------------------------------
/shiny-demo/leaflet-minicharts-demo/global.R:
--------------------------------------------------------------------------------
1 | library(dplyr)
2 | library(shiny)
3 | library(leaflet)
4 | library(leaflet.minicharts)
5 |
6 | data("eco2mix")
7 | load("regions.rda")
8 |
9 | # Remove data for the whole country
10 | prodRegions <- eco2mix %>% filter(area != "France")
11 |
12 | # Production columns
13 | prodCols <- names(prodRegions)[6:13]
14 |
15 | # Create base map
16 | tilesURL <- "http://server.arcgisonline.com/ArcGIS/rest/services/Canvas/World_Light_Gray_Base/MapServer/tile/{z}/{y}/{x}"
17 |
18 | basemap <- leaflet(width = "100%", height = "400px") %>%
19 | addTiles(tilesURL) %>%
20 | addPolylines(data = regions, weight = 1, color = "brown")
21 |
--------------------------------------------------------------------------------
/javascript/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "leaflet.minicharts",
3 | "version": "0.2.1",
4 | "description": "javascript library for the R package leaflet.minicharts",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "author": "Francois Guillem",
10 | "license": "GPL-2.0+",
11 | "dependencies": {
12 | "leaflet.flow": "0.2.5",
13 | "leaflet.minichart": "0.2.5"
14 | },
15 | "devDependencies": {
16 | "grunt": "^1.5.3",
17 | "grunt-browserify": "^6.0.0",
18 | "grunt-contrib-clean": "^2.0.0",
19 | "grunt-contrib-concat": "^1.0.1",
20 | "grunt-contrib-uglify": "^5.0.1",
21 | "grunt-contrib-watch": "^1.1.0"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/shiny-demo/leaflet-minicharts-demo/ui.R:
--------------------------------------------------------------------------------
1 | # User interface
2 | library(shiny)
3 | library(leaflet)
4 |
5 | ui <- fluidPage(
6 | titlePanel("Demo of leaflet.minicharts"),
7 | p("This application uses the data.frame 'eco2mix', included in the 'leaflet.minicharts' packages.",
8 | "It contains the monthly electric production of french regions from 2013 to 2017."),
9 |
10 | sidebarLayout(
11 |
12 | sidebarPanel(
13 | selectInput("prods", "Select productions", choices = prodCols, multiple = TRUE),
14 | selectInput("type", "Chart type", choices = c("bar","pie", "polar-area", "polar-radius")),
15 | checkboxInput("labels", "Show values")
16 | ),
17 |
18 | mainPanel(
19 | leafletOutput("map")
20 | )
21 |
22 | )
23 | )
24 |
--------------------------------------------------------------------------------
/shiny-demo/leaflet-minicharts-demo/server.R:
--------------------------------------------------------------------------------
1 | # server function
2 | function(input, output, session) {
3 | # Initialize map
4 | output$map <- renderLeaflet({
5 | basemap %>%
6 | addMinicharts(
7 | prodRegions$lng, prodRegions$lat,
8 | layerId = prodRegions$area,
9 | width = 45, height = 45
10 | )
11 | })
12 |
13 | # Update charts each time input value changes
14 | observe({
15 | if (length(input$prods) == 0) {
16 | data <- 1
17 | } else {
18 | data <- prodRegions[, input$prods]
19 | }
20 | maxValue <- max(as.matrix(data))
21 |
22 | leafletProxy("map", session) %>%
23 | updateMinicharts(
24 | prodRegions$area,
25 | chartdata = data,
26 | maxValues = maxValue,
27 | time = prodRegions$month,
28 | type = ifelse(length(input$prods) < 2, "polar-area", input$type),
29 | showLabels = input$labels
30 | )
31 | })
32 | }
33 |
--------------------------------------------------------------------------------
/tests/testthat/test-flows.R:
--------------------------------------------------------------------------------
1 | # context("flows")
2 | #
3 | # # Helper function
4 | # expect_invoke_js_method <- function(method, postProcess = I) {
5 | # map <- leaflet::leaflet() %>%
6 | # addFlows(0, 0, 1, 1, layerId = "a") %>%
7 | # postProcess()
8 | #
9 | # expect_true(all(method %in% map$jsmethods))
10 | # }
11 | #
12 | # with_mock(
13 | # `leaflet::invokeMethod` = function(map, data, method, ...) {
14 | # map$jsargs = list(...)
15 | # map$jsmethods <- c(map$jsmethods, method)
16 | # map
17 | # },
18 | # {
19 | #
20 | # test_that("One can add, update, clear and remove flows", {
21 | # expect_invoke_js_method("addFlows")
22 | # expect_invoke_js_method("updateFlows", function(map) {
23 | # updateFlows(map, layerId = "a", flow = 0.5)
24 | # })
25 | # expect_invoke_js_method("clearFlows", clearFlows)
26 | # expect_invoke_js_method("removeFlows", function(map) {
27 | # removeFlows(map, "a")
28 | # })
29 | # })
30 | #
31 | # }
32 | # )
33 |
--------------------------------------------------------------------------------
/appveyor.yml:
--------------------------------------------------------------------------------
1 | # DO NOT CHANGE the "init" and "install" sections below
2 |
3 | # Download script file from GitHub
4 | init:
5 | ps: |
6 | $ErrorActionPreference = "Stop"
7 | Invoke-WebRequest http://raw.github.com/krlmlr/r-appveyor/master/scripts/appveyor-tool.ps1 -OutFile "..\appveyor-tool.ps1"
8 | Import-Module '..\appveyor-tool.ps1'
9 |
10 | install:
11 | ps: Bootstrap
12 |
13 | # Adapt as necessary starting from here
14 |
15 | build_script:
16 | - travis-tool.sh install_deps
17 |
18 | test_script:
19 | - travis-tool.sh run_tests
20 |
21 | on_failure:
22 | - 7z a failure.zip *.Rcheck\*
23 | - appveyor PushArtifact failure.zip
24 |
25 | artifacts:
26 | - path: '*.Rcheck\**\*.log'
27 | name: Logs
28 |
29 | - path: '*.Rcheck\**\*.out'
30 | name: Logs
31 |
32 | - path: '*.Rcheck\**\*.fail'
33 | name: Logs
34 |
35 | - path: '*.Rcheck\**\*.Rout'
36 | name: Logs
37 |
38 | - path: '\*_*.tar.gz'
39 | name: Bits
40 |
41 | - path: '\*_*.zip'
42 | name: Bits
43 |
--------------------------------------------------------------------------------
/DESCRIPTION:
--------------------------------------------------------------------------------
1 | Package: leaflet.minicharts
2 | Type: Package
3 | Title: Mini Charts for Interactive Maps
4 | Version: 0.6.3
5 | Authors@R: c(
6 | person(given = "Tatiana",
7 | family = "Vargas",
8 | role = c("aut", "cre"),
9 | email = "tatiana.vargas@rte-france.com"),
10 | person("Jalal-Edine", "ZAWAM", role = "aut"),
11 | person("Benoit", "Thieurmel", email = "benoit.thieurmel@datastorm.fr", role = "aut"),
12 | person("Francois", "Guillem", role = "aut"),
13 | person("RTE", role = "cph")
14 | )
15 | Description: Add and modify small charts on an interactive map created with
16 | package 'leaflet'. These charts can be used to represent at same time multiple
17 | variables on a single map.
18 | License: GPL (>= 2) | file LICENSE
19 | Depends: R (>= 2.10)
20 | Imports: leaflet (>= 1.1.0),
21 | htmltools
22 | Encoding: UTF-8
23 | LazyData: true
24 | RoxygenNote: 7.2.2
25 | Suggests: knitr,
26 | rmarkdown,
27 | dplyr,
28 | shiny,
29 | manipulateWidget,
30 | testthat,
31 | covr
32 | VignetteBuilder: knitr
33 |
--------------------------------------------------------------------------------
/cran-comments.md:
--------------------------------------------------------------------------------
1 | ## Test environments
2 | * local OS X install, R 3.6.1
3 | * ubuntu 14.04 (on travis-ci), R 3.6.1
4 | * win-builder (devel and release)
5 |
6 | ## R CMD check results
7 |
8 | 0 errors | 0 warnings | 1 note
9 |
10 | * Re-Fixing CRAN URL not in canonical form in README, Sorry for that. Thank you!
11 |
12 | ## Version: 0.6.3
13 | * update person, new maintener Tatiana Vargas
14 | * Fix/comment tests =>
15 | - '`with_mock()` was deprecated in testthat 3.3.0.
16 | Please use `with_mocked_bindings()` instead'
17 |
18 | ## Test environments
19 | * R version 4.1.2 (2021-11-01)
20 | * Platform: x86_64-pc-linux-gnu (64-bit)
21 | * Running under: Ubuntu 22.04.4 LTS
22 |
23 | ## R CMD check results (only local warnings/notes)
24 | ❯ checking data for ASCII and uncompressed saves ... OK
25 | WARNING
26 | ‘qpdf’ is needed for checks on size reduction of PDFs
27 |
28 | ❯ checking for future file timestamps ... NOTE
29 | unable to verify current time
30 |
31 | 0 errors ✔ | 1 warning ✖ | 1 note ✖
32 |
33 |
34 | # Re-Fixing OLD Travis URL in README (deleted),
35 | Sorry for that (didn't see that with local check `rcmdcheck::rcmdcheck(args = "--as-cran")`.
36 | Thank you!
37 |
38 |
39 |
--------------------------------------------------------------------------------
/R/preprocess_args.R:
--------------------------------------------------------------------------------
1 | # Copyright © 2016 RTE Réseau de transport d’électricité
2 |
3 | #' Private function that prepare arguments for other functions. It transforms
4 | #' them in a table with one column per argument. In order to improve memory
5 | #' management, optional arguments with a single value are not added to the table
6 | #' but there value is stored in a specific list.
7 | #'
8 | #' @param required
9 | #' Named list of required parameters
10 | #' @param optional
11 | #' Named list of optional parameters
12 | #'
13 | #' @return
14 | #' A list with two elements:
15 | #' - options: data.frame with required args and variing optional args
16 | #' - staticOptions: a list with single value args.
17 | #'
18 | #' @noRd
19 | #'
20 | .preprocessArgs <- function(required, optional) {
21 | options <- do.call(data.frame, required)
22 | staticOptions <- list()
23 | for (o in names(optional)) {
24 | if (!is.null(optional[[o]])) {
25 | if (length(optional[[o]]) == 1) {
26 | staticOptions[[o]] <- optional[[o]]
27 | } else {
28 | options[[o]] <- optional[[o]]
29 | }
30 | }
31 | }
32 | list(
33 | options = options,
34 | staticOptions = staticOptions
35 | )
36 | }
37 |
--------------------------------------------------------------------------------
/man/eco2mix.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/data-doc.R
3 | \docType{data}
4 | \name{eco2mix}
5 | \alias{eco2mix}
6 | \alias{eco2mixBalance}
7 | \title{Electric production, consumption and exchanges of France}
8 | \format{
9 | An object of class \code{data.frame} with 686 rows and 22 columns.
10 |
11 | An object of class \code{data.frame} with 430 rows and 7 columns.
12 | }
13 | \usage{
14 | eco2mix
15 |
16 | eco2mixBalance
17 | }
18 | \description{
19 | \code{eco2mix} contains the electric production, consumption and exchanges
20 | of France from january 2010 to february 2017 and of 12 french regions from
21 | january 2013 to february 2017.
22 |
23 | In addition to the total production, the table contains one column for each
24 | type of production. The table also contains the latitude and longitude of
25 | the center of the regions.
26 |
27 | \code{eco2mixBalance} is an extract of \code{eco2mix} that contains only
28 | exchanges between France and neighbouring countries, in a convenient format
29 | to represent flows on a map.
30 | }
31 | \references{
32 | "https://www.rte-france.com/eco2mix"
33 | }
34 | \author{
35 | Francois Guillem
36 | }
37 | \keyword{datasets}
38 |
--------------------------------------------------------------------------------
/R/data-doc.R:
--------------------------------------------------------------------------------
1 | #' Electric production, consumption and exchanges of France
2 | #'
3 | #' @description
4 | #' \code{eco2mix} contains the electric production, consumption and exchanges
5 | #' of France from january 2010 to february 2017 and of 12 french regions from
6 | #' january 2013 to february 2017.
7 | #'
8 | #' In addition to the total production, the table contains one column for each
9 | #' type of production. The table also contains the latitude and longitude of
10 | #' the center of the regions.
11 | #'
12 | #' \code{eco2mixBalance} is an extract of \code{eco2mix} that contains only
13 | #' exchanges between France and neighbouring countries, in a convenient format
14 | #' to represent flows on a map.
15 | #'
16 | #' @docType data
17 | #' @author Francois Guillem
18 | #' @references "https://www.rte-france.com/eco2mix"
19 | #' @keywords datasets
20 | "eco2mix"
21 |
22 | #' @rdname eco2mix
23 | "eco2mixBalance"
24 |
25 |
26 | #' d3 color palette
27 | #'
28 | #' @description
29 | #' A character vector containing ten colors. These colors are used as the default
30 | #' color palette
31 | #' @docType data
32 | #' @author Francois Guillem
33 | #' @references \url{https://github.com/d3/d3-scale}
34 | #' @keywords datasets
35 | "d3.schemeCategory10"
36 |
--------------------------------------------------------------------------------
/javascript/sync_with.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | 'use strict';
3 |
4 | LeafletWidget.methods.syncWith = function(groupname) {
5 | var self = this;
6 | if (!LeafletWidget.syncGroups) {
7 | LeafletWidget.syncGroups = {};
8 | LeafletWidget.syncGroups.sync = function(map, groupname) {
9 | var zoom = map.getZoom();
10 | var center = map.getCenter();
11 | if(LeafletWidget.syncGroups[groupname].length > 1){
12 | for (var i = 0; i < LeafletWidget.syncGroups[groupname].length; i++) {
13 | LeafletWidget.syncGroups[groupname][i].off("move");
14 | LeafletWidget.syncGroups[groupname][i].setView(center, zoom, {animate: false});
15 | LeafletWidget.syncGroups[groupname][i].on("move", function() {
16 | LeafletWidget.syncGroups.sync(this, groupname);
17 | });
18 | }
19 | }
20 | }
21 | }
22 | if (!LeafletWidget.syncGroups[groupname]) LeafletWidget.syncGroups[groupname] = [];
23 |
24 | LeafletWidget.syncGroups[groupname].push(self);
25 |
26 | // Bug with leafet > 1, & no really need...?
27 | self.on("move", function() {
28 | LeafletWidget.syncGroups.sync(self, groupname);
29 | });
30 |
31 | self.syncGroup = groupname;
32 | LeafletWidget.syncGroups.sync(self, groupname);
33 | }
34 | }());
35 |
--------------------------------------------------------------------------------
/javascript/Gruntfile.js:
--------------------------------------------------------------------------------
1 | // Copyright © 2016 RTE Réseau de transport d’électricité
2 |
3 | module.exports = function(grunt) {
4 |
5 | // Project configuration.
6 | grunt.initConfig({
7 | pkg: grunt.file.readJSON('package.json'),
8 | browserify: {
9 | options: {
10 | browserifyOptions : {
11 | debug: true
12 | }
13 | },
14 | build: {
15 | files: {
16 | "../inst/leaflet.minicharts.min.js": ["index.js"]
17 | }
18 | }
19 | },
20 | uglify: {
21 | options: {
22 | banner: '/*! <%= pkg.name %> <%= pkg.version %> <%= grunt.template.today("yyyy-mm-dd") %>\n' +
23 | 'Copyright © 2016 RTE Réseau de transport d’électricité */\n'
24 | },
25 | build: {
26 | src: '../inst/leaflet.minicharts.min.js',
27 | dest: '../inst/leaflet.minicharts.min.js'
28 | },
29 | },
30 | watch: {
31 | build: {
32 | files: ["*.js"],
33 | tasks: ['browserify']
34 | }
35 | }
36 | });
37 |
38 | // Load plugins
39 | grunt.loadNpmTasks('grunt-browserify');
40 | grunt.loadNpmTasks('grunt-contrib-uglify');
41 | grunt.loadNpmTasks('grunt-contrib-watch');
42 | // Default task(s).
43 | grunt.registerTask('build', ['browserify', 'uglify']);
44 | grunt.registerTask('default', ["build"]);
45 |
46 | };
47 |
--------------------------------------------------------------------------------
/man/popupArgs.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/popup_args.R
3 | \name{popupArgs}
4 | \alias{popupArgs}
5 | \title{Options for popup generation}
6 | \usage{
7 | popupArgs(
8 | showTitle = TRUE,
9 | showValues = TRUE,
10 | labels = NULL,
11 | supValues = NULL,
12 | supLabels = colnames(supValues),
13 | html = NULL,
14 | noPopup = FALSE,
15 | digits = NULL
16 | )
17 | }
18 | \arguments{
19 | \item{showTitle}{If \code{TRUE} layer id is displayed as title of
20 | popups.}
21 |
22 | \item{showValues}{If \code{TRUE}, values are displayed in popups}
23 |
24 | \item{labels}{Names of values. If \code{NULL}, column names of the data bound
25 | to a chart are used.}
26 |
27 | \item{supValues}{A \code{data.frame} containing additional values to display
28 | in popups.}
29 |
30 | \item{supLabels}{Names of the additional values.}
31 |
32 | \item{html}{Character vector containing custom html code for popups. You can
33 | use this parameter when you are not happy with the default popups.}
34 |
35 | \item{noPopup}{If \code{TRUE}, popups are not created.}
36 |
37 | \item{digits}{Max number of decimal digits to display for numeric values. If
38 | \code{NULL}, all digits are displayed.}
39 | }
40 | \value{
41 | List containing options for popup generation
42 | }
43 | \description{
44 | This function simply returns a list of options to control the generation of
45 | popups.
46 | }
47 |
--------------------------------------------------------------------------------
/R/popup_args.R:
--------------------------------------------------------------------------------
1 | #' Options for popup generation
2 | #'
3 | #' This function simply returns a list of options to control the generation of
4 | #' popups.
5 | #'
6 | #' @param showTitle If \code{TRUE} layer id is displayed as title of
7 | #' popups.
8 | #' @param showValues If \code{TRUE}, values are displayed in popups
9 | #' @param labels Names of values. If \code{NULL}, column names of the data bound
10 | #' to a chart are used.
11 | #' @param supValues A \code{data.frame} containing additional values to display
12 | #' in popups.
13 | #' @param supLabels Names of the additional values.
14 | #' @param html Character vector containing custom html code for popups. You can
15 | #' use this parameter when you are not happy with the default popups.
16 | #' @param noPopup If \code{TRUE}, popups are not created.
17 | #' @param digits Max number of decimal digits to display for numeric values. If
18 | #' \code{NULL}, all digits are displayed.
19 | #'
20 | #' @return List containing options for popup generation
21 | #'
22 | #' @export
23 | popupArgs <- function(showTitle = TRUE, showValues = TRUE, labels = NULL,
24 | supValues = NULL, supLabels = colnames(supValues),
25 | html = NULL, noPopup = FALSE, digits = NULL) {
26 | if(!showTitle && !showValues & is.null(html) & is.null(supValues)) noPopup <- TRUE
27 | if (!is.null(html) | noPopup) supValues <- NULL
28 | as.list(environment())
29 | }
30 |
--------------------------------------------------------------------------------
/man/syncWith.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/sync_with.R
3 | \name{syncWith}
4 | \alias{syncWith}
5 | \title{Synchronize multiple maps}
6 | \usage{
7 | syncWith(map, groupname)
8 | }
9 | \arguments{
10 | \item{map}{A leaflet map object created with \code{\link[leaflet]{leaflet}}.}
11 |
12 | \item{groupname}{Character string. All maps that use the same group name will
13 | be synchronized.}
14 | }
15 | \value{
16 | The modified leaflet map object.
17 | }
18 | \description{
19 | This function can be used when multiple leaflet maps are displayed on the
20 | same view (for instance in a shiny application or a Rmarkdown document) and
21 | one wants to synchronize their center, zoom and time.
22 |
23 | \code{syncWith()} can also be used with basic leaflet maps to synchronize
24 | only their zoom and center.
25 | }
26 | \examples{
27 | if (require(manipulateWidget) & require(leaflet)) {
28 |
29 | # Synchronize zoom and center of basic maps.
30 | basicMap1 <- leaflet() \%>\% addTiles() \%>\% syncWith("basicmaps")
31 | basicMap2 <- leaflet() \%>\% addTiles() \%>\% syncWith("basicmaps")
32 | combineWidgets(basicMap1, basicMap2)
33 |
34 | # Synchronize time step of two maps that represent the evolution of some
35 | # variable.
36 | map1 <- leaflet() \%>\% addTiles() \%>\%
37 | addMinicharts(0, 40, chartdata = 1:10, time = 1:10) \%>\%
38 | syncWith("maps")
39 | map2 <- leaflet() \%>\% addTiles() \%>\%
40 | addMinicharts(0, 40, chartdata = 10:1, time = 1:10) \%>\%
41 | syncWith("maps")
42 | combineWidgets(map1, map2)
43 |
44 | }
45 |
46 | }
47 |
--------------------------------------------------------------------------------
/R/sync_with.R:
--------------------------------------------------------------------------------
1 | #' Synchronize multiple maps
2 | #'
3 | #' @description
4 | #' This function can be used when multiple leaflet maps are displayed on the
5 | #' same view (for instance in a shiny application or a Rmarkdown document) and
6 | #' one wants to synchronize their center, zoom and time.
7 | #'
8 | #' \code{syncWith()} can also be used with basic leaflet maps to synchronize
9 | #' only their zoom and center.
10 | #'
11 | #' @param groupname Character string. All maps that use the same group name will
12 | #' be synchronized.
13 | #' @inheritParams addMinicharts
14 | #'
15 | #' @return The modified leaflet map object.
16 | #'
17 | #' @examples
18 | #' if (require(manipulateWidget) & require(leaflet)) {
19 | #'
20 | #' # Synchronize zoom and center of basic maps.
21 | #' basicMap1 <- leaflet() %>% addTiles() %>% syncWith("basicmaps")
22 | #' basicMap2 <- leaflet() %>% addTiles() %>% syncWith("basicmaps")
23 | #' combineWidgets(basicMap1, basicMap2)
24 | #'
25 | #' # Synchronize time step of two maps that represent the evolution of some
26 | #' # variable.
27 | #' map1 <- leaflet() %>% addTiles() %>%
28 | #' addMinicharts(0, 40, chartdata = 1:10, time = 1:10) %>%
29 | #' syncWith("maps")
30 | #' map2 <- leaflet() %>% addTiles() %>%
31 | #' addMinicharts(0, 40, chartdata = 10:1, time = 1:10) %>%
32 | #' syncWith("maps")
33 | #' combineWidgets(map1, map2)
34 | #'
35 | #' }
36 | #'
37 | #' @export
38 | syncWith <- function(map, groupname) {
39 | # Add minichart dependency to the map dependencies
40 | map$dependencies <- c(map$dependencies, minichartDeps())
41 |
42 | invokeMethod(map, data = leaflet::getMapData(map), "syncWith", groupname)
43 | }
44 |
--------------------------------------------------------------------------------
/tests/testthat/test-preprocess_args.R:
--------------------------------------------------------------------------------
1 | context(".preprocessArgs")
2 |
3 | describe(".preprocessArgs", {
4 | it ("creates a data.frame with one column per argument", {
5 | opts <- .preprocessArgs(list(a = 1:3, b = 4:6), list(c = 7:9, d = 10:12))
6 | expect_is(opts$options, "data.frame")
7 | expect_equal(dim(opts$options), c(3, 4))
8 | expect_equal(names(opts$options), c("a", "b", "c", "d"))
9 | })
10 |
11 | it ("separates static optional arguments", {
12 | opts <- .preprocessArgs(list(a = 1:3, b = 4:6), list(c = 7:9, d = 10))
13 | expect_is(opts$options, "data.frame")
14 | expect_equal(dim(opts$options), c(3, 3))
15 | expect_equal(names(opts$options), c("a", "b", "c"))
16 | expect_is(opts$staticOptions, "list")
17 | expect_equal(names(opts$staticOptions), "d")
18 | })
19 |
20 | it ("does not separate static required arguments", {
21 | opts <- .preprocessArgs(list(a = 1:3, b = 4), list(c = 7:9, d = 10:12))
22 | expect_is(opts$options, "data.frame")
23 | expect_equal(dim(opts$options), c(3, 4))
24 | expect_equal(names(opts$options), c("a", "b", "c", "d"))
25 | expect_equal(length(opts$staticOptions), 0)
26 | })
27 |
28 | it ("does not accept NULL required arguments", {
29 | expect_error(.preprocessArgs(list(a = 1:3, b = NULL), list(c = 7:9, d = 10:12)))
30 | })
31 |
32 | it ("accepts NULL optional required", {
33 | opts <- .preprocessArgs(list(a = 1:3, b = 4:6), list(c = 7:9, d = NULL))
34 | expect_is(opts$options, "data.frame")
35 | expect_equal(dim(opts$options), c(3, 3))
36 | expect_equal(names(opts$options), c("a", "b", "c"))
37 | expect_equal(length(opts$staticOptions), 0)
38 | })
39 |
40 | })
41 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # leaflet.minicharts
2 |
3 | > Minicharts for dynamic [{leaflet}](https://rstudio.github.io/leaflet/) maps
4 |
5 |
6 | [](https://cran.r-project.org/package=leaflet.minicharts)
7 | [](https://cran.r-project.org/package=leaflet.minicharts)
8 |
9 |
10 |
11 | For a few years now, it has become easy to create interactive maps with R thanks to the package `leaflet` by the Rstudio team. Nevertheless, it only provides only a few functions to create basic shapes on a map, so the information that can be represented on a single map is limited: if you have some data associated to some points, you can only represent at most two variables by drawing circles and changing their radius and color according to data.
12 |
13 | `leaflet.minicharts` is an R package that provides two functions to add and update small charts on an interactive maps created with the package `leaflet`. These charts can be used to represent as many variables as desired associated to geographical points. Currently, three types of chart are supported: barcharts (the default), pie charts and polar area charts (with two variants: "polar-area" and "polar-radius", where values are represented respectively by the area or the radius of the slices).
14 |
15 | Here are screenshots of sample outputs:
16 |
17 | 
18 | 
19 | 
20 |
21 | ## Installation and usage
22 |
23 | You can install the package from CRAN:
24 |
25 | ```r
26 | install.packages("leaflet.minicharts")
27 | ```
28 |
29 | To see how simple it is to use this package, have a look at the [package vignette](https://cran.r-project.org/package=leaflet.minicharts/vignettes/introduction.html).
30 |
31 |
32 | ## Contributing
33 |
34 | Contributions to the library are welcome and can be submitted in the form of pull requests to this repository:
35 |
36 | https://github.com/rte-antares-rpackage/leaflet.minicharts
37 |
38 | This package contains some javascript code. To modify it requires `npm` and `grunt`. First modify files in the `javascript` folder. Then in a terminal, run the following commands:
39 |
40 | ```
41 | cd javascript
42 | npm install
43 | grunt build
44 | ```
45 |
46 | ## License Information
47 |
48 | Copyright 2015-2018 RTE (France)
49 |
50 | * RTE: http://www.rte-france.com
51 |
52 | This Source Code is subject to the terms of the GNU General Public License, version 2 or any higher version. If a copy of the GPL-v2 was not distributed with this file, You can obtain one at https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html.
53 |
--------------------------------------------------------------------------------
/inst/minicharts.css:
--------------------------------------------------------------------------------
1 | /* popups */
2 |
3 | .popup > h2 {
4 | color: #555;
5 | font-size: 16px;
6 | margin: 5px 0 0 0;
7 | }
8 |
9 | .popup table {
10 | border: none 1px;
11 | border-collapse: collapse;
12 | }
13 |
14 | .popup table td {
15 | border: none;
16 | padding: 2px 5px;
17 | border: solid 1px #DDDDDD;
18 | border-left-style: none;
19 | border-right-style: none;
20 | }
21 |
22 | .popup .key {
23 | font-weight:bold;
24 | color: #1f77b4;
25 | text-align: right;
26 | padding-right: 5px;
27 | }
28 |
29 | .popup .key::after {
30 | content:" :";
31 | }
32 |
33 | .popup .value {
34 | text-align: right;
35 | }
36 |
37 | .popup hr {
38 | margin: 3px 0 6px 0;
39 | }
40 |
41 | /* time slider */
42 |
43 | .leaflet-control-slider {
44 | font-size: 16px;
45 | height:16px;
46 | line-height:16px;
47 | position:relative;
48 | width: 120px;
49 | }
50 |
51 | p.time-slider-label {
52 | margin: 0;
53 | font-size: 16px;
54 | text-align: center;
55 | color: #333;
56 | }
57 |
58 | input[type=range].time-slider {
59 | -webkit-appearance: none;
60 | width: 100px;
61 | height:16px;
62 | margin: 0;
63 | padding: 0;
64 | margin-right: 5px;
65 | vertical-align: middle;
66 | position:absolute;
67 | }
68 |
69 | input[type=range].time-slider:focus {
70 | outline: none;
71 | }
72 |
73 | input[type=range].time-slider::-ms-track {
74 | width: 100%;
75 | cursor: pointer;
76 | background: transparent;
77 | border-color: transparent;
78 | color: transparent;
79 | margin:5px 0;
80 | }
81 |
82 | /* Special styling for WebKit/Blink */
83 | input[type=range].time-slider::-webkit-slider-thumb {
84 | -webkit-appearance: none;
85 | border: 1px solid #777;
86 | height: 16px;
87 | width: 16px;
88 | border-radius: 8px;
89 | background: #ffffff;
90 | cursor: pointer;
91 | margin-top: -6px;
92 | box-sizing: border-box;
93 | }
94 |
95 | input[type=range].time-slider::-moz-range-thumb {
96 | border: 1px solid #777;
97 | height: 14px;
98 | width: 14px;
99 | border-radius: 7px;
100 | background: #ffffff;
101 | cursor: pointer;
102 | }
103 |
104 | input[type=range].time-slider::-ms-thumb {
105 | border: 1px solid #777777;
106 | height: 14px;
107 | width: 14px;
108 | border-radius: 7px;
109 | background: #ffffff;
110 | cursor: pointer;
111 | }
112 |
113 | input[type=range].time-slider::-webkit-slider-runnable-track {
114 | width: 100%;
115 | height: 6px;
116 | cursor: pointer;
117 | background: #ddd;
118 | border-radius: 3px;
119 | border: 1px solid #bbb;
120 | box-sizing: border-box;
121 | margin: 5px 0;
122 | }
123 |
124 | input[type=range].time-slider:focus::-webkit-slider-runnable-track {
125 | }
126 |
127 | input[type=range].time-slider::-moz-range-track {
128 | width: 100%;
129 | height: 6px;
130 | cursor: pointer;
131 | background: #ddd;
132 | border-radius: 3px;
133 | border: 1px solid #bbb;
134 | box-sizing: border-box;
135 | }
136 |
137 | input[type=range].time-slider::-ms-track {
138 | width: 100%;
139 | height: 6px;
140 | cursor: pointer;
141 | background: #ddd;
142 | border-radius: 3px;
143 | border: 1px solid #bbb;
144 | box-sizing: border-box;
145 | }
146 |
147 | .playpause {
148 | cursor: pointer;
149 | color: #999;
150 | position: absolute;
151 | right: 0;
152 | }
153 |
--------------------------------------------------------------------------------
/javascript/flow_bindings.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | 'use strict';
3 |
4 | var utils = require("./utils");
5 | /*
6 | Add a segment on the map with a triangle in the middle representing its direction.
7 |
8 | @param data:
9 | data.frame with columns x0, y0, x1, y1 and optionnaly dir, color, opacity, weight
10 | popup and layerId
11 |
12 | */
13 | LeafletWidget.methods.addFlows = function(options, timeLabels, initialTime, popupArgs, popupOptions) {
14 | var self = this;
15 |
16 | // Initialize time slider
17 | var timeId = utils.initTimeSlider(this, timeLabels, initialTime);
18 |
19 | // Add method to update time
20 | utils.addSetTimeIdMethod("Flow", "setStyle");
21 |
22 | // Create flows
23 | utils.processOptions(options, function(opts, i, staticOpts) {
24 | for (var t = 0; t < opts.length; t++) {
25 | if (typeof opts[t].value != "undefined") opts[t].data = [opts[t].value];
26 | else if (typeof staticOpts.value != "undefined") opts[t].data = [staticOpts.value];
27 |
28 | if(popupArgs && popupArgs.supValues) {
29 | opts[t].popupData = popupArgs.supValues[i][t];
30 | }
31 | if(popupArgs && popupArgs.html) {
32 | opts[t].popupHTML = popupArgs.html[i][t];
33 | }
34 | }
35 |
36 | var l = L.flow(
37 | [staticOpts.lat0, staticOpts.lng0],
38 | [staticOpts.lat1, staticOpts.lng1],
39 | utils.getInitOptions(opts, staticOpts, timeId)
40 | );
41 | l.opts = opts;
42 | l.timeId = timeId;
43 | if (staticOpts.layerId.indexOf("_flow") != 0) l.layerId = staticOpts.layerId;
44 | l.popupArgs = popupArgs;
45 | if (popupOptions) l.popupOptions = popupOptions;
46 |
47 | utils.setPopup(l, timeId);
48 | self.layerManager.addLayer(l, "flow", staticOpts.layerId);
49 | });
50 | };
51 |
52 | /*
53 | Update the style of directed segments
54 |
55 | @param data
56 | data.frame with columns layerId and optionnaly dir, color, opacity popup
57 | and weight
58 |
59 | */
60 | LeafletWidget.methods.updateFlows = function(options, timeLabels, initialTime, popupArgs, popupOptions) {
61 | var self = this;
62 |
63 | var timeId = utils.initTimeSlider(this, timeLabels, initialTime);
64 |
65 | utils.processOptions(options, function(opts, i, staticOpts) {
66 | var l = self.layerManager.getLayer("flow", staticOpts.layerId);
67 | if (!l) return;
68 |
69 | if (popupArgs) l.popupArgs = popupArgs;
70 | if (popupOptions) l.popupOptions = popupOptions;
71 | for (var t = 0; t < opts.length; t++) {
72 | if (typeof opts[t].value != "undefined") opts[t].data = [opts[t].value];
73 | else if (typeof staticOpts.value != "undefined") opts[t].data = [staticOpts.value];
74 | else if (l.opts[t]) opts[t].data = l.opts[t].data
75 |
76 | if(popupArgs && popupArgs.supValues) {
77 | opts[t].popupData = popupArgs.supValues[i][t];
78 | } else {
79 | if (l.opts[t]) opts[t].popupData = l.opts[t].popupData;
80 | }
81 | if(popupArgs && popupArgs.html) {
82 | opts[t].popupHTML = popupArgs.html[i][t];
83 | } else {
84 | if (l.opts[t]) opts[t].popupHTML = l.opts[t].popupHTML;
85 | }
86 | }
87 |
88 | l.setStyle(utils.getInitOptions(opts, staticOpts, timeId));
89 | l.opts = opts;
90 | l.timeId = timeId;
91 |
92 | utils.setPopup(l, timeId);
93 | });
94 | };
95 |
96 | utils.addRemoveMethods("Flow", "flow");
97 | }());
98 |
--------------------------------------------------------------------------------
/NEWS:
--------------------------------------------------------------------------------
1 | Changes in version 0.6.3 (2025-10-28)
2 | * update maintainer person
3 |
4 |
5 |
6 | Changes in version 0.6.2 (2021-04-07)
7 |
8 | NEW FEATURES:
9 | * add popupOptions
10 |
11 | Changes in version 0.6.1 (2019-12-16)
12 |
13 | BUGFIXES:
14 | * Fix / improve syncWith
15 |
16 | Changes in version 0.6.0 (2019-11-26)
17 |
18 | BUGFIXES:
19 | * Fix syncGroups
20 | * Fix flow with NULL value / nearest position
21 |
22 | Changes in version 0.5.4 (2018-06-06)
23 |
24 | BUGFIXES:
25 | * Fix syncGroups using leaflet 2.0.0
26 | * Fix clear and remove methods leaflet 2.0.0
27 |
28 | Changes in version 0.5.3 (2018-04-25)
29 |
30 | * leaflet.minicharts take into account leaflet 2.0.0 and leaflet.flow 0.2.3
31 |
32 | Changes in version 0.5.2 (2017-12-06)
33 |
34 | BUGFIXES:
35 | * update file description
36 | * Invalid maxValues (0, NA, ...)
37 |
38 | Changes in version 0.5.1 (2017-08-17)
39 |
40 | BUGFIXES:
41 | * updateMinicharts() was incapable of updating color palette (#19)
42 |
43 | Changes in version 0.5.0 (2017-07-10)
44 |
45 | NEW FEATURES:
46 | * addMinicharts() and updateMinicharts() gain a new parameter "onChange" that permits to execute some arbitrary javascript code each time a minichart is updated.
47 | * The package now uses unit testing, code coverage and continuous integration in order to improve code quality and reliability.
48 |
49 | BUGFIXES:
50 | * Some warnings that appeared with R 3.4 have been removed.
51 | * Parameter "initialTime" was not working correctly in some situations.
52 | * Parameter "fillColor" was ignored if someone tried to add a single minicharts.
53 |
54 |
55 | Changes in version 0.4.0 (2017-06-19)
56 |
57 | NEW FEATURES:
58 | * New function syncWith() to synchronize zoom, center and time of multiple leaflet maps.
59 |
60 |
61 | Changes in version 0.3.1 (2017-05-30)
62 |
63 | BUGFIXES:
64 | * addMinicharts() uses functions that are only available in package leaflet >= 1.1.0. The package now requires this version of leaflet.
65 |
66 |
67 | Changes in version 0.3 (2017-05-19)
68 |
69 | NEW FEATURES:
70 | * Default popups are now automatically generated for charts and flows. A new function "popupArgs()" has been added to control how popups are generated.
71 | * The size of the JSON data passed from R to javascript has been optimized. In some situations it has been divided by 10, so maps are generated quicker and when they are saved on harddrive, the resulting files are smaller.
72 |
73 | BUGFIXES:
74 | * The default color palette was not found when leaflet.minicharts was imported in another package.
75 | * add/updateMinicharts/Flows were crashing if layerId was a factor and some levels were missing.
76 | * Some bugs were occuring if after an update of a map, the number of timesteps increased.
77 |
78 |
79 | Changes in version 0.2 (2017-05-05)
80 |
81 | BREAKING CHANGES:
82 | * Argument "data" of addMinicharts() and updateMinicharts() has been renamed "chartdata" to avoid confusion with parameter "data" of function leaflet().
83 |
84 | NEW FEATURES:
85 | * addMinicharts() now automatically adds a legend to the map by default.
86 | * addMinicharts() has new parameters "labelMinSize" and "labelMaxSize" to control label size.
87 | * addMinicharts() has a new parameter "time" that can be used to create an animated map that represents the evolution of one or more variables. Animations can be shared by saving the map as html or including it in a Ramrkdown document. No need to use Shiny anymore!
88 | * New functions removeMinicharts() and clearMinicharts() to remove some or all minicharts from a map.
89 | * It is now possible to represent flows between two points with function addFlow() and the associated functions updateFlows(), removeFlows() and clearFlows().
90 |
91 | BUGFIXES:
92 | * addMinicharts was crashing if user tried to add a single chart to a map.
93 |
--------------------------------------------------------------------------------
/man/addFlows.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/add_flows.R
3 | \name{addFlows}
4 | \alias{addFlows}
5 | \alias{updateFlows}
6 | \alias{removeFlows}
7 | \alias{clearFlows}
8 | \title{Add or modify flows on a leaflet map}
9 | \usage{
10 | addFlows(
11 | map,
12 | lng0,
13 | lat0,
14 | lng1,
15 | lat1,
16 | color = "blue",
17 | flow = 1,
18 | opacity = 1,
19 | dir = NULL,
20 | time = NULL,
21 | popup = popupArgs(labels = "Flow"),
22 | layerId = NULL,
23 | timeFormat = NULL,
24 | initialTime = NULL,
25 | maxFlow = max(abs(flow)),
26 | minThickness = 1,
27 | maxThickness = 20,
28 | popupOptions = NULL
29 | )
30 |
31 | updateFlows(
32 | map,
33 | layerId,
34 | color = NULL,
35 | flow = NULL,
36 | opacity = NULL,
37 | dir = NULL,
38 | time = NULL,
39 | popup = NULL,
40 | timeFormat = NULL,
41 | initialTime = NULL,
42 | maxFlow = NULL,
43 | minThickness = 1,
44 | maxThickness = 20,
45 | popupOptions = NULL
46 | )
47 |
48 | removeFlows(map, layerId)
49 |
50 | clearFlows(map)
51 | }
52 | \arguments{
53 | \item{map}{A leaflet map object created with \code{\link[leaflet]{leaflet}}.}
54 |
55 | \item{lng0}{Longitude of the origin of the flow.}
56 |
57 | \item{lat0}{Latitude of the origin of the flow.}
58 |
59 | \item{lng1}{Longitude of the destination of the flow.}
60 |
61 | \item{lat1}{Latitude of the destination of the flow.}
62 |
63 | \item{color}{Color of the flow.}
64 |
65 | \item{flow}{Value of the flow between the origin and the destination. If
66 | argument \code{dir} is not set, negative values are interpreted as flows
67 | from destination to origin.}
68 |
69 | \item{opacity}{Opacity of the flow.}
70 |
71 | \item{dir}{Direction of the flow. 1 indicates that the flow goes from origin
72 | to destination and -1 indicates that it goes from destination to origin. If
73 | 0, the arrow is not drawn. If \code{NULL}, then it is equal to the sign of
74 | \code{weight}.}
75 |
76 | \item{time}{A vector with length equal to the number of rows in \code{chartdata}
77 | and containing either numbers representing time indices or dates or
78 | datetimes. Each unique value must appear as many times as the others. This
79 | parameter can be used when one wants to represent the evolution of some
80 | variables on a map.}
81 |
82 | \item{popup}{Options that control popup generation.}
83 |
84 | \item{layerId}{An ID variable. It is mandatory when one wants to update some
85 | chart with \code{updateMinicharts}.}
86 |
87 | \item{timeFormat}{Character string used to format dates and times when
88 | argument \code{time} is a \code{Date}, \code{POSIXct} or \code{POSIXlt}
89 | object. See \code{\link[base]{strptime}} for more information.}
90 |
91 | \item{initialTime}{This argument can be used to set the initial time step
92 | shown when the map is created. It is used only when argument \code{time} is
93 | set.}
94 |
95 | \item{maxFlow}{Maximal value a flow could take.}
96 |
97 | \item{minThickness}{minimal thickness of the line that represents the flow.}
98 |
99 | \item{maxThickness}{maximal thickness of the line that represents the flow.}
100 |
101 | \item{popupOptions}{Change default popupOptions (ex : autoClose, maxHeight, closeButton ...)
102 | See \code{\link[leaflet]{popupOptions}} for more informations.}
103 | }
104 | \value{
105 | The modified leaflet map object.
106 | }
107 | \description{
108 | These functions can be used to represent flows and their evolution on a map
109 | created with \code{\link[leaflet]{leaflet}()}. Flows are simply represented
110 | by a segment between two points with an arrow at its center that indicates the
111 | direction of the flow.
112 | }
113 | \examples{
114 |
115 | require(leaflet)
116 |
117 | # Toy example
118 | leaflet() \%>\% addTiles() \%>\%
119 | addFlows(0, 0, 1, 1, flow = 10)
120 |
121 | # Electric exchanges between France and neighboring countries
122 | data("eco2mixBalance")
123 | bal <- eco2mixBalance
124 | leaflet() \%>\% addTiles() \%>\%
125 | addFlows(
126 | bal$lng0, bal$lat0, bal$lng1, bal$lat1,
127 | flow = bal$balance,
128 | time = bal$month
129 | )
130 |
131 | # popupOptions
132 | data("eco2mixBalance")
133 | bal <- eco2mixBalance
134 | leaflet() \%>\% addTiles() \%>\%
135 | addFlows(
136 | bal$lng0, bal$lat0, bal$lng1, bal$lat1,
137 | flow = bal$balance,
138 | time = bal$month,
139 | popupOptions = list(closeOnClick = FALSE, autoClose = FALSE)
140 | )
141 |
142 | }
143 |
--------------------------------------------------------------------------------
/javascript/minichart_bindings.js:
--------------------------------------------------------------------------------
1 | // Copyright © 2016 RTE Réseau de transport d’électricité
2 |
3 | (function() {
4 | 'use strict';
5 |
6 | var utils = require("./utils");
7 | var d3 = require("d3");
8 |
9 | LeafletWidget.methods.addMinicharts = function(options, data, maxValues, colorPalette, timeLabels, initialTime, popupArgs, onChange, popupOptions) {
10 | var self = this;
11 | var timeId = utils.initTimeSlider(this, timeLabels, initialTime);
12 |
13 | // Add method to update time
14 | utils.addSetTimeIdMethod("Minichart", "setOptions");
15 |
16 | // Create and add minicharts to the map
17 | utils.processOptions(options, function(opts, i, staticOpts) {
18 | for (var t = 0; t < opts.length; t++) {
19 | if (data) {
20 | opts[t].data = data[i][t];
21 | }
22 |
23 | if(popupArgs.supValues) {
24 | opts[t].popupData = popupArgs.supValues[i][t];
25 | }
26 |
27 | if(popupArgs.html) {
28 | opts[t].popupHTML = popupArgs.html[i][t];
29 | }
30 |
31 | if (maxValues) opts[t].maxValues = maxValues;
32 |
33 | if (!opts[t].data || opts[t].data.length == 1) {
34 | opts[t].colors = opts[t].fillColor || staticOpts.fillColor || d3.schemeCategory10[0];
35 | } else {
36 | opts[t].colors = colorPalette || d3.schemeCategory10;
37 | }
38 | }
39 |
40 | var l = L.minichart(
41 | [staticOpts.lat, staticOpts.lng],
42 | utils.getInitOptions(opts, staticOpts, timeId)
43 | );
44 |
45 | // Keep a reference of colors and data for later use.
46 | l.opts = opts;
47 | l.colorPalette = colorPalette || d3.schemeCategory10;
48 | l.timeId = timeId;
49 | l.popupArgs = popupArgs;
50 | if (popupOptions) l.popupOptions = popupOptions;
51 | if (staticOpts.layerId.indexOf("_minichart") != 0) l.layerId = staticOpts.layerId;
52 |
53 | // Popups
54 | var popup = utils.setPopup(l, timeId);
55 |
56 | self.layerManager.addLayer(l, "minichart", staticOpts.layerId);
57 |
58 | if (onChange) {
59 | l.onChange = onChange;
60 | l.onChange(utils.getInitOptions(opts, staticOpts, timeId), popup, d3);
61 | }
62 | });
63 | };
64 |
65 | LeafletWidget.methods.updateMinicharts = function(options, data, maxValues, colorPalette, timeLabels, initialTime, popupArgs, legendLab, onChange, popupOptions) {
66 | var self = this;
67 | var timeId = utils.initTimeSlider(this, timeLabels, initialTime);
68 |
69 | utils.processOptions(options, function(opts, i, staticOpts) {
70 | var l = self.layerManager.getLayer("minichart", staticOpts.layerId);
71 | // If the layer does not exist quit the function
72 | if (!l) return;
73 |
74 | if (popupArgs) l.popupArgs = popupArgs;
75 | else if(data && legendLab) {l.popupArgs.labels = legendLab}
76 | if (popupOptions) l.popupOptions = popupOptions;
77 | for (var t = 0; t < opts.length; t++) { // loop over time steps
78 | if (data) {
79 | opts[t].data = data[i][t];
80 | } else {
81 | if (l.opts[t]) opts[t].data = l.opts[t].data;
82 | }
83 |
84 | if (popupArgs && popupArgs.supValues) {
85 | opts[t].popupData = popupArgs.supValues[i][t];
86 | } else {
87 | if (l.opts[t]) opts[t].popupData = l.opts[t].popupData;
88 | }
89 | if (popupArgs && popupArgs.html) {
90 | opts[t].popupHTML = popupArgs.html[i][t];
91 | } else {
92 | if (l.opts[t]) opts[t].popupHTML = l.opts[t].popupHTML;
93 | }
94 |
95 | if (opts[t].data.length == 1) {
96 | if (opts[t].fillColor) opts[t].colors = opts[t].fillColor;
97 | else if (staticOpts.fillColor) opts[t].colors = staticOpts.fillColor;
98 | else if (l.opts[t] && l.opts[t].fillColor) opts[t].colors = l.opts[t].fillColor;
99 | else opts[t].colors = l.opts[0].fillColor;
100 | } else if (colorPalette) {
101 | opts[t].colors = colorPalette;
102 | } else {
103 | opts[t].colors = l.colorPalette;
104 | }
105 |
106 | if (maxValues) opts[t].maxValues = maxValues;
107 | }
108 |
109 | l.opts = opts;
110 | l.setOptions(utils.getInitOptions(opts, staticOpts, timeId));
111 | if (onChange) {
112 | l.onChange = eval(onChange);
113 | }
114 |
115 | var popup = utils.setPopup(l, timeId);
116 | if (l.onChange) l.onChange(utils.getInitOptions(opts, staticOpts, timeId), popup, d3);
117 |
118 | });
119 | };
120 |
121 | utils.addRemoveMethods("Minichart", "minichart");
122 | }());
123 |
--------------------------------------------------------------------------------
/javascript/timeslider.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | 'use strict';
3 |
4 | L.TimeSlider = L.Control.extend({
5 | options: {
6 | position: "bottomright",
7 | timeLabels: null,
8 | interval: 1000,
9 | onTimeIdChange: function(timeId) {console.log(timeId)}
10 | },
11 |
12 | initialize: function(options) {
13 | L.Control.prototype.initialize.call(this, options);
14 |
15 | var container = L.DomUtil.create('div', "leaflet-bar leaflet-control");
16 | container.style.padding = "5px";
17 | container.style.backgroundColor = 'white';
18 |
19 | var label = L.DomUtil.create("p", "time-slider-label", container);
20 | var sliderContainer = L.DomUtil.create("div", "leaflet-control-slider", container);
21 |
22 | var slider = L.DomUtil.create("input", "time-slider", sliderContainer);
23 | slider.type = "range";
24 | slider.min = 0;
25 | slider.max = options.timeLabels.length - 1;
26 | slider.value = 0;
27 |
28 | var btn = L.DomUtil.create("i", "playpause fa fa-play", sliderContainer);
29 |
30 | this._container = container;
31 | this._slider = slider;
32 | this._play = false;
33 | this._btn = btn;
34 | this._label = label;
35 | },
36 |
37 | onAdd: function(map) {
38 | var self = this;
39 | self.map = map;
40 |
41 | L.DomEvent.disableClickPropagation(self._container);
42 | self._slider.onchange = function(e) {
43 | self.setTimeId(self.getTimeId(), true);
44 | self.updateGroup(function(map) {
45 | map.controls._controlsById.tslider.setTimeId(self.getTimeId());
46 | });
47 | };
48 | self._btn.onclick = function(e) {
49 | self.playPause(!self._play);
50 | self.updateGroup(function(map) {
51 | map.controls._controlsById.tslider.playPause(self._play);
52 | });
53 | };
54 |
55 | self.setTimeLabels(self.options.timeLabels);
56 |
57 | return self._container;
58 | },
59 |
60 | playPause: function(play) {
61 | var self = this;
62 | self._play = play;
63 | if(self._play) {
64 | self._btn.className = "playpause fa fa-pause";
65 | if (self.getTimeId() == self._slider.max) {
66 | self.setTimeId(0);
67 | }
68 |
69 | self._intervalId = setInterval(function() {
70 | self.setTimeId(self.getTimeId() + 1);
71 | if (self.getTimeId() == self._slider.max) {
72 | clearInterval(self._intervalId);
73 | self._play = false;
74 | self._btn.className = "playpause fa fa-play";
75 | }
76 | }, self.options.interval);
77 | } else {
78 | clearInterval(self._intervalId);
79 | self._btn.className = "playpause fa fa-play";
80 | }
81 | },
82 |
83 | setTimeId: function(timeId, skip) {
84 | var self = this;
85 | if (!skip) self._slider.value = timeId;
86 | self._label.innerHTML = self.options.timeLabels[timeId];
87 | self.options.onTimeIdChange(timeId);
88 | },
89 |
90 | getTimeId: function() {
91 | return parseInt(this._slider.value);
92 | },
93 |
94 | setTimeLabels: function(timeLabels) {
95 | if (timeLabels === null) return;
96 | var self = this;
97 |
98 | if (typeof timeLabels == "undefined") {
99 | timeLabels = ["undefined"];
100 | } else if (timeLabels.constructor != Array) {
101 | timeLabels = [timeLabels];
102 | }
103 |
104 | if (timeLabels.length < 2) {
105 | self._container.style.display = "none";
106 | } else {
107 | self._container.style.display = "block";
108 | }
109 |
110 | var currentTimeLabel = self.options.timeLabels[self.getTimeId()];
111 | self.options.timeLabels = timeLabels;
112 | var newTimeId = self.toTimeId(currentTimeLabel);
113 |
114 | self._slider.max = timeLabels.length - 1;
115 | self.setTimeId(newTimeId);
116 | },
117 |
118 | toTimeId: function(label) {
119 | if (typeof this.options.timeLabels == "undefined" ||
120 | this.options.timeLabels.constructor != Array) {
121 | return 0;
122 | }
123 |
124 | var timeId = this.options.timeLabels.indexOf(label);
125 | if (timeId == -1) timeId = 0;
126 | return timeId;
127 | },
128 |
129 | updateGroup: function(updateFun) {
130 | var self = this;
131 | if (self.map.syncGroup) {
132 | var syncMap;
133 | for (var i = 0; i < LeafletWidget.syncGroups[self.map.syncGroup].length; i++) {
134 | syncMap = LeafletWidget.syncGroups[self.map.syncGroup][i];
135 | if (syncMap != self.map && syncMap.controls._controlsById.tslider) {
136 | updateFun(syncMap);
137 | }
138 | }
139 | }
140 | }
141 | });
142 |
143 | L.timeSlider = function(options) {
144 | return new L.TimeSlider(options);
145 | };
146 | }());
147 |
--------------------------------------------------------------------------------
/R/add_flows.R:
--------------------------------------------------------------------------------
1 | #' Add or modify flows on a leaflet map
2 | #'
3 | #' These functions can be used to represent flows and their evolution on a map
4 | #' created with \code{\link[leaflet]{leaflet}()}. Flows are simply represented
5 | #' by a segment between two points with an arrow at its center that indicates the
6 | #' direction of the flow.
7 | #'
8 | #' @param lng0 Longitude of the origin of the flow.
9 | #' @param lat0 Latitude of the origin of the flow.
10 | #' @param lng1 Longitude of the destination of the flow.
11 | #' @param lat1 Latitude of the destination of the flow.
12 | #' @param color Color of the flow.
13 | #' @param flow Value of the flow between the origin and the destination. If
14 | #' argument \code{dir} is not set, negative values are interpreted as flows
15 | #' from destination to origin.
16 | #' @param opacity Opacity of the flow.
17 | #' @param dir Direction of the flow. 1 indicates that the flow goes from origin
18 | #' to destination and -1 indicates that it goes from destination to origin. If
19 | #' 0, the arrow is not drawn. If \code{NULL}, then it is equal to the sign of
20 | #' \code{weight}.
21 | #' @param maxFlow Maximal value a flow could take.
22 | #' @param minThickness minimal thickness of the line that represents the flow.
23 | #' @param maxThickness maximal thickness of the line that represents the flow.
24 | #' @inheritParams addMinicharts
25 | #'
26 | #' @return
27 | #' The modified leaflet map object.
28 | #' @examples
29 | #'
30 | #' require(leaflet)
31 | #'
32 | #' # Toy example
33 | #' leaflet() %>% addTiles() %>%
34 | #' addFlows(0, 0, 1, 1, flow = 10)
35 | #'
36 | #' # Electric exchanges between France and neighboring countries
37 | #' data("eco2mixBalance")
38 | #' bal <- eco2mixBalance
39 | #' leaflet() %>% addTiles() %>%
40 | #' addFlows(
41 | #' bal$lng0, bal$lat0, bal$lng1, bal$lat1,
42 | #' flow = bal$balance,
43 | #' time = bal$month
44 | #' )
45 | #'
46 | #' # popupOptions
47 | #' data("eco2mixBalance")
48 | #' bal <- eco2mixBalance
49 | #' leaflet() %>% addTiles() %>%
50 | #' addFlows(
51 | #' bal$lng0, bal$lat0, bal$lng1, bal$lat1,
52 | #' flow = bal$balance,
53 | #' time = bal$month,
54 | #' popupOptions = list(closeOnClick = FALSE, autoClose = FALSE)
55 | #' )
56 | #'
57 | #' @export
58 | addFlows <- function(map, lng0, lat0, lng1, lat1, color = "blue", flow = 1,
59 | opacity = 1, dir = NULL, time = NULL, popup = popupArgs(labels = "Flow"),
60 | layerId = NULL,
61 | timeFormat = NULL, initialTime = NULL, maxFlow = max(abs(flow)),
62 | minThickness = 1, maxThickness = 20, popupOptions = NULL) {
63 | if (is.null(time)) time <- 1
64 | if (is.null(layerId)) layerId <- sprintf("_flow (%s,%s) -> (%s,%s)", lng0, lat0, lng1, lat1)
65 |
66 | options <- .preprocessArgs(
67 | required = list(lng0 = lng0, lat0 = lat0, lng1 = lng1, lat1 = lat1, layerId = layerId, time = time),
68 | optional = list(dir = dir, color = color, value = flow, maxValue = maxFlow,
69 | minThickness = minThickness, maxThickness = maxThickness,
70 | opacity = opacity)
71 | )
72 |
73 | args <- .prepareJSArgs(options, NULL, popup,
74 | initialTime = initialTime, timeFormat = timeFormat)
75 |
76 | # Add minichart and font-awesome to the map dependencies
77 | map$dependencies <- c(map$dependencies, minichartDeps())
78 |
79 | invokeMethod(map, data = leaflet::getMapData(map), "addFlows", args$options,
80 | args$timeLabels, args$initialTime, args$popupArgs, popupOptions) %>%
81 | expandLimits(c(lat0, lat1), c(lng0, lng1))
82 | }
83 |
84 | #' @rdname addFlows
85 | #' @export
86 | updateFlows <- function(map, layerId, color = NULL, flow = NULL, opacity = NULL,
87 | dir = NULL, time = NULL, popup = NULL,
88 | timeFormat = NULL, initialTime = NULL, maxFlow = NULL,
89 | minThickness = 1, maxThickness = 20, popupOptions = NULL) {
90 | if (is.null(time)) time <- 1
91 |
92 | options <- .preprocessArgs(
93 | required = list(layerId = layerId, time = time),
94 | optional = list(dir = dir, color = color, value = flow, maxValue = maxFlow,
95 | minThickness = minThickness, maxThickness = maxThickness,
96 | opacity = opacity)
97 | )
98 |
99 | args <- .prepareJSArgs(options, NULL, popup,
100 | initialTime = initialTime, timeFormat = timeFormat)
101 |
102 | if(is.null(flow)) {
103 | args$timeLabels <- NULL
104 | }
105 |
106 | invokeMethod(map, data = leaflet::getMapData(map), "updateFlows", args$options,
107 | args$timeLabels, args$initialTime, args$popupArgs, popupOptions)
108 | }
109 |
110 | #' @rdname addFlows
111 | #' @export
112 | removeFlows <- function(map, layerId) {
113 | invokeMethod(map, leaflet::getMapData(map), "removeFlows", layerId)
114 | }
115 |
116 | #' @rdname addFlows
117 | #' @export
118 | clearFlows <- function(map) {
119 | invokeMethod(map, leaflet::getMapData(map), "clearFlows") %>%
120 | leaflet::removeControl("minichartsLegend")
121 | }
122 |
--------------------------------------------------------------------------------
/tests/testthat/test-minicharts.R:
--------------------------------------------------------------------------------
1 | # context("minichart")
2 | #
3 | # # Helper function
4 | # expect_invoke_js_method <- function(method, postProcess = I) {
5 | # map <- leaflet::leaflet() %>%
6 | # addMinicharts(0, 0, 1:3, layerId = "a") %>%
7 | # postProcess()
8 | #
9 | # expect_true(all(method %in% map$jsmethods))
10 | # }
11 | #
12 | # with_mock(
13 | # `leaflet::invokeMethod` = function(map, data, method, ...) {
14 | # map$jsargs <- list(...)
15 | # map$jsmethods <- c(map$jsmethods, method)
16 | # map
17 | # },
18 | # `leaflet::addLegend` = function(map, ...) {
19 | # map$legendArgs <- list(...)
20 | # map
21 | # },
22 | # `leaflet::removeControl` = function(map, id) {
23 | # map$controlRemoved <- id
24 | # map
25 | # },
26 | # {
27 | #
28 | # test_that("One can add, update, clear and remove minicharts", {
29 | # expect_invoke_js_method("addMinicharts")
30 | # expect_invoke_js_method("updateMinicharts", function(map) {
31 | # updateMinicharts(map, layerId = "a", chartdata = 1:4)
32 | # })
33 | # expect_invoke_js_method("clearMinicharts", clearMinicharts)
34 | # expect_invoke_js_method("removeMinicharts", function(map) {
35 | # removeMinicharts(map, "a")
36 | # })
37 | # })
38 | #
39 | # describe("addMinicharts", {
40 | # it("correctly manages the 'labels' argument", {
41 | # # No labels
42 | # map <- leaflet::leaflet() %>% addMinicharts(0, 0, 1:3, layerId = "a")
43 | # expect_equal(map$jsargs[[1]][[1]]$static$labels, "none")
44 | # # Auto labels
45 | # map <- leaflet::leaflet() %>%
46 | # addMinicharts(0, 0, 1:3, layerId = "a", showLabels = TRUE)
47 | # expect_equal(map$jsargs[[1]][[1]]$static$labels, "auto")
48 | # # Custom labels
49 | # map <- leaflet::leaflet() %>%
50 | # addMinicharts(0, 0, 1, layerId = "a", showLabels = TRUE,
51 | # labelText = "test")
52 | # expect_equal(map$jsargs[[1]][[1]]$static$labels, "test")
53 | # })
54 | #
55 | # it("adds a legend when chartdata has multiple named columns", {
56 | # map <- leaflet::leaflet() %>%
57 | # addMinicharts(0, 0, data.frame(a = 1, b = 2), layerId = "a")
58 | # expect_false(is.null(map$legendArgs))
59 | # expect_equal(map$legendArgs$labels, I(c("a", "b")))
60 | # # No names, no legend
61 | # map <- leaflet::leaflet() %>% addMinicharts(0, 0, 1:3, layerId = "a")
62 | # expect_null(map$legendArgs)
63 | # # One column, no legend
64 | # map <- leaflet::leaflet() %>%
65 | # addMinicharts(0, 0, data.frame(a = 1), layerId = "a")
66 | # expect_null(map$legendArgs)
67 | # # Argument legend is FALSE
68 | # map <- leaflet::leaflet() %>%
69 | # addMinicharts(0, 0, data.frame(a = 1, b = 2), layerId = "a",
70 | # legend = FALSE)
71 | # expect_null(map$legendArgs)
72 | # })
73 | # })
74 | #
75 | # describe("updateMinicharts", {
76 | # basemap <- leaflet::leaflet() %>% addMinicharts(0, 0, 1)
77 | #
78 | # it("correctly manages the 'labels' argument", {
79 | # # No labels
80 | # map <- basemap %>% updateMinicharts("a", showLabels = FALSE)
81 | # expect_equal(map$jsargs[[1]][[1]]$static$labels, "none")
82 | # # Auto labels
83 | # map <- basemap %>% updateMinicharts("a", showLabels = TRUE)
84 | # expect_equal(map$jsargs[[1]][[1]]$static$labels, "auto")
85 | # # Custom labels
86 | # map <- basemap %>%
87 | # updateMinicharts("a", showLabels = TRUE, labelText = "test")
88 | # expect_equal(map$jsargs[[1]][[1]]$static$labels, "test")
89 | # })
90 | #
91 | # it ("does not update labels by default", {
92 | # map <- basemap %>% updateMinicharts("a")
93 | # expect_null(map$jsargs[[1]][[1]]$static$labels)
94 | # })
95 | #
96 | # it ("updates time slider only when data is updated", {
97 | # map <- basemap %>% updateMinicharts("a")
98 | # expect_null(map$jsargs[[5]])
99 | #
100 | # map <- basemap %>% updateMinicharts("a", chartdata = 2)
101 | # expect_false(is.null(map$jsargs[[5]]))
102 | # })
103 | #
104 | # it ("updates legend only when data is updated", {
105 | # baseMap <- leaflet::leaflet() %>% addMinicharts(0, 0, 1, layerId = "a")
106 | #
107 | # map <- baseMap %>% updateMinicharts("a", data.frame(a = 1, b = 2))
108 | # expect_false(is.null(map$legendArgs))
109 | # expect_equal(map$legendArgs$labels, I(c("a", "b")))
110 | # # No names, no legend
111 | # map <- baseMap %>% updateMinicharts("a", 1:3)
112 | # expect_null(map$legendArgs)
113 | # expect_equal(map$controlRemoved, "minichartsLegend")
114 | # # One column, no legend
115 | # map <- baseMap %>% updateMinicharts("a", data.frame(a = 1))
116 | # expect_null(map$legendArgs)
117 | # expect_equal(map$controlRemoved, "minichartsLegend")
118 | # # Argument legend is FALSE
119 | # map <- baseMap %>% updateMinicharts("a", data.frame(a = 1, b = 2), legend = FALSE)
120 | # expect_null(map$legendArgs)
121 | # expect_equal(map$controlRemoved, "minichartsLegend")
122 | #
123 | # # data is not updated
124 | # map <- baseMap %>% updateMinicharts("a")
125 | # expect_null(map$legendArgs)
126 | # expect_null(map$controlRemoved)
127 | # })
128 | #
129 | # })
130 | #
131 | # }
132 | # )
133 |
--------------------------------------------------------------------------------
/javascript/utils.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | 'use strict';
3 |
4 | var d3 = require("d3");
5 |
6 | module.exports.initTimeSlider = initTimeSlider;
7 | module.exports.processOptions = processOptions;
8 | module.exports.addSetTimeIdMethod = addSetTimeIdMethod;
9 | module.exports.addRemoveMethods = addRemoveMethods;
10 | module.exports.getInitOptions = getInitOptions;
11 | module.exports.setPopup = setPopup;
12 |
13 | // Add a time slider if it does not already exist
14 | function initTimeSlider(map, timeLabels, initialTime) {
15 | var tslider;
16 |
17 | if (!map.controls._controlsById.tslider) {
18 | tslider = L.timeSlider({
19 | timeLabels: timeLabels,
20 | onTimeIdChange: function(timeId) {
21 | var types = ["minichart", "flow"];
22 | for (var i = 0; i < types.length; i++) {
23 | var layers = map.layerManager._byCategory[types[i]];
24 | for (var k in layers) {
25 | if (layers[k]) layers[k].setTimeId(timeId);
26 | }
27 | }
28 | }
29 | });
30 | map.controls.add(tslider, "tslider");
31 | } else {
32 | tslider = map.controls._controlsById.tslider;
33 | if (typeof timeLabels != "undefined") tslider.setTimeLabels(timeLabels);
34 | }
35 |
36 | var timeId;
37 | if (typeof initialTime != "undefined" && initialTime !== null) {
38 | timeId = tslider.toTimeId(initialTime);
39 | } else {
40 | timeId = tslider.getTimeId();
41 | }
42 | tslider.setTimeId(timeId);
43 |
44 | return timeId;
45 | }
46 |
47 | /* Convert options sent by R in a useful format for the javascript code.
48 | Args:
49 | - options: object sent by R
50 | - callback: function(opts, i) with 'opts' the options for a given layer
51 | and 'i' the index of the given layer
52 | */
53 | function processOptions(options, callback) {
54 | for (var i = 0; i < options.length; i++) {
55 | var opts = [];
56 | var timesteps = getNumberOfTimesteps(options);
57 |
58 | for (var t = 0; t < timesteps; t++) {
59 | var opt = {};
60 | for (var k in options[i].dyn) {
61 | if (options[i].dyn.hasOwnProperty(k)) {
62 | opt[k] = options[i].dyn[k][t];
63 | }
64 | }
65 |
66 | opts.push(opt);
67 | }
68 |
69 | callback(opts, i, options[i].static);
70 | }
71 | }
72 |
73 | function getNumberOfTimesteps(options) {
74 | return options[0].timeSteps;
75 | }
76 |
77 | function getInitOptions(opts, staticOpts, timeId) {
78 | var opt = opts[timeId];
79 | for (var k in opt) {
80 | if (opt.hasOwnProperty(k)) {
81 | staticOpts[k] = opt[k];
82 | }
83 | }
84 | return staticOpts;
85 | }
86 |
87 | // Add to a leaflet class the method "setTimeId" to update a layer when timeId
88 | // changes.
89 | function addSetTimeIdMethod(className, updateFunName) {
90 | if (!L[className].prototype.setTimeId) {
91 | L[className].prototype.setTimeId = function(timeId) {
92 | if (timeId == this.timeId) return;
93 |
94 | if (typeof this.opts !== "undefined" && typeof this.opts[timeId] !== 'undefined') {
95 | var opt = this.opts[timeId];
96 | this[updateFunName](opt);
97 | var popup = setPopup(this, timeId);
98 | if (this.onChange) this.onChange(opt, popup, d3);
99 | }
100 | this.timeId = timeId;
101 | };
102 | }
103 | }
104 |
105 | function addRemoveMethods(className, groupName) {
106 | LeafletWidget.methods["remove" + className + "s"] = function(layerId) {
107 | if (layerId.constructor != Array) layerId = [layerId];
108 | for (var i = 0; i < layerId.length; i++) {
109 | this.layerManager.removeLayer(groupName, layerId[i]);
110 | }
111 | };
112 |
113 | LeafletWidget.methods["clear" + className + "s"] = function() {
114 | this.layerManager.clearLayers(groupName);
115 | };
116 | }
117 |
118 | function setPopup(l, timeId) {
119 | if (l.popupArgs.noPopup) return;
120 |
121 | if (l.opts[timeId].popupHTML) {
122 | if(l.isPopupOpen() === false){
123 | l.bindPopup(l.opts[timeId].popupHTML, l.popupOptions);
124 | } else {
125 | l.setPopupContent(l.opts[timeId].popupHTML);
126 | }
127 | /* l.bindPopup(l.opts[timeId].popupHTML); */
128 | return;
129 | }
130 |
131 | var title, content, popup;
132 | if (l.layerId && l.popupArgs.showTitle) title = "
" + l.layerId + "
";
133 | else title = "";
134 | content = "";
135 | if (l.opts[timeId].data) {
136 | var values, keys;
137 | if(l.popupArgs.showValues) {
138 | values = l.opts[timeId].data;
139 | keys = l.popupArgs.labels.concat(l.popupArgs.supLabels);
140 | } else {
141 | values = [];
142 | keys = l.popupArgs.supLabels;
143 | }
144 |
145 | if (l.opts[timeId].popupData) values = values.concat(l.opts[timeId].popupData);
146 |
147 | if (keys.length == 0) {
148 | content = values.join(", ");
149 | } else {
150 | var rows = [];
151 | for (var i = 0; i < values.length; i++) {
152 | var row = "";
153 | if (l.popupArgs.digits && isNumeric(values[i])) {
154 | values[i] = parseFloat(parseFloat(values[i]).toFixed(l.popupArgs.digits));
155 | }
156 | row += "" + keys[i] + " | ";
157 | row += "" + values[i] + " | ";
158 | row = "" + row + "
";
159 | rows.push(row);
160 | }
161 | content = rows.join("");
162 | content = '';
163 | }
164 | }
165 |
166 | popup = '';
167 | /* l.bindPopup(popup); */
168 | if(l.isPopupOpen() === false){
169 | l.bindPopup(popup, l.popupOptions);
170 | } else {
171 | l.setPopupContent(popup);
172 | }
173 | return popup;
174 | }
175 |
176 | function isNumeric(n) {
177 | return !isNaN(parseFloat(n)) && isFinite(n);
178 | }
179 | }());
180 |
--------------------------------------------------------------------------------
/R/prepare_js_args.R:
--------------------------------------------------------------------------------
1 | #' Prepare arguments before sending them to the javascript program.
2 | #'
3 | #' Globally the function reorder data by layerId and time and then it split it
4 | #' by layerId. It also compute some useful values like labels for legend,
5 | #' domain of data, number of data columns.
6 | #'
7 | #' @param options Object created with .preprocessArgs()
8 | #' @param chartdata Data to represent with minicharts
9 | #' @param popupArgs object created with function popupArgs()
10 | #' @param onChange see addMinicharts()
11 | #'
12 | #' @return
13 | #' A list with the following elements:
14 | #' - options: graphical options, split by layerId. Each element contains
15 | #' elements "dyn" (data.frame with values that vary with time), "static"
16 | #' and "timeSteps" (number of time steps)
17 | #' - chartdata: numeric matrix split by layerId
18 | #' - maxValues: maximal value observed in chartdata (NULL if chartdata is NULL)
19 | #' - ncols: number of columns in chartdata
20 | #' - popupArgs: List containing additional data to display in popups (or the
21 | #' popup HTML) split by layerId
22 | #' - legendLab: Labels to use in legends
23 | #' - onChange: Javascript function that will be executed when charts are
24 | #' updated
25 | #'
26 | #' @noRd
27 | #'
28 | .prepareJSArgs <- function(options, chartdata = NULL, popupArgs = NULL,
29 | onChange = NULL, timeFormat = NULL,
30 | initialTime = NULL) {
31 |
32 | static <- c("layerId", "lat", "lat0", "lat1", "lng", "lng0", "lng1")
33 |
34 | staticOptions <- options$staticOptions
35 | options <- options$options
36 | time <- options$time
37 |
38 | correctOrder <- order(options$layerId, options$time)
39 |
40 | options <- options[correctOrder, ]
41 |
42 | if (is.null(chartdata)) {
43 | legendLab <- NULL
44 | maxValues <- NULL
45 | ncols <- 0
46 | } else {
47 | # When adding only one minichart, chartdata can be a vector or a data frame, so it
48 | # needs to be converted to a matrix with correct lines and columns
49 | if (nrow(options) == 1 && is.vector(chartdata)) {
50 | chartdata <- matrix(chartdata, nrow = 1)
51 | } else {
52 | if (is.vector(chartdata)) {
53 | chartdata <- matrix(chartdata, ncol = 1, nrow = nrow(options))
54 | }
55 | }
56 |
57 | # Save column names for legend and transform data in a matrix without names
58 | if (!is.null(popupArgs)) {
59 | if (is.null(popupArgs$labels)) popupArgs$labels <- colnames(chartdata)
60 | legendLab <- popupArgs$labels
61 | } else {
62 | legendLab <- colnames(chartdata)
63 | }
64 | chartdata <- unname(as.matrix(chartdata))
65 |
66 | # Save additional information about data before splitting it
67 | maxValues <- max(abs(chartdata))
68 | ncols <- ncol(chartdata)
69 |
70 | # sort data and split it by layer
71 | chartdata <- chartdata[correctOrder, ] %>%
72 | split(options$layerId, drop = TRUE) %>%
73 | lapply(matrix, ncol = ncols) %>%
74 | unname()
75 | }
76 |
77 | # Popup additional data
78 | if (!is.null(popupArgs$supValues)) {
79 | if (is.null(popupArgs$supLabels)) popupArgs$supLabels <- colnames(popupArgs$supValues)
80 |
81 | if (is.null(popupArgs$supLabels) && !is.null(popupArgs$labels)) {
82 | popupArgs$supLabels <- rep("", ncol(popupArgs$supValues))
83 | } else if (is.null(popupArgs$labels) && !is.null(popupArgs$supLabels)) {
84 | popupArgs$labels <-rep("", ncols)
85 | }
86 |
87 | popupArgs$supValues <- popupArgs$supValues[correctOrder, ] %>%
88 | as.matrix() %>%
89 | split(options$layerId, drop = TRUE) %>%
90 | lapply(matrix, ncol = ncol(popupArgs$supValues)) %>%
91 | unname()
92 | }
93 |
94 | # Popup html
95 | if (!is.null(popupArgs$html)) {
96 | popupArgs$html <- popupArgs$html[correctOrder] %>%
97 | as.character() %>%
98 | split(options$layerId, drop = TRUE) %>%
99 | lapply(.I) %>%
100 | unname()
101 | }
102 |
103 |
104 | # If there is only one variable in chartdata, we draw circles with different radius
105 | # else we draw bar charts by default.
106 | if ("type" %in% names(staticOptions) && staticOptions$type == "auto") {
107 | staticOptions$type <- ifelse (!is.null(ncols) && ncols == 1, "polar-area", "bar")
108 | }
109 |
110 | # Ensure layerId is a character vector
111 | if ("layerId" %in% names(options)) options$layerId <- as.character(options$layerId)
112 |
113 | # Finally split options by layer
114 | options <- split(options, options$layerId, drop = TRUE) %>%
115 | unname() %>%
116 | lapply(function(df) {
117 | df$time <- NULL
118 | res <- list(dyn = df, static = list(), timeSteps = nrow(df))
119 | # Add common static options
120 | for (var in names(staticOptions)) {
121 | res$static[[var]] <- staticOptions[[var]]
122 | }
123 | # Add individual static options
124 | for (var in static) {
125 | if (var %in% names(df)) {
126 | res$dyn[[var]] <- NULL
127 | res$static[[var]] <- df[[var]][1]
128 | }
129 | }
130 | res
131 | })
132 |
133 | # Ensure labels will always be translated as arrays in JSON
134 | if(!is.null(popupArgs)) {
135 | popupArgs$labels <- .I(popupArgs$labels)
136 | popupArgs$supLabels <- .I(popupArgs$supLabels)
137 | }
138 |
139 | # Prepare onChange argument
140 | if (!is.null(onChange)) {
141 | onChange <- sprintf("(function(opts, popup, d3){%s})", onChange)
142 | onChange <- JS(onChange)
143 | }
144 |
145 | # Prepare time labels
146 | timeLabels <- sort(unique(time))
147 | if (!is.null(timeFormat)) {
148 | timeLabels <- format(timeLabels, format = timeFormat)
149 | if (!is.null(initialTime)) {
150 | initialTime <- format(initialTime, format = timeFormat)
151 | }
152 | }
153 | timeLabels <- as.character(timeLabels)
154 | if (!is.null(initialTime)) initialTime <- as.character(initialTime)
155 |
156 | list(
157 | options = options,
158 | chartdata = chartdata,
159 | maxValues = maxValues,
160 | ncols = ncols,
161 | popupArgs = popupArgs,
162 | legendLab = .I(legendLab),
163 | onChange = onChange,
164 | timeLabels = .I(timeLabels),
165 | initialTime = initialTime
166 | )
167 | }
168 |
169 |
170 | .I <- function(x) {
171 | if(is.null(x)) x <- list()
172 | I(x)
173 | }
174 |
--------------------------------------------------------------------------------
/tests/testthat/test-prepare_js_args.R:
--------------------------------------------------------------------------------
1 | context(".prepareJSArgs")
2 |
3 | describe(".prepareJSArgs", {
4 |
5 | # Prepare some fake data
6 | nTimeIds <- 5
7 | layerIds <- c("a", "b")
8 | nLayers <- length(layerIds)
9 |
10 | mydata <- data.frame(
11 | a = rnorm(nLayers * nTimeIds),
12 | b = rnorm(nLayers * nTimeIds)
13 | )
14 |
15 | myOptions <- .preprocessArgs(
16 | list(
17 | lng = rep(1:nLayers, each = nTimeIds),
18 | lat = rep(1:nLayers, each = nTimeIds),
19 | layerId = rep(layerIds, each = nTimeIds),
20 | time = rep(1:nTimeIds, each = nLayers)
21 | ),
22 | list(
23 | width = 60,
24 | height = seq_len(nLayers * nTimeIds)
25 | )
26 | )
27 |
28 | jsArgs <- .prepareJSArgs(myOptions, mydata)
29 |
30 | # Helper functions
31 | expect_split_by_layer <- function(x, checkElement) {
32 | expect_is(x, "list")
33 | expect_equal(length(x), nLayers)
34 | for (el in x) {
35 | checkElement(el)
36 | }
37 | }
38 |
39 | it("returns an object with the correct structure", {
40 | elements <- c("options", "chartdata", "maxValues", "ncols", "legendLab",
41 | "onChange", "timeLabels", "initialTime")
42 | expect_true(all(elements %in% names(jsArgs)))
43 | expect_is(jsArgs$legendLab, "AsIs")
44 | expect_equal(jsArgs$legendLab, I(colnames(mydata)))
45 | expect_equal(jsArgs$maxValues, max(abs(mydata)))
46 | expect_equal(jsArgs$ncols, ncol(mydata))
47 | expect_is(jsArgs$timeLabels, "AsIs")
48 |
49 | expect_split_by_layer(jsArgs$options, function(el) {
50 | expect_is(el$dyn, "data.frame")
51 | expect_equal(names(el$dyn), "height")
52 | expect_equal(nrow(el$dyn), nTimeIds)
53 |
54 | expect_is(el$static, "list")
55 | expect_true(all(c("lng", "lat", "layerId", "width") %in% names(el$static)))
56 |
57 | expect_equal(el$timeSteps, nTimeIds)
58 | })
59 |
60 | expect_split_by_layer(jsArgs$chartdata, function(el) {
61 | expect_is(el, "matrix")
62 | expect_equal(mode(el), "numeric")
63 | expect_equal(dim(el), c(nTimeIds, ncol(mydata)))
64 | })
65 | })
66 |
67 | it ("handles case when chartdata is a vector", {
68 | it ("single timeId and layer", {
69 | singleOption <- list(options = myOptions$options[1,])
70 | myData <- 1:3
71 | jsArgs <- .prepareJSArgs(singleOption, myData)
72 | expect_equal(length(jsArgs$chartdata), 1)
73 | expect_equal(dim(jsArgs$chartdata[[1]]), c(1, length(myData)))
74 | })
75 |
76 | it ("single column", {
77 | myData <- 1:nrow(myOptions$options)
78 | jsArgs <- .prepareJSArgs(myOptions, myData)
79 | expect_split_by_layer(jsArgs$chartdata, function(el) {
80 | expect_is(el, "matrix")
81 | expect_equal(mode(el), "numeric")
82 | expect_equal(dim(el), c(nTimeIds, 1))
83 | })
84 | })
85 |
86 | it ("single value", {
87 | myData <- 1
88 | jsArgs <- .prepareJSArgs(myOptions, myData)
89 | expect_split_by_layer(jsArgs$chartdata, function(el) {
90 | expect_is(el, "matrix")
91 | expect_equal(mode(el), "numeric")
92 | expect_equal(dim(el), c(nTimeIds, 1))
93 | expect_true(all(el == myData))
94 | })
95 | })
96 | })
97 |
98 | it ("uses default values when chartdata is NULL", {
99 | expect_silent(jsArgs <- .prepareJSArgs(myOptions, NULL))
100 | expect_equal(jsArgs$legendLab, I(list()))
101 | expect_equal(jsArgs$maxValues, NULL)
102 | expect_equal(jsArgs$ncols, 0)
103 | })
104 |
105 | it ("chooses correct type when type = 'auto'", {
106 |
107 | it ("multiple columns", {
108 | myOptions$staticOptions$type <- "auto"
109 | jsArgs <- .prepareJSArgs(myOptions, mydata)
110 | expect_split_by_layer(jsArgs$options, function(el) {
111 | expect_equal(el$static$type, "bar")
112 | })
113 | })
114 |
115 | it ("single column", {
116 | myOptions$staticOptions$type <- "auto"
117 | jsArgs <- .prepareJSArgs(myOptions, mydata[, "a", drop = FALSE])
118 | expect_split_by_layer(jsArgs$options, function(el) {
119 | expect_equal(el$static$type, "polar-area")
120 | })
121 | })
122 |
123 |
124 | })
125 |
126 | it ("transforms 'onChange' in a javascript function", {
127 | jsArgs <- .prepareJSArgs(myOptions, onChange = "console.log('ok')")
128 | expect_is(jsArgs$onChange, "JS_EVAL")
129 | expect_true(grepl("^\\(function\\(.+\\) ?\\{.+\\}\\)", jsArgs$onChange))
130 | })
131 |
132 | it ("supports custom popups", {
133 |
134 | it ("custom html", {
135 | html <- rnorm(nLayers * nTimeIds)
136 |
137 | jsArgs <- .prepareJSArgs(myOptions, mydata, popupArgs(html = html))
138 | expect_split_by_layer(jsArgs$popupArgs$html, function(el) {
139 | expect_is(el, "AsIs")
140 | expect_equal(mode(el), "character")
141 | expect_equal(length(el), nTimeIds)
142 | })
143 | })
144 |
145 | it ("custom labels", {
146 | jsArgs <- .prepareJSArgs(myOptions, mydata, popupArgs(labels = c("c", "d")))
147 | expect_equal(jsArgs$legendLab, I(c("c", "d")))
148 | })
149 |
150 | it ("additional data", {
151 | popupData <- data.frame(
152 | c = rnorm(nLayers * nTimeIds),
153 | d = rnorm(nLayers * nTimeIds)
154 | )
155 |
156 | jsArgs <- .prepareJSArgs(myOptions, mydata,
157 | popupArgs(supValues = popupData))
158 |
159 | expect_split_by_layer(jsArgs$popupArgs$supValues, function(el) {
160 | expect_is(el, "matrix")
161 | expect_equal(mode(el), "numeric")
162 | expect_equal(dim(el), c(nTimeIds, ncol(mydata)))
163 | })
164 | expect_is(jsArgs$popupArgs$supLabels, "AsIs")
165 | expect_equal(jsArgs$popupArgs$supLabels, I(names(popupData)))
166 | })
167 | })
168 |
169 | it ("fills missing labels", {
170 | popupData <- data.frame(
171 | c = rnorm(nLayers * nTimeIds),
172 | d = rnorm(nLayers * nTimeIds)
173 | )
174 |
175 | jsArgs <- .prepareJSArgs(myOptions, mydata,
176 | popupArgs(supValues = unname(popupData)))
177 | expect_equal(jsArgs$popupArgs$labels, I(names(mydata)))
178 | expect_equal(jsArgs$popupArgs$supLabels, I(c("", "")))
179 |
180 | jsArgs <- .prepareJSArgs(myOptions, unname(mydata),
181 | popupArgs(supValues = popupData))
182 | expect_equal(jsArgs$popupArgs$labels, I(c("", "")))
183 | expect_equal(jsArgs$popupArgs$supLabels, I(names(popupData)))
184 | })
185 |
186 | it ("can use custom format for time labels", {
187 | myOptions$options$time <- Sys.Date() + myOptions$options$time
188 | format <- "%m - %d"
189 | expectedLabels <- format(unique(myOptions$options$time), format = format)
190 |
191 | jsArgs <- .prepareJSArgs(myOptions, mydata, timeFormat = format,
192 | initialTime = min(myOptions$options$time))
193 | expect_equal(jsArgs$timeLabels, I(expectedLabels))
194 | expect_equal(jsArgs$initialTime, expectedLabels[1])
195 | })
196 |
197 | it ("returns timeLabels and initialTimes in character format", {
198 | jsArgs <- .prepareJSArgs(myOptions, mydata,
199 | initialTime = min(myOptions$options$time))
200 | expect_equal(mode(jsArgs$timeLabels), "character")
201 | expect_is(jsArgs$initialTime, "character")
202 | })
203 | })
204 |
--------------------------------------------------------------------------------
/vignettes/introduction.Rmd:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Introduction to leaflet.minicharts"
3 | author: "Francois Guillem"
4 | date: "`r Sys.Date()`"
5 | output: rmarkdown::html_vignette
6 | vignette: >
7 | %\VignetteIndexEntry{introduction}
8 | %\VignetteEngine{knitr::rmarkdown}
9 | %\VignetteEncoding{UTF-8}
10 | ---
11 |
12 | For a few years now, it has become very to create interactive maps with R thanks to the package `leaflet` by the Rstudio team. Nevertheless, it only provides only a few functions to create basic shapes on a map, so the information that can be represented on a single map is limited: if you have some data associated to some points, you can only represent at most two variables by drawing circles and changing their radius and color according to data.
13 |
14 | `leaflet.minicharts` is an R package that provides two functions to add and update small charts on an interactive maps created with the package `leaflet`. These charts can be used to represent as many variables as desired associated to geographical points. Currently, three types of chart are supported: barcharts (the default), pie charts and polar area charts.
15 |
16 | let's have a look to a concrete example.
17 |
18 | ## Data
19 |
20 | The package provides a table that contains the electric production, consumption and exchanges of France from january 2010 to february 2017 and of 12 french regions from january 2013 to february 2017.
21 |
22 | In addition to the total production, the table contains one column for each type of production. The table also contains the latitude and longitude of the center of the regions.
23 |
24 | ```{r}
25 | library(leaflet.minicharts)
26 | data("eco2mix")
27 | head(eco2mix)
28 | ```
29 |
30 | ## Renewable productions in 2016
31 |
32 | Nowadays, France has an objective of 23% of renewable energies in the consumption of the country by 2020. Are the country close to its objective. Is the share of renewable energies similar in all regions?
33 |
34 | To answer this question let us focus on the year 2016 We first prepare the required data with package `dplyr`:
35 |
36 | ```{r message=FALSE}
37 | library(dplyr)
38 |
39 | prod2016 <- eco2mix %>%
40 | mutate(
41 | renewable = bioenergy + solar + wind + hydraulic,
42 | non_renewable = total - bioenergy - solar - wind - hydraulic
43 | ) %>%
44 | filter(grepl("2016", month) & area != "France") %>%
45 | select(-month) %>%
46 | group_by(area, lat, lng) %>%
47 | summarise_all(sum) %>%
48 | ungroup()
49 |
50 | head(prod2016)
51 | ```
52 |
53 | We also create a base map that will be used in all the following examples
54 |
55 | ```{r message=FALSE, results='hide'}
56 | library(leaflet)
57 |
58 | tilesURL <- "http://server.arcgisonline.com/ArcGIS/rest/services/Canvas/World_Light_Gray_Base/MapServer/tile/{z}/{y}/{x}"
59 |
60 | basemap <- leaflet(width = "100%", height = "400px") %>%
61 | addTiles(tilesURL)
62 | ```
63 |
64 | We now add to the base map a pie chart for each region that represents the share of renewable energies. We also change the width of the pie charts so their area is proportional to the total production of the corresponding region.
65 |
66 | ```{r}
67 | colors <- c("#4fc13c", "#cccccc")
68 |
69 | basemap %>%
70 | addMinicharts(
71 | prod2016$lng, prod2016$lat,
72 | type = "pie",
73 | chartdata = prod2016[, c("renewable", "non_renewable")],
74 | colorPalette = colors,
75 | width = 60 * sqrt(prod2016$total) / sqrt(max(prod2016$total)), transitionTime = 0
76 | )
77 | ```
78 |
79 | We can see that the three south east regions exceed the target of 23%, but most regions are far from this objective. Globally, renewable energies represented only 19% percent of the production of 2016.
80 |
81 | Now let's represent the different types of renewable production using bar charts.
82 |
83 | ```{r}
84 | renewable2016 <- prod2016 %>% select(hydraulic, solar, wind)
85 | colors <- c("#3093e5", "#fcba50", "#a0d9e8")
86 | basemap %>%
87 | addMinicharts(
88 | prod2016$lng, prod2016$lat,
89 | chartdata = renewable2016,
90 | colorPalette = colors,
91 | width = 45, height = 45
92 | )
93 | ```
94 |
95 | Hydraulic production is far more important than solar and wind. Without surprise, solar production is more important in south while wind production is more important in the north.
96 |
97 | ## Representing a single variable
98 |
99 | `leaflet.minicharts` has been designed to represent multiple variables at once, but you still may want to use it to represent a single variable. In the next example, we represent the total load of each french region in 2016. When data passed to `addMinicharts` contains a single column, it automatically represents it with circle which area is proportional to the corresponding value. In the example we also use the parameter `showLabels` to display rounded values of the variable inside the circles.
100 |
101 | ```{r}
102 | basemap %>%
103 | addMinicharts(
104 | prod2016$lng, prod2016$lat,
105 | chartdata = prod2016$load,
106 | showLabels = TRUE,
107 | width = 45
108 | )
109 | ```
110 |
111 | This is nice, isn't it?
112 |
113 | ## Animated maps
114 |
115 | Until now, we have only represented aggregated data but it would be nice to create a map that represents the evolution over time of some variables. It is actually easy with `leaflet.minicharts`. The first step is to construct a table containing longitude, latitude, a time column and the variables we want to represent. The table `eco2mix` already has all these columns. We only need to filter the rows containing data for the entire country.
116 |
117 | ```{r}
118 | prodRegions <- eco2mix %>% filter(area != "France")
119 | ```
120 |
121 | Now we can create our animated map by using the argument "time":
122 |
123 | ```{r}
124 | basemap %>%
125 | addMinicharts(
126 | prodRegions$lng, prodRegions$lat,
127 | chartdata = prodRegions[, c("hydraulic", "solar", "wind")],
128 | time = prodRegions$month,
129 | colorPalette = colors,
130 | width = 45, height = 45
131 | )
132 | ```
133 |
134 | ## Represent flows
135 |
136 | Since version 0.2, `leaflet.minicharts` has also functions to represent flows between points and their evolution. To illustrate this, let's represent the evolution of electricity exchanges between France and Neighboring countries.
137 |
138 | To do that, we use function `addFlows`. It requires coordinates of two points for each flow and the value of the flow. Other arguments are similar to `addMinicharts`.
139 |
140 | ```{r}
141 | data("eco2mixBalance")
142 | bal <- eco2mixBalance
143 | basemap %>%
144 | addFlows(
145 | bal$lng0, bal$lat0, bal$lng1, bal$lat1,
146 | flow = bal$balance,
147 | time = bal$month
148 | )
149 | ```
150 |
151 | Of course, you can represent flows and minicharts on the same map!
152 |
153 | ## Use in shiny web applications
154 |
155 | In shiny applications, you can create nice transition effects by using functions `leafletproxy` and `updateMinicharts`/`updateFlows`. In the server function you first need to initialize the map and the minicharts. The important thing here is to use parameter `layerId` so that `updateMinicharts` can know which chart to update with which values.
156 |
157 | ```{r eval = FALSE}
158 | server <- function(input, output, session) {
159 | # Initialize map
160 | output$mymap <- renderLeaflet(
161 | leaflet() %>% addTiles() %>%
162 | addMinicharts(lon, lat, layerId = uniqueChartIds)
163 | )
164 | }
165 | ```
166 |
167 | Then use `leafletProxy()` and `updateMinicharts` in your reactive code:
168 |
169 | ```{r eval = FALSE}
170 | server <- function(input, output, session) {
171 | # Initialize map
172 | ...
173 |
174 | # Update map
175 | observe({
176 | newdata <- getData(input$myinput)
177 |
178 | leafletProxy("mymap") %>%
179 | updateMinicharts(uniqueChartIds, chartdata = newdata, ...)
180 | })
181 | }
182 | ```
183 |
184 | You can find a [live example here](https://francoisguillem.shinyapps.io/shiny-demo/).
185 |
--------------------------------------------------------------------------------
/man/addMinicharts.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/add_minicharts.R
3 | \name{addMinicharts}
4 | \alias{addMinicharts}
5 | \alias{updateMinicharts}
6 | \alias{removeMinicharts}
7 | \alias{clearMinicharts}
8 | \title{Add or update charts on a leaflet map}
9 | \usage{
10 | addMinicharts(
11 | map,
12 | lng,
13 | lat,
14 | chartdata = 1,
15 | time = NULL,
16 | maxValues = NULL,
17 | type = "auto",
18 | fillColor = d3.schemeCategory10[1],
19 | colorPalette = d3.schemeCategory10,
20 | width = 30,
21 | height = 30,
22 | opacity = 1,
23 | showLabels = FALSE,
24 | labelText = NULL,
25 | labelMinSize = 8,
26 | labelMaxSize = 24,
27 | labelStyle = NULL,
28 | transitionTime = 750,
29 | popup = popupArgs(),
30 | layerId = NULL,
31 | legend = TRUE,
32 | legendPosition = "topright",
33 | timeFormat = NULL,
34 | initialTime = NULL,
35 | onChange = NULL,
36 | popupOptions = NULL
37 | )
38 |
39 | updateMinicharts(
40 | map,
41 | layerId,
42 | chartdata = NULL,
43 | time = NULL,
44 | maxValues = NULL,
45 | type = NULL,
46 | fillColor = NULL,
47 | colorPalette = d3.schemeCategory10,
48 | width = NULL,
49 | height = NULL,
50 | opacity = NULL,
51 | showLabels = NULL,
52 | labelText = NULL,
53 | labelMinSize = NULL,
54 | labelMaxSize = NULL,
55 | labelStyle = NULL,
56 | transitionTime = NULL,
57 | popup = NULL,
58 | legend = TRUE,
59 | legendPosition = NULL,
60 | timeFormat = NULL,
61 | initialTime = NULL,
62 | onChange = NULL,
63 | popupOptions = NULL
64 | )
65 |
66 | removeMinicharts(map, layerId)
67 |
68 | clearMinicharts(map)
69 | }
70 | \arguments{
71 | \item{map}{A leaflet map object created with \code{\link[leaflet]{leaflet}}.}
72 |
73 | \item{lng}{Longitude where to place the charts.}
74 |
75 | \item{lat}{Latitude where to place the charts.}
76 |
77 | \item{chartdata}{A numeric matrix with number of rows equal to the number of
78 | elements in \code{lng} or \code{lat} and number of column equal to the
79 | number of variables to represent. If parameter \code{time} is set, the
80 | number of rows must be equal to the length of \code{lng} times the number
81 | of unique time steps in the data.}
82 |
83 | \item{time}{A vector with length equal to the number of rows in \code{chartdata}
84 | and containing either numbers representing time indices or dates or
85 | datetimes. Each unique value must appear as many times as the others. This
86 | parameter can be used when one wants to represent the evolution of some
87 | variables on a map.}
88 |
89 | \item{maxValues}{maximal absolute values of the variables to represent.
90 | It can be a vector with one value per column of \code{chartdata} or a single
91 | value. Using a single value enforces charts to use a unique scale for all
92 | variables. If it is \code{NULL}, the maximum value of \code{chartdata} is used.}
93 |
94 | \item{type}{Type of chart. Possible values are \code{"bar"} for bar charts,
95 | \code{"pie"} for pie charts, \code{"polar-area"} and \code{"polar-radius"}
96 | for polar area charts where the values are represented respectively by the
97 | area or the radius of the slices. Finally it can be equal to \code{"auto"},
98 | the default. In this case, if there is only one variable to represent, the
99 | chart will be a single circle, else it is a barchart.}
100 |
101 | \item{fillColor}{Used only if data contains only one column. It is the color
102 | used to fill the circles.}
103 |
104 | \item{colorPalette}{Color palette to use when \code{chartdata} contains more than
105 | one column.}
106 |
107 | \item{width}{maximal width of the created elements.}
108 |
109 | \item{height}{maximal height of the created elements.}
110 |
111 | \item{opacity}{Opacity of the chart.}
112 |
113 | \item{showLabels}{Should values be displayed above chart elements.}
114 |
115 | \item{labelText}{character vector containing the text content of the charts.
116 | Used only if \code{chartdata} contains only one column.}
117 |
118 | \item{labelMinSize}{Minimal height of labels in pixels. When there is not
119 | enough space for labels, they are hidden.}
120 |
121 | \item{labelMaxSize}{Maximal height of labels in pixels.}
122 |
123 | \item{labelStyle}{Character string containing CSS properties to apply to the
124 | labels.}
125 |
126 | \item{transitionTime}{Duration in milliseconds of the transitions when a
127 | property of a chart is updated.}
128 |
129 | \item{popup}{Options that control popup generation.}
130 |
131 | \item{layerId}{An ID variable. It is mandatory when one wants to update some
132 | chart with \code{updateMinicharts}.}
133 |
134 | \item{legend}{If TRUE and if data has column names, then a legend is
135 | automatically added to the map.}
136 |
137 | \item{legendPosition}{Where should legend be placed?}
138 |
139 | \item{timeFormat}{Character string used to format dates and times when
140 | argument \code{time} is a \code{Date}, \code{POSIXct} or \code{POSIXlt}
141 | object. See \code{\link[base]{strptime}} for more information.}
142 |
143 | \item{initialTime}{This argument can be used to set the initial time step
144 | shown when the map is created. It is used only when argument \code{time} is
145 | set.}
146 |
147 | \item{onChange}{(For power users who know javascript) A character string
148 | containing javascript code that is executed each time a chart is updated.
149 | See the details section to understand why and how to use this parameter.}
150 |
151 | \item{popupOptions}{Change default popupOptions (ex : autoClose, maxHeight, closeButton ...)
152 | See \code{\link[leaflet]{popupOptions}} for more informations.}
153 | }
154 | \value{
155 | The modified leaflet map object. \code{addMinicharts} add new minicharts to
156 | the map. \code{updateMinicharts} updates minicharts that have already been
157 | added to the map. \code{removeMinicharts} removes some specific charts from
158 | the map and \code{clearMinicharts} removes all charts from the map and
159 | if necessary the legend that has been automatically created.
160 | }
161 | \description{
162 | these functions add or update minicharts in a leaflet map at given coordinates:
163 | they can be bar charts, pie charts or polar charts where chartdata is encoded
164 | either by area or by radius.
165 | }
166 | \details{
167 | Since version 0.5, the parameter \code{onChange} can be used to execute
168 | some arbitrary javascript code each time a chart is updated (with
169 | \code{updateMinicharts()} or when time step changes). A typical use case
170 | would be to change the color of a polygon added with
171 | \code{\link[leaflet]{addPolygons}} based on the data of the chart. It is even
172 | possible to create an invisible chart and use it to manage the color and the
173 | popup of a polygon. Here is a sample code that do that:
174 |
175 | \preformatted{
176 | leaflet() \%>\% addTiles() \%>\%
177 | addPolygons(data = myPolygons, layerId = myPolygons$myIds) \%>\%
178 | addMinicharts(
179 | mydata$lon, mydata$lat,
180 | time = mydata$time
181 | fillColor = mydata$color,
182 | layerId = mydata$myIds,
183 | width = 0, height = 0,
184 | onChange = "
185 | var s = this._map.layerManager.getLayer("shape", this.layerId);
186 | s.bindPopup(popup);
187 | if (opts.fillColor) {
188 | d3.select(s._path)
189 | .transition()
190 | .duration(750)
191 | .attr("fill", opts.fillColor);
192 | }"
193 | )
194 |
195 | }
196 |
197 | The following objects are available when executing the javascript code:
198 | \describe{
199 | \item{this}{The current minichart object. See
200 | \url{https://rte-antares-rpackage.github.io/leaflet.minichart/-_L.Minichart_.html}
201 | for more information.
202 | }
203 | \item{opts}{The current options passed to the current minichart object.}
204 | \item{popup}{Popup html.}
205 | \item{d3}{The D3 module.}
206 | }
207 |
208 | Here is a toy example
209 | }
210 | \examples{
211 | require(leaflet)
212 | mymap <- leaflet() \%>\% addTiles() \%>\% addMinicharts(0, 0, chartdata = 1:3, layerId = "c1")
213 |
214 | mymap
215 | mymap \%>\% updateMinicharts("c1", maxValues = 6)
216 | mymap \%>\% updateMinicharts("c1", type="pie")
217 |
218 | # popupOptions
219 | mymap <- leaflet() \%>\% addTiles() \%>\%
220 | addMinicharts(0, 0, chartdata = 1:3, layerId = "c1", popupOptions = list(closeButton = FALSE))
221 |
222 | mymap
223 | mymap \%>\% updateMinicharts("c1", maxValues = 6, popupOptions = list(closeButton = TRUE))
224 |
225 | }
226 |
--------------------------------------------------------------------------------
/R/add_minicharts.R:
--------------------------------------------------------------------------------
1 | # Copyright © 2016 RTE Réseau de transport d’électricité
2 |
3 | #' Add or update charts on a leaflet map
4 | #'
5 | #' these functions add or update minicharts in a leaflet map at given coordinates:
6 | #' they can be bar charts, pie charts or polar charts where chartdata is encoded
7 | #' either by area or by radius.
8 | #'
9 | #' @param map A leaflet map object created with \code{\link[leaflet]{leaflet}}.
10 | #' @param lng Longitude where to place the charts.
11 | #' @param lat Latitude where to place the charts.
12 | #' @param chartdata A numeric matrix with number of rows equal to the number of
13 | #' elements in \code{lng} or \code{lat} and number of column equal to the
14 | #' number of variables to represent. If parameter \code{time} is set, the
15 | #' number of rows must be equal to the length of \code{lng} times the number
16 | #' of unique time steps in the data.
17 | #' @param time A vector with length equal to the number of rows in \code{chartdata}
18 | #' and containing either numbers representing time indices or dates or
19 | #' datetimes. Each unique value must appear as many times as the others. This
20 | #' parameter can be used when one wants to represent the evolution of some
21 | #' variables on a map.
22 | #' @param maxValues maximal absolute values of the variables to represent.
23 | #' It can be a vector with one value per column of \code{chartdata} or a single
24 | #' value. Using a single value enforces charts to use a unique scale for all
25 | #' variables. If it is \code{NULL}, the maximum value of \code{chartdata} is used.
26 | #' @param type Type of chart. Possible values are \code{"bar"} for bar charts,
27 | #' \code{"pie"} for pie charts, \code{"polar-area"} and \code{"polar-radius"}
28 | #' for polar area charts where the values are represented respectively by the
29 | #' area or the radius of the slices. Finally it can be equal to \code{"auto"},
30 | #' the default. In this case, if there is only one variable to represent, the
31 | #' chart will be a single circle, else it is a barchart.
32 | #' @param fillColor Used only if data contains only one column. It is the color
33 | #' used to fill the circles.
34 | #' @param colorPalette Color palette to use when \code{chartdata} contains more than
35 | #' one column.
36 | #' @param width maximal width of the created elements.
37 | #' @param height maximal height of the created elements.
38 | #' @param opacity Opacity of the chart.
39 | #' @param showLabels Should values be displayed above chart elements.
40 | #' @param labelText character vector containing the text content of the charts.
41 | #' Used only if \code{chartdata} contains only one column.
42 | #' @param labelStyle Character string containing CSS properties to apply to the
43 | #' labels.
44 | #' @param labelMinSize Minimal height of labels in pixels. When there is not
45 | #' enough space for labels, they are hidden.
46 | #' @param labelMaxSize Maximal height of labels in pixels.
47 | #' @param transitionTime Duration in milliseconds of the transitions when a
48 | #' property of a chart is updated.
49 | #' @param popup Options that control popup generation.
50 | #' @param layerId An ID variable. It is mandatory when one wants to update some
51 | #' chart with \code{updateMinicharts}.
52 | #' @param legend If TRUE and if data has column names, then a legend is
53 | #' automatically added to the map.
54 | #' @param legendPosition Where should legend be placed?
55 | #' @param timeFormat Character string used to format dates and times when
56 | #' argument \code{time} is a \code{Date}, \code{POSIXct} or \code{POSIXlt}
57 | #' object. See \code{\link[base]{strptime}} for more information.
58 | #' @param initialTime This argument can be used to set the initial time step
59 | #' shown when the map is created. It is used only when argument \code{time} is
60 | #' set.
61 | #' @param onChange (For power users who know javascript) A character string
62 | #' containing javascript code that is executed each time a chart is updated.
63 | #' See the details section to understand why and how to use this parameter.
64 | #' @param popupOptions Change default popupOptions (ex : autoClose, maxHeight, closeButton ...)
65 | #' See \code{\link[leaflet]{popupOptions}} for more informations.
66 | #'
67 | #' @details
68 | #' Since version 0.5, the parameter \code{onChange} can be used to execute
69 | #' some arbitrary javascript code each time a chart is updated (with
70 | #' \code{updateMinicharts()} or when time step changes). A typical use case
71 | #' would be to change the color of a polygon added with
72 | #' \code{\link[leaflet]{addPolygons}} based on the data of the chart. It is even
73 | #' possible to create an invisible chart and use it to manage the color and the
74 | #' popup of a polygon. Here is a sample code that do that:
75 | #'
76 | #' \preformatted{
77 | #' leaflet() \%>\% addTiles() \%>\%
78 | #' addPolygons(data = myPolygons, layerId = myPolygons$myIds) \%>\%
79 | #' addMinicharts(
80 | #' mydata$lon, mydata$lat,
81 | #' time = mydata$time
82 | #' fillColor = mydata$color,
83 | #' layerId = mydata$myIds,
84 | #' width = 0, height = 0,
85 | #' onChange = "
86 | #' var s = this._map.layerManager.getLayer("shape", this.layerId);
87 | #' s.bindPopup(popup);
88 | #' if (opts.fillColor) {
89 | #' d3.select(s._path)
90 | #' .transition()
91 | #' .duration(750)
92 | #' .attr("fill", opts.fillColor);
93 | #' }"
94 | #' )
95 | #'
96 | #' }
97 | #'
98 | #' The following objects are available when executing the javascript code:
99 | #' \describe{
100 | #' \item{this}{The current minichart object. See
101 | #' \url{https://rte-antares-rpackage.github.io/leaflet.minichart/-_L.Minichart_.html}
102 | #' for more information.
103 | #' }
104 | #' \item{opts}{The current options passed to the current minichart object.}
105 | #' \item{popup}{Popup html.}
106 | #' \item{d3}{The D3 module.}
107 | #' }
108 | #'
109 | #' Here is a toy example
110 | #'
111 | #'
112 | #' @return
113 | #' The modified leaflet map object. \code{addMinicharts} add new minicharts to
114 | #' the map. \code{updateMinicharts} updates minicharts that have already been
115 | #' added to the map. \code{removeMinicharts} removes some specific charts from
116 | #' the map and \code{clearMinicharts} removes all charts from the map and
117 | #' if necessary the legend that has been automatically created.
118 | #'
119 | #' @examples
120 | #' require(leaflet)
121 | #' mymap <- leaflet() %>% addTiles() %>% addMinicharts(0, 0, chartdata = 1:3, layerId = "c1")
122 | #'
123 | #' mymap
124 | #' mymap %>% updateMinicharts("c1", maxValues = 6)
125 | #' mymap %>% updateMinicharts("c1", type="pie")
126 | #'
127 | #' # popupOptions
128 | #' mymap <- leaflet() %>% addTiles() %>%
129 | #' addMinicharts(0, 0, chartdata = 1:3, layerId = "c1", popupOptions = list(closeButton = FALSE))
130 | #'
131 | #' mymap
132 | #' mymap %>% updateMinicharts("c1", maxValues = 6, popupOptions = list(closeButton = TRUE))
133 | #'
134 | #' @export
135 | #'
136 | addMinicharts <- function(map, lng, lat, chartdata = 1, time = NULL, maxValues = NULL, type = "auto",
137 | fillColor = d3.schemeCategory10[1], colorPalette = d3.schemeCategory10,
138 | width = 30, height = 30, opacity = 1, showLabels = FALSE,
139 | labelText = NULL, labelMinSize = 8, labelMaxSize = 24,
140 | labelStyle = NULL,
141 | transitionTime = 750,
142 | popup = popupArgs(),
143 | layerId = NULL, legend = TRUE, legendPosition = "topright",
144 | timeFormat = NULL, initialTime = NULL, onChange = NULL,
145 | popupOptions = NULL) {
146 | # Prepare options
147 | type <- match.arg(type, c("auto", "bar", "pie", "polar-area", "polar-radius"))
148 | if (is.null(layerId)) layerId <- sprintf("_minichart (%s,%s)", lng, lat)
149 | if (is.null(time)) time <- 1
150 | if (is.null(popup$labels)) popup$labels <- colnames(chartdata)
151 |
152 | if (showLabels) {
153 | if (!is.null(labelText)) labels <- labelText
154 | else labels <- "auto"
155 | } else {
156 | labels <- "none"
157 | }
158 |
159 | options <- .preprocessArgs(
160 | required = list(lng = lng, lat = lat, layerId = layerId, time = time),
161 | optional = list(type = type, width = width, height = height,
162 | opacity = opacity, labels = labels,
163 | labelMinSize = labelMinSize, labelMaxSize = labelMaxSize,
164 | labelStyle = labelStyle,
165 | transitionTime = transitionTime, fillColor = fillColor)
166 | )
167 |
168 | args <- .prepareJSArgs(options, chartdata, popup, onChange,
169 | initialTime = initialTime, timeFormat = timeFormat)
170 |
171 | if (is.null(maxValues)) maxValues <- args$maxValues
172 |
173 | # Add minichart and font-awesome to the map dependencies
174 | map$dependencies <- c(map$dependencies, minichartDeps())
175 |
176 | # control maxValues
177 | if(!is.null(maxValues)){
178 | if(!is.null(args$chartdata)){
179 | if(!(length(maxValues) == 1 | length(maxValues) == ncol(args$chartdata[[1]]))){
180 | stop("'maxValues' should be a single number or have same length as 'data'")
181 | }
182 | }
183 | maxValues <- unname(maxValues)
184 | maxValues[maxValues == 0 | is.na(maxValues) | is.infinite(maxValues)] <- 1
185 | }
186 |
187 | map <- invokeMethod(map, data = leaflet::getMapData(map), "addMinicharts",
188 | args$options, args$chartdata, maxValues, colorPalette,
189 | args$timeLabels, args$initialTime, args$popupArgs, args$onChange,
190 | popupOptions)
191 |
192 | if (legend && length(args$legendLab) > 0 && args$ncol > 1) {
193 | legendCol <- colorPalette[(seq_len(args$ncols)-1) %% args$ncols + 1]
194 | map <- addLegend(map, labels = args$legendLab, colors = legendCol, opacity = 1,
195 | layerId = "minichartsLegend", position = legendPosition)
196 | }
197 |
198 | map %>% expandLimits(lat, lng)
199 | }
200 |
201 | #' @export
202 | #' @rdname addMinicharts
203 | updateMinicharts <- function(map, layerId, chartdata = NULL, time = NULL, maxValues = NULL, type = NULL,
204 | fillColor = NULL, colorPalette = d3.schemeCategory10,
205 | width = NULL, height = NULL, opacity = NULL, showLabels = NULL,
206 | labelText = NULL, labelMinSize = NULL,
207 | labelMaxSize = NULL, labelStyle = NULL,
208 | transitionTime = NULL, popup = NULL,
209 | legend = TRUE, legendPosition = NULL,
210 | timeFormat = NULL, initialTime = NULL, onChange = NULL,
211 | popupOptions = NULL) {
212 |
213 | if (!is.null(type)) {
214 | type <- match.arg(type, c("auto", "bar", "pie", "polar-area", "polar-radius"))
215 | }
216 | if (is.null(time)) time <- 1
217 | if (!is.null(chartdata) & !is.null(popup) & is.null(popup$labels)) popup$labels <- colnames(chartdata)
218 |
219 | if (is.null(showLabels)) {
220 | labels <- NULL
221 | } else {
222 | if (showLabels) {
223 | if (!is.null(labelText)) labels <- labelText
224 | else labels <- "auto"
225 | } else {
226 | labels <- "none"
227 | }
228 | }
229 |
230 | options <- .preprocessArgs(
231 | required = list(layerId = layerId, time = time),
232 | optional = list(type = type, width = width, height = height,
233 | opacity = opacity, labels = labels,
234 | labelMinSize = labelMinSize, labelMaxSize = labelMaxSize,
235 | labelStyle = labelStyle,
236 | labelText = labelText, transitionTime = transitionTime,
237 | fillColor = fillColor)
238 | )
239 |
240 | args <- .prepareJSArgs(options, chartdata, popup, onChange,
241 | initialTime = initialTime, timeFormat = timeFormat)
242 |
243 | # Update time slider only if data is updated
244 | if(is.null(chartdata)) {
245 | args$timeLabels <- NULL
246 | }
247 |
248 | # Update legend if required
249 | if (!is.null(args$chartdata)) {
250 | if (legend && length(args$legendLab) > 0 && args$ncols > 1) {
251 | legendCol <- colorPalette[(seq_len(args$ncols)-1) %% args$ncols + 1]
252 | map <- addLegend(map, labels = args$legendLab, colors = legendCol, opacity = 1,
253 | layerId = "minichartsLegend", position = legendPosition)
254 | } else {
255 | map <- leaflet::removeControl(map, "minichartsLegend")
256 | }
257 | }
258 |
259 | # control maxValues
260 | if(!is.null(maxValues)){
261 | if(!is.null(args$chartdata)){
262 | if(!(length(maxValues) == 1 | length(maxValues) == ncol(args$chartdata[[1]]))){
263 | stop("'maxValues' should be a single number or have same length as 'data'")
264 | }
265 | }
266 | maxValues <- unname(maxValues)
267 | maxValues[maxValues == 0 | is.na(maxValues) | is.infinite(maxValues)] <- 1
268 | }
269 |
270 | map %>%
271 | invokeMethod(leaflet::getMapData(map), "updateMinicharts",
272 | args$options, args$chartdata, maxValues, colorPalette,
273 | args$timeLabels, args$initialTime, args$popupArgs,
274 | args$legendLab, args$onChange, popupOptions)
275 | }
276 |
277 | #' @rdname addMinicharts
278 | #' @export
279 | removeMinicharts <- function(map, layerId) {
280 | invokeMethod(map, leaflet::getMapData(map), "removeMinicharts", layerId)
281 | }
282 |
283 | #' @rdname addMinicharts
284 | #' @export
285 | clearMinicharts <- function(map) {
286 | invokeMethod(map, leaflet::getMapData(map), "clearMinicharts") %>%
287 | leaflet::removeControl("minichartsLegend")
288 | }
289 |
--------------------------------------------------------------------------------
/inst/font-awesome-4.7.0/css/font-awesome.min.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome
3 | * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License)
4 | */@font-face{font-family:'FontAwesome';src:url('../fonts/fontawesome-webfont.ttf?v=4.7.0') format('truetype');font-weight:normal;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left{margin-right:.3em}.fa.fa-pull-right{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-remove:before,.fa-close:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook-f:before,.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before,.fa-gratipay:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper-pp:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-resistance:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-y-combinator-square:before,.fa-yc-square:before,.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}.fa-buysellads:before{content:"\f20d"}.fa-connectdevelop:before{content:"\f20e"}.fa-dashcube:before{content:"\f210"}.fa-forumbee:before{content:"\f211"}.fa-leanpub:before{content:"\f212"}.fa-sellsy:before{content:"\f213"}.fa-shirtsinbulk:before{content:"\f214"}.fa-simplybuilt:before{content:"\f215"}.fa-skyatlas:before{content:"\f216"}.fa-cart-plus:before{content:"\f217"}.fa-cart-arrow-down:before{content:"\f218"}.fa-diamond:before{content:"\f219"}.fa-ship:before{content:"\f21a"}.fa-user-secret:before{content:"\f21b"}.fa-motorcycle:before{content:"\f21c"}.fa-street-view:before{content:"\f21d"}.fa-heartbeat:before{content:"\f21e"}.fa-venus:before{content:"\f221"}.fa-mars:before{content:"\f222"}.fa-mercury:before{content:"\f223"}.fa-intersex:before,.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-venus-double:before{content:"\f226"}.fa-mars-double:before{content:"\f227"}.fa-venus-mars:before{content:"\f228"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-neuter:before{content:"\f22c"}.fa-genderless:before{content:"\f22d"}.fa-facebook-official:before{content:"\f230"}.fa-pinterest-p:before{content:"\f231"}.fa-whatsapp:before{content:"\f232"}.fa-server:before{content:"\f233"}.fa-user-plus:before{content:"\f234"}.fa-user-times:before{content:"\f235"}.fa-hotel:before,.fa-bed:before{content:"\f236"}.fa-viacoin:before{content:"\f237"}.fa-train:before{content:"\f238"}.fa-subway:before{content:"\f239"}.fa-medium:before{content:"\f23a"}.fa-yc:before,.fa-y-combinator:before{content:"\f23b"}.fa-optin-monster:before{content:"\f23c"}.fa-opencart:before{content:"\f23d"}.fa-expeditedssl:before{content:"\f23e"}.fa-battery-4:before,.fa-battery:before,.fa-battery-full:before{content:"\f240"}.fa-battery-3:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-battery-2:before,.fa-battery-half:before{content:"\f242"}.fa-battery-1:before,.fa-battery-quarter:before{content:"\f243"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-mouse-pointer:before{content:"\f245"}.fa-i-cursor:before{content:"\f246"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-sticky-note:before{content:"\f249"}.fa-sticky-note-o:before{content:"\f24a"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-diners-club:before{content:"\f24c"}.fa-clone:before{content:"\f24d"}.fa-balance-scale:before{content:"\f24e"}.fa-hourglass-o:before{content:"\f250"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-hourglass:before{content:"\f254"}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:"\f255"}.fa-hand-stop-o:before,.fa-hand-paper-o:before{content:"\f256"}.fa-hand-scissors-o:before{content:"\f257"}.fa-hand-lizard-o:before{content:"\f258"}.fa-hand-spock-o:before{content:"\f259"}.fa-hand-pointer-o:before{content:"\f25a"}.fa-hand-peace-o:before{content:"\f25b"}.fa-trademark:before{content:"\f25c"}.fa-registered:before{content:"\f25d"}.fa-creative-commons:before{content:"\f25e"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-tripadvisor:before{content:"\f262"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-get-pocket:before{content:"\f265"}.fa-wikipedia-w:before{content:"\f266"}.fa-safari:before{content:"\f267"}.fa-chrome:before{content:"\f268"}.fa-firefox:before{content:"\f269"}.fa-opera:before{content:"\f26a"}.fa-internet-explorer:before{content:"\f26b"}.fa-tv:before,.fa-television:before{content:"\f26c"}.fa-contao:before{content:"\f26d"}.fa-500px:before{content:"\f26e"}.fa-amazon:before{content:"\f270"}.fa-calendar-plus-o:before{content:"\f271"}.fa-calendar-minus-o:before{content:"\f272"}.fa-calendar-times-o:before{content:"\f273"}.fa-calendar-check-o:before{content:"\f274"}.fa-industry:before{content:"\f275"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-map-o:before{content:"\f278"}.fa-map:before{content:"\f279"}.fa-commenting:before{content:"\f27a"}.fa-commenting-o:before{content:"\f27b"}.fa-houzz:before{content:"\f27c"}.fa-vimeo:before{content:"\f27d"}.fa-black-tie:before{content:"\f27e"}.fa-fonticons:before{content:"\f280"}.fa-reddit-alien:before{content:"\f281"}.fa-edge:before{content:"\f282"}.fa-credit-card-alt:before{content:"\f283"}.fa-codiepie:before{content:"\f284"}.fa-modx:before{content:"\f285"}.fa-fort-awesome:before{content:"\f286"}.fa-usb:before{content:"\f287"}.fa-product-hunt:before{content:"\f288"}.fa-mixcloud:before{content:"\f289"}.fa-scribd:before{content:"\f28a"}.fa-pause-circle:before{content:"\f28b"}.fa-pause-circle-o:before{content:"\f28c"}.fa-stop-circle:before{content:"\f28d"}.fa-stop-circle-o:before{content:"\f28e"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-hashtag:before{content:"\f292"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-percent:before{content:"\f295"}.fa-gitlab:before{content:"\f296"}.fa-wpbeginner:before{content:"\f297"}.fa-wpforms:before{content:"\f298"}.fa-envira:before{content:"\f299"}.fa-universal-access:before{content:"\f29a"}.fa-wheelchair-alt:before{content:"\f29b"}.fa-question-circle-o:before{content:"\f29c"}.fa-blind:before{content:"\f29d"}.fa-audio-description:before{content:"\f29e"}.fa-volume-control-phone:before{content:"\f2a0"}.fa-braille:before{content:"\f2a1"}.fa-assistive-listening-systems:before{content:"\f2a2"}.fa-asl-interpreting:before,.fa-american-sign-language-interpreting:before{content:"\f2a3"}.fa-deafness:before,.fa-hard-of-hearing:before,.fa-deaf:before{content:"\f2a4"}.fa-glide:before{content:"\f2a5"}.fa-glide-g:before{content:"\f2a6"}.fa-signing:before,.fa-sign-language:before{content:"\f2a7"}.fa-low-vision:before{content:"\f2a8"}.fa-viadeo:before{content:"\f2a9"}.fa-viadeo-square:before{content:"\f2aa"}.fa-snapchat:before{content:"\f2ab"}.fa-snapchat-ghost:before{content:"\f2ac"}.fa-snapchat-square:before{content:"\f2ad"}.fa-pied-piper:before{content:"\f2ae"}.fa-first-order:before{content:"\f2b0"}.fa-yoast:before{content:"\f2b1"}.fa-themeisle:before{content:"\f2b2"}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:"\f2b3"}.fa-fa:before,.fa-font-awesome:before{content:"\f2b4"}.fa-handshake-o:before{content:"\f2b5"}.fa-envelope-open:before{content:"\f2b6"}.fa-envelope-open-o:before{content:"\f2b7"}.fa-linode:before{content:"\f2b8"}.fa-address-book:before{content:"\f2b9"}.fa-address-book-o:before{content:"\f2ba"}.fa-vcard:before,.fa-address-card:before{content:"\f2bb"}.fa-vcard-o:before,.fa-address-card-o:before{content:"\f2bc"}.fa-user-circle:before{content:"\f2bd"}.fa-user-circle-o:before{content:"\f2be"}.fa-user-o:before{content:"\f2c0"}.fa-id-badge:before{content:"\f2c1"}.fa-drivers-license:before,.fa-id-card:before{content:"\f2c2"}.fa-drivers-license-o:before,.fa-id-card-o:before{content:"\f2c3"}.fa-quora:before{content:"\f2c4"}.fa-free-code-camp:before{content:"\f2c5"}.fa-telegram:before{content:"\f2c6"}.fa-thermometer-4:before,.fa-thermometer:before,.fa-thermometer-full:before{content:"\f2c7"}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:"\f2c8"}.fa-thermometer-2:before,.fa-thermometer-half:before{content:"\f2c9"}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:"\f2ca"}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:"\f2cb"}.fa-shower:before{content:"\f2cc"}.fa-bathtub:before,.fa-s15:before,.fa-bath:before{content:"\f2cd"}.fa-podcast:before{content:"\f2ce"}.fa-window-maximize:before{content:"\f2d0"}.fa-window-minimize:before{content:"\f2d1"}.fa-window-restore:before{content:"\f2d2"}.fa-times-rectangle:before,.fa-window-close:before{content:"\f2d3"}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:"\f2d4"}.fa-bandcamp:before{content:"\f2d5"}.fa-grav:before{content:"\f2d6"}.fa-etsy:before{content:"\f2d7"}.fa-imdb:before{content:"\f2d8"}.fa-ravelry:before{content:"\f2d9"}.fa-eercast:before{content:"\f2da"}.fa-microchip:before{content:"\f2db"}.fa-snowflake-o:before{content:"\f2dc"}.fa-superpowers:before{content:"\f2dd"}.fa-wpexplorer:before{content:"\f2de"}.fa-meetup:before{content:"\f2e0"}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}
5 |
--------------------------------------------------------------------------------