├── .Rbuildignore ├── .gitignore ├── DESCRIPTION ├── LICENSE ├── LICENSE.md ├── NAMESPACE ├── R ├── assertions.R ├── attrs.R ├── data.R ├── horizon.R ├── plot.R └── utils-pipe.R ├── README.md ├── _pkgdown.yml ├── data-raw └── data.R ├── data └── tsdata.rda ├── docs ├── LICENSE-text.html ├── LICENSE.html ├── authors.html ├── docsearch.css ├── docsearch.js ├── index.html ├── index_files │ ├── horizon-1.6.1 │ │ └── horizon-timeseries-chart.min.js │ ├── horizon-binding-0.0.0.9000 │ │ └── horizon.js │ ├── horizon-binding-0.0.1 │ │ └── horizon.js │ └── htmlwidgets-1.3 │ │ └── htmlwidgets.js ├── link.svg ├── pkgdown.css ├── pkgdown.js ├── pkgdown.yml └── reference │ ├── attrs.html │ ├── horizon-shiny.html │ ├── horizon.html │ ├── index.html │ ├── pipe.html │ └── tsdata.html ├── inst └── htmlwidgets │ ├── horizonplot.js │ ├── horizonplot.yaml │ └── lib │ └── horizon │ └── horizon-timeseries-chart.min.js └── man ├── attrs.Rd ├── figures └── README-example-1.png ├── horizon-shiny.Rd ├── horizon.Rd ├── pipe.Rd └── tsdata.Rd /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^test\.R$ 2 | ^LICENSE\.md$ 3 | ^data-raw$ 4 | ^_pkgdown\.yml$ 5 | ^docs$ 6 | ^pkgdown$ 7 | ^README\.Rmd$ 8 | ^.*\.Rproj$ 9 | ^\.Rproj\.user$ 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .Rproj.user 2 | .Rhistory 3 | .RData 4 | .Ruserdata 5 | horizon.Rproj 6 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: horizonplot 2 | Title: Create Horizon Plots of Time Series 3 | Version: 0.0.1.9999 4 | Authors@R: person("John", "Coene", email = "jcoenep@gmail.com", role = c("aut", "cre"), comment = c(ORCID = "0000-0002-6637-4107")) 5 | Description: Create horizon plots of time series. 6 | License: MIT + file LICENSE 7 | Encoding: UTF-8 8 | LazyData: true 9 | Depends: R (>= 2.10) 10 | Imports: 11 | purrr, 12 | dplyr, 13 | rlang, 14 | magrittr, 15 | assertthat, 16 | htmlwidgets 17 | RoxygenNote: 7.1.0 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | YEAR: 2019 2 | COPYRIGHT HOLDER: John Coene 3 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Copyright (c) 2019 John Coene 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | S3method("$<-",uneval) 4 | S3method("[",uneval) 5 | S3method("[<-",uneval) 6 | S3method("[[<-",uneval) 7 | S3method(print,uneval) 8 | export("%>%") 9 | export(attrs) 10 | export(horizon) 11 | export(horizonOutput) 12 | export(renderHorizon) 13 | import(assertthat) 14 | import(htmlwidgets) 15 | import(purrr) 16 | importFrom(magrittr,"%>%") 17 | -------------------------------------------------------------------------------- /R/assertions.R: -------------------------------------------------------------------------------- 1 | not_missing <- function(x) { 2 | !missing(x) 3 | } 4 | 5 | on_failure(not_missing) <- function(call, env) { 6 | paste0( 7 | "Missing `", 8 | crayon::red(deparse(call$x)), 9 | "`." 10 | ) 11 | } 12 | 13 | has_attrs <- function(x){ 14 | if(is.null(x)) 15 | return(FALSE) 16 | if(!length(x)) 17 | return(FALSE) 18 | return(TRUE) 19 | } 20 | 21 | on_failure(has_attrs) <- function(call, env) { 22 | "No attributes found, see `attrs`." 23 | } -------------------------------------------------------------------------------- /R/attrs.R: -------------------------------------------------------------------------------- 1 | #' Attributes 2 | #' 3 | #' Attributes to draw horizon plot. 4 | #' 5 | #' @param x,y,... List of name value pairs giving attributes to map to 6 | #' variables. The names for x and y aspects are typically omitted because 7 | #' they are so common; all other aspects must be named. 8 | #' 9 | #' @section Coordinates: 10 | #' Valid coordinates (depending on layer). 11 | #' \itemize{ 12 | #' \item{\code{x}, \code{y}} 13 | #' \item{\code{val}} 14 | #' } 15 | #' 16 | #' @export 17 | attrs <- function(x, y, ...) { 18 | exprs <- rlang::enquos(x = x, y = y, ..., .ignore_empty = "all") 19 | aes <- new_aes(exprs, env = parent.frame()) 20 | .construct_aesthetics(aes) 21 | } 22 | 23 | # construct aesthetics for re-use 24 | .construct_aesthetics <- function(aes, cl = NULL){ 25 | class <- "attrs" 26 | if(!is.null(cl)) 27 | class <- append(class, cl) 28 | structure(aes, class = c(class, class(aes))) 29 | } 30 | 31 | # Wrap symbolic objects in quosures but pull out constants out of 32 | # quosures for backward-compatibility 33 | new_aesthetic <- function(x, env = globalenv()) { 34 | if (rlang::is_quosure(x)) { 35 | if (!rlang::quo_is_symbolic(x)) { 36 | x <- rlang::quo_get_expr(x) 37 | } 38 | return(x) 39 | } 40 | 41 | if (rlang::is_symbolic(x)) { 42 | x <- rlang::new_quosure(x, env = env) 43 | return(x) 44 | } 45 | 46 | x 47 | } 48 | 49 | new_aes <- function(x, env = globalenv()) { 50 | stopifnot(is.list(x)) 51 | x <- lapply(x, new_aesthetic, env = env) 52 | structure(x, class = c("uneval")) 53 | } 54 | 55 | #' @export 56 | print.uneval <- function(x, ...) { 57 | cat("Aesthetics: \n") 58 | 59 | if (length(x) == 0) { 60 | cat("\n") 61 | } else { 62 | values <- vapply(x, rlang::quo_label, character(1)) 63 | bullets <- paste0("* ", format(paste0("`", names(x), "`")), " -> ", values, "\n") 64 | 65 | cat(bullets, sep = "") 66 | } 67 | 68 | invisible(x) 69 | } 70 | 71 | #' @export 72 | "[.uneval" <- function(x, i, ...) { 73 | new_aes(NextMethod()) 74 | } 75 | 76 | # If necessary coerce replacements to quosures for compatibility 77 | #' @export 78 | "[[<-.uneval" <- function(x, i, value) { 79 | new_aes(NextMethod()) 80 | } 81 | #' @export 82 | "$<-.uneval" <- function(x, i, value) { 83 | # Can't use NextMethod() because of a bug in R 3.1 84 | x <- unclass(x) 85 | x[[i]] <- value 86 | new_aes(x) 87 | } 88 | #' @export 89 | "[<-.uneval" <- function(x, i, value) { 90 | new_aes(NextMethod()) 91 | } 92 | 93 | # is aesthetic? 94 | is_attrs <- function(x, cl = "attrs"){ 95 | aes <- FALSE 96 | if(inherits(x, cl)) 97 | aes <- TRUE 98 | return(aes) 99 | } 100 | 101 | # retrieve aesthetics 102 | get_attrs <- function(...){ 103 | aes <- list(...) %>% 104 | keep(is_attrs) 105 | 106 | if(length(aes)) 107 | aes[[1]] 108 | else 109 | list() 110 | } 111 | 112 | # mutate aesthetics 113 | mutate_aes <- function(main_aes = NULL, aes = NULL, inherit = TRUE){ 114 | 115 | if(is.null(aes) && isTRUE(inherit)) 116 | return(main_aes) 117 | 118 | if(isTRUE(inherit)){ 119 | # aes overrides main_aes 120 | main_aes <- main_aes[!names(main_aes) %in% names(aes)] 121 | combined <- append(aes, main_aes) 122 | return(combined) 123 | } 124 | 125 | return(aes) 126 | } 127 | 128 | # conver to name for selection 129 | attrs_to_columns <- function(attrs){ 130 | keep(attrs, function(x){ 131 | !rlang::is_bare_atomic(x) 132 | }) %>% 133 | map(rlang::as_label) %>% 134 | unname() %>% 135 | unlist() 136 | } 137 | 138 | # coordinate to JSON options 139 | attrs_to_opts <- function(attrs, var){ 140 | if(rlang::is_null(attrs[[var]])) 141 | return(NULL) 142 | if(rlang::is_bare_atomic(attrs[[var]])) 143 | return(attrs[[var]]) 144 | rlang::as_label(attrs[[var]]) 145 | } -------------------------------------------------------------------------------- /R/data.R: -------------------------------------------------------------------------------- 1 | #' Random data 2 | #' 3 | #' Generated data for examples. 4 | #' 5 | #' @format An data.frame object with 5000 rows and 3 columns 6 | #' \describe{ 7 | #' \item{\code{date}}{ Date sequence} 8 | #' \item{\code{value}}{ A number} 9 | #' \item{\code{group}}{ A group name} 10 | #' } 11 | "tsdata" -------------------------------------------------------------------------------- /R/horizon.R: -------------------------------------------------------------------------------- 1 | #' Initialise 2 | #' 3 | #' Initialise an horizon plot. 4 | #' 5 | #' @param data A \code{data.frame} or \link[tibble]{tibble}. 6 | #' @param width,height Must be a valid CSS unit (like \code{'100\%'}, 7 | #' \code{'400px'}, \code{'auto'}) or a number, which will be coerced to a 8 | #' string and have \code{'px'} appended. 9 | #' @param elementId Id of element. 10 | #' @param ... Attributes, specified using \code{\link{attrs}}. 11 | #' @param utc Whether to use the local timezone (\code{FALSE}) 12 | #' or UTC (\code{TRUE}). 13 | #' @param military Whether to show time in 24h or 12h (am/pm) format. 14 | #' @param bands Number of horizon bands to use. 15 | #' @param mode Mode used to represent negative values. 16 | #' \code{offset} renders the negative values from the top of the chart 17 | #' downwards, while /code{mirror} represents them upwards as if they 18 | #' were positive values, albeit with a different color. 19 | #' @param normalize Whether to normalize all series Y axis to the same extent, 20 | #' in order to compare series in absolute terms. It defines the behavior of the 21 | #' dynamic calculation of the max Y, when \code{extent} is not explicitly set. 22 | #' If \code{TRUE}, the extent is calculated as the global maximum value of all 23 | #' the data points combined across all series. If \code{FALSE}, each series extent 24 | #' will be based on their own local maximum. 25 | #' @param scale Set the y-axis scale exponent. Only values > 0 are supported. 26 | #' An exponent of 1 (default) represents a linear Y scale. A function 27 | #' (\link[htmlwidgets]{JS}) receives the series ID as input and should return a 28 | #' numeric value. A number sets the same scale exponent for all the series. 29 | #' @param positive_colors,negative_colors Colors to use for the positive and 30 | #' negative value bands. 31 | #' @param positive_colors_stops,negative_colors_stops Each stop represents an 32 | #' interpolation ratio and only values between \code{]0, 1[} (excluding) are permitted. 33 | #' The stops are used to interpolate the middle colors in \code{*_colors} and are 34 | #' only applicable if there are more than 2 colors. If the number of stops is less 35 | #' than number of middle colors, linear interpolation is used to populate the remaining 36 | #' stops. A value of \code{NULL} (default) results in complete linear interpolation. 37 | #' @param aggregation A function (\link[htmlwidgets]{JS}) to reduce multiple values 38 | #' to a single number, in case there is more than one \code{y} value per unique \code{x} 39 | #' and \code{group} (in \code{\link{attrs}}). 40 | #' @param extent JavaScript function or number to set the y-axis maximum absolute value. 41 | #' By default (\code{NULL}), the max Y is calculated dynamically from the data. 42 | #' A function (\link[htmlwidgets]{JS}) receives the series ID as input and should return 43 | #' a numeric value. A numeric sets the same extent for all the series. 44 | #' @param interpolation_curve Interpolation curve function used to draw lines between points. 45 | #' Should be a \href{https://github.com/d3/d3-shape#curves}{d3 curve function}. A falsy value 46 | #' sets linear interpolation (\href{https://github.com/d3/d3-shape#curveLinear}{curveLinear}). 47 | #' @param ruler Whether to show the ruler. 48 | #' @param zoom Whether to enable pointer-based zoom interactions on the chart, along the time 49 | #' (x-axis) dimension. 50 | #' @param transition Duration (in milliseconds) of the transitions between data states. 51 | #' 52 | #' @examples 53 | #' horizon(tsdata, attrs(x = dates, y = value, group = grp)) 54 | #' 55 | #' @import purrr 56 | #' @import htmlwidgets 57 | #' @import assertthat 58 | #' 59 | #' @export 60 | horizon <- function(data, ..., utc = TRUE, military = TRUE, bands = 4L, mode = c("offset", "mirror"), 61 | normalize = FALSE, scale = 1L, positive_colors = c("white", "midnightBlue"), negative_colors = c("white", "crimson"), 62 | interpolation_curve = NULL, ruler = TRUE, zoom = FALSE, transition = FALSE, positive_colors_stops = NULL, 63 | negative_colors_stops = NULL, aggregation = NULL, extent = NULL, width = "100%", height = NULL, elementId = NULL) { 64 | 65 | mode <- match.arg(mode) 66 | 67 | # must pass data 68 | assert_that(not_missing(data)) 69 | 70 | # extract & process coordinates 71 | attrs <- get_attrs(...) 72 | assert_that(has_attrs(attrs)) 73 | columns <- attrs_to_columns(attrs) 74 | 75 | x = list( 76 | data = data, 77 | series = attrs_to_opts(attrs, "group"), 78 | ts = attrs_to_opts(attrs, "x"), 79 | val = attrs_to_opts(attrs, "y"), 80 | useUtc = utc, 81 | use24h = military, 82 | horizonBands = bands, 83 | horizonMode = mode, 84 | yNormalize = normalize, 85 | yScaleExp = scale, 86 | yExtent = extent, 87 | yAggregation = aggregation, 88 | positiveColors = positive_colors, 89 | positiveColorStops = positive_colors_stops, 90 | negativeColors = negative_colors, 91 | negativeColorsStops = negative_colors_stops, 92 | interpolationCurve = interpolation_curve, 93 | showRuler = ruler, 94 | enableZoom = zoom, 95 | transitionDuration = transition 96 | ) 97 | 98 | # create widget 99 | htmlwidgets::createWidget( 100 | name = 'horizonplot', 101 | x, 102 | width = width, 103 | height = height, 104 | package = 'horizonplot', 105 | elementId = elementId, 106 | preRenderHook = render_horizon, 107 | sizingPolicy = htmlwidgets::sizingPolicy( 108 | padding = 0, 109 | browser.fill = TRUE 110 | ) 111 | ) 112 | } 113 | 114 | render_horizon <- function(h){ 115 | h$x$data <- apply(h$x$data, 1, as.list) 116 | return(h) 117 | } 118 | 119 | #' Shiny bindings for horizon 120 | #' 121 | #' Output and render functions for using horizon within Shiny 122 | #' applications and interactive Rmd documents. 123 | #' 124 | #' @param outputId output variable to read from 125 | #' @param width,height Must be a valid CSS unit (like \code{'100\%'}, 126 | #' \code{'400px'}, \code{'auto'}) or a number, which will be coerced to a 127 | #' string and have \code{'px'} appended. 128 | #' @param expr An expression that generates a horizon 129 | #' @param env The environment in which to evaluate \code{expr}. 130 | #' @param quoted Is \code{expr} a quoted expression (with \code{quote()})? This 131 | #' is useful if you want to save an expression in a variable. 132 | #' 133 | #' @name horizon-shiny 134 | #' 135 | #' @export 136 | horizonOutput <- function(outputId, width = '100%', height = '400px'){ 137 | htmlwidgets::shinyWidgetOutput(outputId, 'horizon', width, height, package = 'horizon') 138 | } 139 | 140 | #' @rdname horizon-shiny 141 | #' @export 142 | renderHorizon <- function(expr, env = parent.frame(), quoted = FALSE) { 143 | if (!quoted) { expr <- substitute(expr) } # force quoted 144 | htmlwidgets::shinyRenderWidget(expr, horizonOutput, env, quoted = TRUE) 145 | } 146 | -------------------------------------------------------------------------------- /R/plot.R: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohnCoene/horizonplot/75b052f6a93a673ccab1d3268a8b552339d876c3/R/plot.R -------------------------------------------------------------------------------- /R/utils-pipe.R: -------------------------------------------------------------------------------- 1 | #' Pipe operator 2 | #' 3 | #' See \code{magrittr::\link[magrittr:pipe]{\%>\%}} for details. 4 | #' 5 | #' @name %>% 6 | #' @rdname pipe 7 | #' @keywords internal 8 | #' @export 9 | #' @importFrom magrittr %>% 10 | #' @usage lhs \%>\% rhs 11 | NULL 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | # horizon 5 | 6 | Create horizon plots time series in R via [horizon-timeseries-chart](https://github.com/vasturiano/horizon-timeseries-chart). 7 | 8 | > Still in development. 9 | 10 | ## Installation 11 | 12 | Install from [GitHub](https://github.com/) with: 13 | 14 | ``` r 15 | # install.packages("remotes") 16 | remotes::install_github("JohnCoene/horizonplot") 17 | ``` 18 | 19 | ## Example 20 | 21 | ```r 22 | library(horizonplot) 23 | 24 | horizon(tsdata, attrs(x = dates, y = value, group = grp)) 25 | ``` 26 | -------------------------------------------------------------------------------- /_pkgdown.yml: -------------------------------------------------------------------------------- 1 | destination: docs 2 | -------------------------------------------------------------------------------- /data-raw/data.R: -------------------------------------------------------------------------------- 1 | set.seed(123) 2 | n <- 1000 3 | 4 | # white noise: 5 | wn <- ts(rnorm(n)) 6 | 7 | # initialise the first two values: 8 | ar1 <- ma1 <- arma11 <- arma22 <- wn[1:2] 9 | 10 | # loop through and create the 3:1000th values: 11 | for(i in 3:n){ 12 | ar1[i] <- ar1[i - 1] * 0.8 + wn[i] 13 | ma1[i] <- wn[i - 1] * 0.8 + wn[i] 14 | arma11[i] <- arma11[i - 1] * 0.8 + wn[i - 1] * 0.8 + wn[i] 15 | arma22[i] <- arma22[i - 1] * 0.8 + arma22[i - 2] * (-0.3) + 0.8 * wn[i-1] - 0.3 * wn[i-2] + wn[i] 16 | } 17 | 18 | # turn them into time series, and for the last two, "integrate" them via cumulative sum 19 | ar1 <- ts(ar1) 20 | ma1 <- ts(ma1) 21 | arma11 <- ts(arma11) 22 | arima111 <- ts(cumsum(arma11)) 23 | arima222 <- ts(cumsum(cumsum(arma22))) 24 | 25 | dates <- seq.Date(Sys.Date()-999, Sys.Date(), by = "days") 26 | tsdata <- tibble( 27 | dates = rep(dates, 5), 28 | value = c(ar1, ma1, arma11, arima111, arima222), 29 | grp = c( 30 | rep("autoregressive", 1000L), 31 | rep("moving average", 1000L), 32 | rep("autoregressive moving average", 1000L), 33 | rep("arima 1", 1000L), 34 | rep("arima 2", 1000L) 35 | ) 36 | ) 37 | 38 | horizon(tsdata, attrs(x = dates, y = value, group = grp)) 39 | 40 | usethis::use_data(tsdata, overwrite = TRUE) 41 | -------------------------------------------------------------------------------- /data/tsdata.rda: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohnCoene/horizonplot/75b052f6a93a673ccab1d3268a8b552339d876c3/data/tsdata.rda -------------------------------------------------------------------------------- /docs/LICENSE-text.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | License • horizon 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 49 | 50 | 51 | 52 | 53 | 54 |
55 |
56 | 90 | 91 | 92 |
93 | 94 |
95 |
96 | 99 | 100 |
YEAR: 2019
101 | COPYRIGHT HOLDER: John Coene
102 | 
103 | 104 |
105 | 106 |
107 | 108 | 109 |
110 | 113 | 114 |
115 |

Site built with pkgdown 1.3.0.

116 |
117 |
118 |
119 | 120 | 121 | 122 | 123 | 124 | 125 | -------------------------------------------------------------------------------- /docs/LICENSE.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | MIT License • horizon 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 49 | 50 | 51 | 52 | 53 | 54 |
55 |
56 | 90 | 91 | 92 |
93 | 94 |
95 |
96 | 99 | 100 |
101 | 102 |

Copyright (c) 2019 John Coene

103 |

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

104 |

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

105 |

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

106 |
107 | 108 |
109 | 110 |
111 | 112 | 113 |
114 | 117 | 118 |
119 |

Site built with pkgdown 1.3.0.

120 |
121 |
122 |
123 | 124 | 125 | 126 | 127 | 128 | 129 | -------------------------------------------------------------------------------- /docs/authors.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Authors • horizon 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 49 | 50 | 51 | 52 | 53 | 54 |
55 |
56 | 90 | 91 | 92 |
93 | 94 |
95 |
96 | 99 | 100 |
    101 |
  • 102 |

    John Coene. Author, maintainer. ORCID 103 |

    104 |
  • 105 |
106 | 107 |
108 | 109 |
110 | 111 | 112 |
113 | 116 | 117 |
118 |

Site built with pkgdown 1.3.0.

119 |
120 |
121 |
122 | 123 | 124 | 125 | 126 | 127 | 128 | -------------------------------------------------------------------------------- /docs/docsearch.css: -------------------------------------------------------------------------------- 1 | /* Docsearch -------------------------------------------------------------- */ 2 | /* 3 | Source: https://github.com/algolia/docsearch/ 4 | License: MIT 5 | */ 6 | 7 | .algolia-autocomplete { 8 | display: block; 9 | -webkit-box-flex: 1; 10 | -ms-flex: 1; 11 | flex: 1 12 | } 13 | 14 | .algolia-autocomplete .ds-dropdown-menu { 15 | width: 100%; 16 | min-width: none; 17 | max-width: none; 18 | padding: .75rem 0; 19 | background-color: #fff; 20 | background-clip: padding-box; 21 | border: 1px solid rgba(0, 0, 0, .1); 22 | box-shadow: 0 .5rem 1rem rgba(0, 0, 0, .175); 23 | } 24 | 25 | @media (min-width:768px) { 26 | .algolia-autocomplete .ds-dropdown-menu { 27 | width: 175% 28 | } 29 | } 30 | 31 | .algolia-autocomplete .ds-dropdown-menu::before { 32 | display: none 33 | } 34 | 35 | .algolia-autocomplete .ds-dropdown-menu [class^=ds-dataset-] { 36 | padding: 0; 37 | background-color: rgb(255,255,255); 38 | border: 0; 39 | max-height: 80vh; 40 | } 41 | 42 | .algolia-autocomplete .ds-dropdown-menu .ds-suggestions { 43 | margin-top: 0 44 | } 45 | 46 | .algolia-autocomplete .algolia-docsearch-suggestion { 47 | padding: 0; 48 | overflow: visible 49 | } 50 | 51 | .algolia-autocomplete .algolia-docsearch-suggestion--category-header { 52 | padding: .125rem 1rem; 53 | margin-top: 0; 54 | font-size: 1.3em; 55 | font-weight: 500; 56 | color: #00008B; 57 | border-bottom: 0 58 | } 59 | 60 | .algolia-autocomplete .algolia-docsearch-suggestion--wrapper { 61 | float: none; 62 | padding-top: 0 63 | } 64 | 65 | .algolia-autocomplete .algolia-docsearch-suggestion--subcategory-column { 66 | float: none; 67 | width: auto; 68 | padding: 0; 69 | text-align: left 70 | } 71 | 72 | .algolia-autocomplete .algolia-docsearch-suggestion--content { 73 | float: none; 74 | width: auto; 75 | padding: 0 76 | } 77 | 78 | .algolia-autocomplete .algolia-docsearch-suggestion--content::before { 79 | display: none 80 | } 81 | 82 | .algolia-autocomplete .ds-suggestion:not(:first-child) .algolia-docsearch-suggestion--category-header { 83 | padding-top: .75rem; 84 | margin-top: .75rem; 85 | border-top: 1px solid rgba(0, 0, 0, .1) 86 | } 87 | 88 | .algolia-autocomplete .ds-suggestion .algolia-docsearch-suggestion--subcategory-column { 89 | display: block; 90 | padding: .1rem 1rem; 91 | margin-bottom: 0.1; 92 | font-size: 1.0em; 93 | font-weight: 400 94 | /* display: none */ 95 | } 96 | 97 | .algolia-autocomplete .algolia-docsearch-suggestion--title { 98 | display: block; 99 | padding: .25rem 1rem; 100 | margin-bottom: 0; 101 | font-size: 0.9em; 102 | font-weight: 400 103 | } 104 | 105 | .algolia-autocomplete .algolia-docsearch-suggestion--text { 106 | padding: 0 1rem .5rem; 107 | margin-top: -.25rem; 108 | font-size: 0.8em; 109 | font-weight: 400; 110 | line-height: 1.25 111 | } 112 | 113 | .algolia-autocomplete .algolia-docsearch-footer { 114 | width: 110px; 115 | height: 20px; 116 | z-index: 3; 117 | margin-top: 10.66667px; 118 | float: right; 119 | font-size: 0; 120 | line-height: 0; 121 | } 122 | 123 | .algolia-autocomplete .algolia-docsearch-footer--logo { 124 | background-image: url("data:image/svg+xml;utf8,"); 125 | background-repeat: no-repeat; 126 | background-position: 50%; 127 | background-size: 100%; 128 | overflow: hidden; 129 | text-indent: -9000px; 130 | width: 100%; 131 | height: 100%; 132 | display: block; 133 | transform: translate(-8px); 134 | } 135 | 136 | .algolia-autocomplete .algolia-docsearch-suggestion--highlight { 137 | color: #FF8C00; 138 | background: rgba(232, 189, 54, 0.1) 139 | } 140 | 141 | 142 | .algolia-autocomplete .algolia-docsearch-suggestion--text .algolia-docsearch-suggestion--highlight { 143 | box-shadow: inset 0 -2px 0 0 rgba(105, 105, 105, .5) 144 | } 145 | 146 | .algolia-autocomplete .ds-suggestion.ds-cursor .algolia-docsearch-suggestion--content { 147 | background-color: rgba(192, 192, 192, .15) 148 | } 149 | -------------------------------------------------------------------------------- /docs/docsearch.js: -------------------------------------------------------------------------------- 1 | $(function() { 2 | 3 | // register a handler to move the focus to the search bar 4 | // upon pressing shift + "/" (i.e. "?") 5 | $(document).on('keydown', function(e) { 6 | if (e.shiftKey && e.keyCode == 191) { 7 | e.preventDefault(); 8 | $("#search-input").focus(); 9 | } 10 | }); 11 | 12 | $(document).ready(function() { 13 | // do keyword highlighting 14 | /* modified from https://jsfiddle.net/julmot/bL6bb5oo/ */ 15 | var mark = function() { 16 | 17 | var referrer = document.URL ; 18 | var paramKey = "q" ; 19 | 20 | if (referrer.indexOf("?") !== -1) { 21 | var qs = referrer.substr(referrer.indexOf('?') + 1); 22 | var qs_noanchor = qs.split('#')[0]; 23 | var qsa = qs_noanchor.split('&'); 24 | var keyword = ""; 25 | 26 | for (var i = 0; i < qsa.length; i++) { 27 | var currentParam = qsa[i].split('='); 28 | 29 | if (currentParam.length !== 2) { 30 | continue; 31 | } 32 | 33 | if (currentParam[0] == paramKey) { 34 | keyword = decodeURIComponent(currentParam[1].replace(/\+/g, "%20")); 35 | } 36 | } 37 | 38 | if (keyword !== "") { 39 | $(".contents").unmark({ 40 | done: function() { 41 | $(".contents").mark(keyword); 42 | } 43 | }); 44 | } 45 | } 46 | }; 47 | 48 | mark(); 49 | }); 50 | }); 51 | 52 | /* Search term highlighting ------------------------------*/ 53 | 54 | function matchedWords(hit) { 55 | var words = []; 56 | 57 | var hierarchy = hit._highlightResult.hierarchy; 58 | // loop to fetch from lvl0, lvl1, etc. 59 | for (var idx in hierarchy) { 60 | words = words.concat(hierarchy[idx].matchedWords); 61 | } 62 | 63 | var content = hit._highlightResult.content; 64 | if (content) { 65 | words = words.concat(content.matchedWords); 66 | } 67 | 68 | // return unique words 69 | var words_uniq = [...new Set(words)]; 70 | return words_uniq; 71 | } 72 | 73 | function updateHitURL(hit) { 74 | 75 | var words = matchedWords(hit); 76 | var url = ""; 77 | 78 | if (hit.anchor) { 79 | url = hit.url_without_anchor + '?q=' + escape(words.join(" ")) + '#' + hit.anchor; 80 | } else { 81 | url = hit.url + '?q=' + escape(words.join(" ")); 82 | } 83 | 84 | return url; 85 | } 86 | -------------------------------------------------------------------------------- /docs/index_files/horizon-binding-0.0.0.9000/horizon.js: -------------------------------------------------------------------------------- 1 | HTMLWidgets.widget({ 2 | 3 | name: 'horizon', 4 | 5 | type: 'output', 6 | 7 | factory: function(el, width, height) { 8 | 9 | var chart, 10 | dom, 11 | data; 12 | 13 | return { 14 | 15 | renderValue: function(x) { 16 | 17 | for (i = 0; i < x.data.length; i++) { 18 | x.data[i][x.ts] = new Date(x.data[i][x.ts]) 19 | } 20 | 21 | console.log(x.data) 22 | 23 | dom = document.getElementById(el.id); 24 | chart = HorizonTSChart(); 25 | chart 26 | .data(x.data) 27 | .series(x.series) 28 | .ts(x.ts) 29 | .val(x.val) 30 | .useUtc(x.useUtc) 31 | .use24h(x.use24h) 32 | .horizonBands(x.horizonBands) 33 | .horizonMode(x.horizonMode) 34 | .yNormalize(x.yNormalize) 35 | .yScaleExp(x.yScaleExp) 36 | .positiveColors(x.positiveColors) 37 | .negativeColors(x.negativeColors) 38 | .showRuler(x.showRuler) 39 | .enableZoom(x.enableZoom) 40 | .transitionDuration(x.transitionDuration) 41 | (dom); 42 | 43 | if(x.hasOwnProperty(x.yAggregation)) 44 | chart.yAggregation(x.yAggregation) 45 | 46 | if(x.hasOwnProperty(x.positiveColorStops)) 47 | chart.positiveColorStops(x.positiveColorStops) 48 | 49 | if(x.hasOwnProperty(x.negativeColorStops)) 50 | chart.negativeColorStops(x.negativeColorStops) 51 | 52 | if(x.hasOwnProperty(x.yExtent)) 53 | chart.yExtent(x.yExtent) 54 | 55 | if(x.hasOwnProperty(x.interpolationCurve)) 56 | chart.interpolationCurve(x.interpolationCurve) 57 | }, 58 | 59 | resize: function(width, height) { 60 | 61 | console.log(dom.offsetHeight); 62 | 63 | chart 64 | .width(dom.offsetWidth) 65 | .height(dom.offsetHeight); 66 | 67 | } 68 | 69 | }; 70 | } 71 | }); -------------------------------------------------------------------------------- /docs/index_files/horizon-binding-0.0.1/horizon.js: -------------------------------------------------------------------------------- 1 | HTMLWidgets.widget({ 2 | 3 | name: 'horizon', 4 | 5 | type: 'output', 6 | 7 | factory: function(el, width, height) { 8 | 9 | var chart, 10 | dom, 11 | data; 12 | 13 | return { 14 | 15 | renderValue: function(x) { 16 | 17 | for (i = 0; i < x.data.length; i++) { 18 | x.data[i][x.ts] = new Date(x.data[i][x.ts]) 19 | } 20 | 21 | console.log(x.data) 22 | 23 | dom = document.getElementById(el.id); 24 | chart = HorizonTSChart(); 25 | chart 26 | .data(x.data) 27 | .width(dom.offsetWidth) 28 | .height(dom.offsetHeight) 29 | .series(x.series) 30 | .ts(x.ts) 31 | .val(x.val) 32 | .useUtc(x.useUtc) 33 | .use24h(x.use24h) 34 | .horizonBands(x.horizonBands) 35 | .horizonMode(x.horizonMode) 36 | .yNormalize(x.yNormalize) 37 | .yScaleExp(x.yScaleExp) 38 | .positiveColors(x.positiveColors) 39 | .negativeColors(x.negativeColors) 40 | .showRuler(x.showRuler) 41 | .enableZoom(x.enableZoom) 42 | .transitionDuration(x.transitionDuration) 43 | (dom); 44 | 45 | if(x.hasOwnProperty(x.yAggregation)) 46 | chart.yAggregation(x.yAggregation) 47 | 48 | if(x.hasOwnProperty(x.positiveColorStops)) 49 | chart.positiveColorStops(x.positiveColorStops) 50 | 51 | if(x.hasOwnProperty(x.negativeColorStops)) 52 | chart.negativeColorStops(x.negativeColorStops) 53 | 54 | if(x.hasOwnProperty(x.yExtent)) 55 | chart.yExtent(x.yExtent) 56 | 57 | if(x.hasOwnProperty(x.interpolationCurve)) 58 | chart.interpolationCurve(x.interpolationCurve) 59 | }, 60 | 61 | resize: function(width, height) { 62 | 63 | console.log(dom.offsetHeight); 64 | 65 | chart 66 | .width(dom.offsetWidth) 67 | .height(dom.offsetHeight); 68 | 69 | } 70 | 71 | }; 72 | } 73 | }); -------------------------------------------------------------------------------- /docs/index_files/htmlwidgets-1.3/htmlwidgets.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | // If window.HTMLWidgets is already defined, then use it; otherwise create a 3 | // new object. This allows preceding code to set options that affect the 4 | // initialization process (though none currently exist). 5 | window.HTMLWidgets = window.HTMLWidgets || {}; 6 | 7 | // See if we're running in a viewer pane. If not, we're in a web browser. 8 | var viewerMode = window.HTMLWidgets.viewerMode = 9 | /\bviewer_pane=1\b/.test(window.location); 10 | 11 | // See if we're running in Shiny mode. If not, it's a static document. 12 | // Note that static widgets can appear in both Shiny and static modes, but 13 | // obviously, Shiny widgets can only appear in Shiny apps/documents. 14 | var shinyMode = window.HTMLWidgets.shinyMode = 15 | typeof(window.Shiny) !== "undefined" && !!window.Shiny.outputBindings; 16 | 17 | // We can't count on jQuery being available, so we implement our own 18 | // version if necessary. 19 | function querySelectorAll(scope, selector) { 20 | if (typeof(jQuery) !== "undefined" && scope instanceof jQuery) { 21 | return scope.find(selector); 22 | } 23 | if (scope.querySelectorAll) { 24 | return scope.querySelectorAll(selector); 25 | } 26 | } 27 | 28 | function asArray(value) { 29 | if (value === null) 30 | return []; 31 | if ($.isArray(value)) 32 | return value; 33 | return [value]; 34 | } 35 | 36 | // Implement jQuery's extend 37 | function extend(target /*, ... */) { 38 | if (arguments.length == 1) { 39 | return target; 40 | } 41 | for (var i = 1; i < arguments.length; i++) { 42 | var source = arguments[i]; 43 | for (var prop in source) { 44 | if (source.hasOwnProperty(prop)) { 45 | target[prop] = source[prop]; 46 | } 47 | } 48 | } 49 | return target; 50 | } 51 | 52 | // IE8 doesn't support Array.forEach. 53 | function forEach(values, callback, thisArg) { 54 | if (values.forEach) { 55 | values.forEach(callback, thisArg); 56 | } else { 57 | for (var i = 0; i < values.length; i++) { 58 | callback.call(thisArg, values[i], i, values); 59 | } 60 | } 61 | } 62 | 63 | // Replaces the specified method with the return value of funcSource. 64 | // 65 | // Note that funcSource should not BE the new method, it should be a function 66 | // that RETURNS the new method. funcSource receives a single argument that is 67 | // the overridden method, it can be called from the new method. The overridden 68 | // method can be called like a regular function, it has the target permanently 69 | // bound to it so "this" will work correctly. 70 | function overrideMethod(target, methodName, funcSource) { 71 | var superFunc = target[methodName] || function() {}; 72 | var superFuncBound = function() { 73 | return superFunc.apply(target, arguments); 74 | }; 75 | target[methodName] = funcSource(superFuncBound); 76 | } 77 | 78 | // Add a method to delegator that, when invoked, calls 79 | // delegatee.methodName. If there is no such method on 80 | // the delegatee, but there was one on delegator before 81 | // delegateMethod was called, then the original version 82 | // is invoked instead. 83 | // For example: 84 | // 85 | // var a = { 86 | // method1: function() { console.log('a1'); } 87 | // method2: function() { console.log('a2'); } 88 | // }; 89 | // var b = { 90 | // method1: function() { console.log('b1'); } 91 | // }; 92 | // delegateMethod(a, b, "method1"); 93 | // delegateMethod(a, b, "method2"); 94 | // a.method1(); 95 | // a.method2(); 96 | // 97 | // The output would be "b1", "a2". 98 | function delegateMethod(delegator, delegatee, methodName) { 99 | var inherited = delegator[methodName]; 100 | delegator[methodName] = function() { 101 | var target = delegatee; 102 | var method = delegatee[methodName]; 103 | 104 | // The method doesn't exist on the delegatee. Instead, 105 | // call the method on the delegator, if it exists. 106 | if (!method) { 107 | target = delegator; 108 | method = inherited; 109 | } 110 | 111 | if (method) { 112 | return method.apply(target, arguments); 113 | } 114 | }; 115 | } 116 | 117 | // Implement a vague facsimilie of jQuery's data method 118 | function elementData(el, name, value) { 119 | if (arguments.length == 2) { 120 | return el["htmlwidget_data_" + name]; 121 | } else if (arguments.length == 3) { 122 | el["htmlwidget_data_" + name] = value; 123 | return el; 124 | } else { 125 | throw new Error("Wrong number of arguments for elementData: " + 126 | arguments.length); 127 | } 128 | } 129 | 130 | // http://stackoverflow.com/questions/3446170/escape-string-for-use-in-javascript-regex 131 | function escapeRegExp(str) { 132 | return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); 133 | } 134 | 135 | function hasClass(el, className) { 136 | var re = new RegExp("\\b" + escapeRegExp(className) + "\\b"); 137 | return re.test(el.className); 138 | } 139 | 140 | // elements - array (or array-like object) of HTML elements 141 | // className - class name to test for 142 | // include - if true, only return elements with given className; 143 | // if false, only return elements *without* given className 144 | function filterByClass(elements, className, include) { 145 | var results = []; 146 | for (var i = 0; i < elements.length; i++) { 147 | if (hasClass(elements[i], className) == include) 148 | results.push(elements[i]); 149 | } 150 | return results; 151 | } 152 | 153 | function on(obj, eventName, func) { 154 | if (obj.addEventListener) { 155 | obj.addEventListener(eventName, func, false); 156 | } else if (obj.attachEvent) { 157 | obj.attachEvent(eventName, func); 158 | } 159 | } 160 | 161 | function off(obj, eventName, func) { 162 | if (obj.removeEventListener) 163 | obj.removeEventListener(eventName, func, false); 164 | else if (obj.detachEvent) { 165 | obj.detachEvent(eventName, func); 166 | } 167 | } 168 | 169 | // Translate array of values to top/right/bottom/left, as usual with 170 | // the "padding" CSS property 171 | // https://developer.mozilla.org/en-US/docs/Web/CSS/padding 172 | function unpackPadding(value) { 173 | if (typeof(value) === "number") 174 | value = [value]; 175 | if (value.length === 1) { 176 | return {top: value[0], right: value[0], bottom: value[0], left: value[0]}; 177 | } 178 | if (value.length === 2) { 179 | return {top: value[0], right: value[1], bottom: value[0], left: value[1]}; 180 | } 181 | if (value.length === 3) { 182 | return {top: value[0], right: value[1], bottom: value[2], left: value[1]}; 183 | } 184 | if (value.length === 4) { 185 | return {top: value[0], right: value[1], bottom: value[2], left: value[3]}; 186 | } 187 | } 188 | 189 | // Convert an unpacked padding object to a CSS value 190 | function paddingToCss(paddingObj) { 191 | return paddingObj.top + "px " + paddingObj.right + "px " + paddingObj.bottom + "px " + paddingObj.left + "px"; 192 | } 193 | 194 | // Makes a number suitable for CSS 195 | function px(x) { 196 | if (typeof(x) === "number") 197 | return x + "px"; 198 | else 199 | return x; 200 | } 201 | 202 | // Retrieves runtime widget sizing information for an element. 203 | // The return value is either null, or an object with fill, padding, 204 | // defaultWidth, defaultHeight fields. 205 | function sizingPolicy(el) { 206 | var sizingEl = document.querySelector("script[data-for='" + el.id + "'][type='application/htmlwidget-sizing']"); 207 | if (!sizingEl) 208 | return null; 209 | var sp = JSON.parse(sizingEl.textContent || sizingEl.text || "{}"); 210 | if (viewerMode) { 211 | return sp.viewer; 212 | } else { 213 | return sp.browser; 214 | } 215 | } 216 | 217 | // @param tasks Array of strings (or falsy value, in which case no-op). 218 | // Each element must be a valid JavaScript expression that yields a 219 | // function. Or, can be an array of objects with "code" and "data" 220 | // properties; in this case, the "code" property should be a string 221 | // of JS that's an expr that yields a function, and "data" should be 222 | // an object that will be added as an additional argument when that 223 | // function is called. 224 | // @param target The object that will be "this" for each function 225 | // execution. 226 | // @param args Array of arguments to be passed to the functions. (The 227 | // same arguments will be passed to all functions.) 228 | function evalAndRun(tasks, target, args) { 229 | if (tasks) { 230 | forEach(tasks, function(task) { 231 | var theseArgs = args; 232 | if (typeof(task) === "object") { 233 | theseArgs = theseArgs.concat([task.data]); 234 | task = task.code; 235 | } 236 | var taskFunc = eval("(" + task + ")"); 237 | if (typeof(taskFunc) !== "function") { 238 | throw new Error("Task must be a function! Source:\n" + task); 239 | } 240 | taskFunc.apply(target, theseArgs); 241 | }); 242 | } 243 | } 244 | 245 | function initSizing(el) { 246 | var sizing = sizingPolicy(el); 247 | if (!sizing) 248 | return; 249 | 250 | var cel = document.getElementById("htmlwidget_container"); 251 | if (!cel) 252 | return; 253 | 254 | if (typeof(sizing.padding) !== "undefined") { 255 | document.body.style.margin = "0"; 256 | document.body.style.padding = paddingToCss(unpackPadding(sizing.padding)); 257 | } 258 | 259 | if (sizing.fill) { 260 | document.body.style.overflow = "hidden"; 261 | document.body.style.width = "100%"; 262 | document.body.style.height = "100%"; 263 | document.documentElement.style.width = "100%"; 264 | document.documentElement.style.height = "100%"; 265 | if (cel) { 266 | cel.style.position = "absolute"; 267 | var pad = unpackPadding(sizing.padding); 268 | cel.style.top = pad.top + "px"; 269 | cel.style.right = pad.right + "px"; 270 | cel.style.bottom = pad.bottom + "px"; 271 | cel.style.left = pad.left + "px"; 272 | el.style.width = "100%"; 273 | el.style.height = "100%"; 274 | } 275 | 276 | return { 277 | getWidth: function() { return cel.offsetWidth; }, 278 | getHeight: function() { return cel.offsetHeight; } 279 | }; 280 | 281 | } else { 282 | el.style.width = px(sizing.width); 283 | el.style.height = px(sizing.height); 284 | 285 | return { 286 | getWidth: function() { return el.offsetWidth; }, 287 | getHeight: function() { return el.offsetHeight; } 288 | }; 289 | } 290 | } 291 | 292 | // Default implementations for methods 293 | var defaults = { 294 | find: function(scope) { 295 | return querySelectorAll(scope, "." + this.name); 296 | }, 297 | renderError: function(el, err) { 298 | var $el = $(el); 299 | 300 | this.clearError(el); 301 | 302 | // Add all these error classes, as Shiny does 303 | var errClass = "shiny-output-error"; 304 | if (err.type !== null) { 305 | // use the classes of the error condition as CSS class names 306 | errClass = errClass + " " + $.map(asArray(err.type), function(type) { 307 | return errClass + "-" + type; 308 | }).join(" "); 309 | } 310 | errClass = errClass + " htmlwidgets-error"; 311 | 312 | // Is el inline or block? If inline or inline-block, just display:none it 313 | // and add an inline error. 314 | var display = $el.css("display"); 315 | $el.data("restore-display-mode", display); 316 | 317 | if (display === "inline" || display === "inline-block") { 318 | $el.hide(); 319 | if (err.message !== "") { 320 | var errorSpan = $("").addClass(errClass); 321 | errorSpan.text(err.message); 322 | $el.after(errorSpan); 323 | } 324 | } else if (display === "block") { 325 | // If block, add an error just after the el, set visibility:none on the 326 | // el, and position the error to be on top of the el. 327 | // Mark it with a unique ID and CSS class so we can remove it later. 328 | $el.css("visibility", "hidden"); 329 | if (err.message !== "") { 330 | var errorDiv = $("
").addClass(errClass).css("position", "absolute") 331 | .css("top", el.offsetTop) 332 | .css("left", el.offsetLeft) 333 | // setting width can push out the page size, forcing otherwise 334 | // unnecessary scrollbars to appear and making it impossible for 335 | // the element to shrink; so use max-width instead 336 | .css("maxWidth", el.offsetWidth) 337 | .css("height", el.offsetHeight); 338 | errorDiv.text(err.message); 339 | $el.after(errorDiv); 340 | 341 | // Really dumb way to keep the size/position of the error in sync with 342 | // the parent element as the window is resized or whatever. 343 | var intId = setInterval(function() { 344 | if (!errorDiv[0].parentElement) { 345 | clearInterval(intId); 346 | return; 347 | } 348 | errorDiv 349 | .css("top", el.offsetTop) 350 | .css("left", el.offsetLeft) 351 | .css("maxWidth", el.offsetWidth) 352 | .css("height", el.offsetHeight); 353 | }, 500); 354 | } 355 | } 356 | }, 357 | clearError: function(el) { 358 | var $el = $(el); 359 | var display = $el.data("restore-display-mode"); 360 | $el.data("restore-display-mode", null); 361 | 362 | if (display === "inline" || display === "inline-block") { 363 | if (display) 364 | $el.css("display", display); 365 | $(el.nextSibling).filter(".htmlwidgets-error").remove(); 366 | } else if (display === "block"){ 367 | $el.css("visibility", "inherit"); 368 | $(el.nextSibling).filter(".htmlwidgets-error").remove(); 369 | } 370 | }, 371 | sizing: {} 372 | }; 373 | 374 | // Called by widget bindings to register a new type of widget. The definition 375 | // object can contain the following properties: 376 | // - name (required) - A string indicating the binding name, which will be 377 | // used by default as the CSS classname to look for. 378 | // - initialize (optional) - A function(el) that will be called once per 379 | // widget element; if a value is returned, it will be passed as the third 380 | // value to renderValue. 381 | // - renderValue (required) - A function(el, data, initValue) that will be 382 | // called with data. Static contexts will cause this to be called once per 383 | // element; Shiny apps will cause this to be called multiple times per 384 | // element, as the data changes. 385 | window.HTMLWidgets.widget = function(definition) { 386 | if (!definition.name) { 387 | throw new Error("Widget must have a name"); 388 | } 389 | if (!definition.type) { 390 | throw new Error("Widget must have a type"); 391 | } 392 | // Currently we only support output widgets 393 | if (definition.type !== "output") { 394 | throw new Error("Unrecognized widget type '" + definition.type + "'"); 395 | } 396 | // TODO: Verify that .name is a valid CSS classname 397 | 398 | // Support new-style instance-bound definitions. Old-style class-bound 399 | // definitions have one widget "object" per widget per type/class of 400 | // widget; the renderValue and resize methods on such widget objects 401 | // take el and instance arguments, because the widget object can't 402 | // store them. New-style instance-bound definitions have one widget 403 | // object per widget instance; the definition that's passed in doesn't 404 | // provide renderValue or resize methods at all, just the single method 405 | // factory(el, width, height) 406 | // which returns an object that has renderValue(x) and resize(w, h). 407 | // This enables a far more natural programming style for the widget 408 | // author, who can store per-instance state using either OO-style 409 | // instance fields or functional-style closure variables (I guess this 410 | // is in contrast to what can only be called C-style pseudo-OO which is 411 | // what we required before). 412 | if (definition.factory) { 413 | definition = createLegacyDefinitionAdapter(definition); 414 | } 415 | 416 | if (!definition.renderValue) { 417 | throw new Error("Widget must have a renderValue function"); 418 | } 419 | 420 | // For static rendering (non-Shiny), use a simple widget registration 421 | // scheme. We also use this scheme for Shiny apps/documents that also 422 | // contain static widgets. 423 | window.HTMLWidgets.widgets = window.HTMLWidgets.widgets || []; 424 | // Merge defaults into the definition; don't mutate the original definition. 425 | var staticBinding = extend({}, defaults, definition); 426 | overrideMethod(staticBinding, "find", function(superfunc) { 427 | return function(scope) { 428 | var results = superfunc(scope); 429 | // Filter out Shiny outputs, we only want the static kind 430 | return filterByClass(results, "html-widget-output", false); 431 | }; 432 | }); 433 | window.HTMLWidgets.widgets.push(staticBinding); 434 | 435 | if (shinyMode) { 436 | // Shiny is running. Register the definition with an output binding. 437 | // The definition itself will not be the output binding, instead 438 | // we will make an output binding object that delegates to the 439 | // definition. This is because we foolishly used the same method 440 | // name (renderValue) for htmlwidgets definition and Shiny bindings 441 | // but they actually have quite different semantics (the Shiny 442 | // bindings receive data that includes lots of metadata that it 443 | // strips off before calling htmlwidgets renderValue). We can't 444 | // just ignore the difference because in some widgets it's helpful 445 | // to call this.renderValue() from inside of resize(), and if 446 | // we're not delegating, then that call will go to the Shiny 447 | // version instead of the htmlwidgets version. 448 | 449 | // Merge defaults with definition, without mutating either. 450 | var bindingDef = extend({}, defaults, definition); 451 | 452 | // This object will be our actual Shiny binding. 453 | var shinyBinding = new Shiny.OutputBinding(); 454 | 455 | // With a few exceptions, we'll want to simply use the bindingDef's 456 | // version of methods if they are available, otherwise fall back to 457 | // Shiny's defaults. NOTE: If Shiny's output bindings gain additional 458 | // methods in the future, and we want them to be overrideable by 459 | // HTMLWidget binding definitions, then we'll need to add them to this 460 | // list. 461 | delegateMethod(shinyBinding, bindingDef, "getId"); 462 | delegateMethod(shinyBinding, bindingDef, "onValueChange"); 463 | delegateMethod(shinyBinding, bindingDef, "onValueError"); 464 | delegateMethod(shinyBinding, bindingDef, "renderError"); 465 | delegateMethod(shinyBinding, bindingDef, "clearError"); 466 | delegateMethod(shinyBinding, bindingDef, "showProgress"); 467 | 468 | // The find, renderValue, and resize are handled differently, because we 469 | // want to actually decorate the behavior of the bindingDef methods. 470 | 471 | shinyBinding.find = function(scope) { 472 | var results = bindingDef.find(scope); 473 | 474 | // Only return elements that are Shiny outputs, not static ones 475 | var dynamicResults = results.filter(".html-widget-output"); 476 | 477 | // It's possible that whatever caused Shiny to think there might be 478 | // new dynamic outputs, also caused there to be new static outputs. 479 | // Since there might be lots of different htmlwidgets bindings, we 480 | // schedule execution for later--no need to staticRender multiple 481 | // times. 482 | if (results.length !== dynamicResults.length) 483 | scheduleStaticRender(); 484 | 485 | return dynamicResults; 486 | }; 487 | 488 | // Wrap renderValue to handle initialization, which unfortunately isn't 489 | // supported natively by Shiny at the time of this writing. 490 | 491 | shinyBinding.renderValue = function(el, data) { 492 | Shiny.renderDependencies(data.deps); 493 | // Resolve strings marked as javascript literals to objects 494 | if (!(data.evals instanceof Array)) data.evals = [data.evals]; 495 | for (var i = 0; data.evals && i < data.evals.length; i++) { 496 | window.HTMLWidgets.evaluateStringMember(data.x, data.evals[i]); 497 | } 498 | if (!bindingDef.renderOnNullValue) { 499 | if (data.x === null) { 500 | el.style.visibility = "hidden"; 501 | return; 502 | } else { 503 | el.style.visibility = "inherit"; 504 | } 505 | } 506 | if (!elementData(el, "initialized")) { 507 | initSizing(el); 508 | 509 | elementData(el, "initialized", true); 510 | if (bindingDef.initialize) { 511 | var result = bindingDef.initialize(el, el.offsetWidth, 512 | el.offsetHeight); 513 | elementData(el, "init_result", result); 514 | } 515 | } 516 | bindingDef.renderValue(el, data.x, elementData(el, "init_result")); 517 | evalAndRun(data.jsHooks.render, elementData(el, "init_result"), [el, data.x]); 518 | }; 519 | 520 | // Only override resize if bindingDef implements it 521 | if (bindingDef.resize) { 522 | shinyBinding.resize = function(el, width, height) { 523 | // Shiny can call resize before initialize/renderValue have been 524 | // called, which doesn't make sense for widgets. 525 | if (elementData(el, "initialized")) { 526 | bindingDef.resize(el, width, height, elementData(el, "init_result")); 527 | } 528 | }; 529 | } 530 | 531 | Shiny.outputBindings.register(shinyBinding, bindingDef.name); 532 | } 533 | }; 534 | 535 | var scheduleStaticRenderTimerId = null; 536 | function scheduleStaticRender() { 537 | if (!scheduleStaticRenderTimerId) { 538 | scheduleStaticRenderTimerId = setTimeout(function() { 539 | scheduleStaticRenderTimerId = null; 540 | window.HTMLWidgets.staticRender(); 541 | }, 1); 542 | } 543 | } 544 | 545 | // Render static widgets after the document finishes loading 546 | // Statically render all elements that are of this widget's class 547 | window.HTMLWidgets.staticRender = function() { 548 | var bindings = window.HTMLWidgets.widgets || []; 549 | forEach(bindings, function(binding) { 550 | var matches = binding.find(document.documentElement); 551 | forEach(matches, function(el) { 552 | var sizeObj = initSizing(el, binding); 553 | 554 | if (hasClass(el, "html-widget-static-bound")) 555 | return; 556 | el.className = el.className + " html-widget-static-bound"; 557 | 558 | var initResult; 559 | if (binding.initialize) { 560 | initResult = binding.initialize(el, 561 | sizeObj ? sizeObj.getWidth() : el.offsetWidth, 562 | sizeObj ? sizeObj.getHeight() : el.offsetHeight 563 | ); 564 | elementData(el, "init_result", initResult); 565 | } 566 | 567 | if (binding.resize) { 568 | var lastSize = { 569 | w: sizeObj ? sizeObj.getWidth() : el.offsetWidth, 570 | h: sizeObj ? sizeObj.getHeight() : el.offsetHeight 571 | }; 572 | var resizeHandler = function(e) { 573 | var size = { 574 | w: sizeObj ? sizeObj.getWidth() : el.offsetWidth, 575 | h: sizeObj ? sizeObj.getHeight() : el.offsetHeight 576 | }; 577 | if (size.w === 0 && size.h === 0) 578 | return; 579 | if (size.w === lastSize.w && size.h === lastSize.h) 580 | return; 581 | lastSize = size; 582 | binding.resize(el, size.w, size.h, initResult); 583 | }; 584 | 585 | on(window, "resize", resizeHandler); 586 | 587 | // This is needed for cases where we're running in a Shiny 588 | // app, but the widget itself is not a Shiny output, but 589 | // rather a simple static widget. One example of this is 590 | // an rmarkdown document that has runtime:shiny and widget 591 | // that isn't in a render function. Shiny only knows to 592 | // call resize handlers for Shiny outputs, not for static 593 | // widgets, so we do it ourselves. 594 | if (window.jQuery) { 595 | window.jQuery(document).on( 596 | "shown.htmlwidgets shown.bs.tab.htmlwidgets shown.bs.collapse.htmlwidgets", 597 | resizeHandler 598 | ); 599 | window.jQuery(document).on( 600 | "hidden.htmlwidgets hidden.bs.tab.htmlwidgets hidden.bs.collapse.htmlwidgets", 601 | resizeHandler 602 | ); 603 | } 604 | 605 | // This is needed for the specific case of ioslides, which 606 | // flips slides between display:none and display:block. 607 | // Ideally we would not have to have ioslide-specific code 608 | // here, but rather have ioslides raise a generic event, 609 | // but the rmarkdown package just went to CRAN so the 610 | // window to getting that fixed may be long. 611 | if (window.addEventListener) { 612 | // It's OK to limit this to window.addEventListener 613 | // browsers because ioslides itself only supports 614 | // such browsers. 615 | on(document, "slideenter", resizeHandler); 616 | on(document, "slideleave", resizeHandler); 617 | } 618 | } 619 | 620 | var scriptData = document.querySelector("script[data-for='" + el.id + "'][type='application/json']"); 621 | if (scriptData) { 622 | var data = JSON.parse(scriptData.textContent || scriptData.text); 623 | // Resolve strings marked as javascript literals to objects 624 | if (!(data.evals instanceof Array)) data.evals = [data.evals]; 625 | for (var k = 0; data.evals && k < data.evals.length; k++) { 626 | window.HTMLWidgets.evaluateStringMember(data.x, data.evals[k]); 627 | } 628 | binding.renderValue(el, data.x, initResult); 629 | evalAndRun(data.jsHooks.render, initResult, [el, data.x]); 630 | } 631 | }); 632 | }); 633 | 634 | invokePostRenderHandlers(); 635 | } 636 | 637 | // Wait until after the document has loaded to render the widgets. 638 | if (document.addEventListener) { 639 | document.addEventListener("DOMContentLoaded", function() { 640 | document.removeEventListener("DOMContentLoaded", arguments.callee, false); 641 | window.HTMLWidgets.staticRender(); 642 | }, false); 643 | } else if (document.attachEvent) { 644 | document.attachEvent("onreadystatechange", function() { 645 | if (document.readyState === "complete") { 646 | document.detachEvent("onreadystatechange", arguments.callee); 647 | window.HTMLWidgets.staticRender(); 648 | } 649 | }); 650 | } 651 | 652 | 653 | window.HTMLWidgets.getAttachmentUrl = function(depname, key) { 654 | // If no key, default to the first item 655 | if (typeof(key) === "undefined") 656 | key = 1; 657 | 658 | var link = document.getElementById(depname + "-" + key + "-attachment"); 659 | if (!link) { 660 | throw new Error("Attachment " + depname + "/" + key + " not found in document"); 661 | } 662 | return link.getAttribute("href"); 663 | }; 664 | 665 | window.HTMLWidgets.dataframeToD3 = function(df) { 666 | var names = []; 667 | var length; 668 | for (var name in df) { 669 | if (df.hasOwnProperty(name)) 670 | names.push(name); 671 | if (typeof(df[name]) !== "object" || typeof(df[name].length) === "undefined") { 672 | throw new Error("All fields must be arrays"); 673 | } else if (typeof(length) !== "undefined" && length !== df[name].length) { 674 | throw new Error("All fields must be arrays of the same length"); 675 | } 676 | length = df[name].length; 677 | } 678 | var results = []; 679 | var item; 680 | for (var row = 0; row < length; row++) { 681 | item = {}; 682 | for (var col = 0; col < names.length; col++) { 683 | item[names[col]] = df[names[col]][row]; 684 | } 685 | results.push(item); 686 | } 687 | return results; 688 | }; 689 | 690 | window.HTMLWidgets.transposeArray2D = function(array) { 691 | if (array.length === 0) return array; 692 | var newArray = array[0].map(function(col, i) { 693 | return array.map(function(row) { 694 | return row[i] 695 | }) 696 | }); 697 | return newArray; 698 | }; 699 | // Split value at splitChar, but allow splitChar to be escaped 700 | // using escapeChar. Any other characters escaped by escapeChar 701 | // will be included as usual (including escapeChar itself). 702 | function splitWithEscape(value, splitChar, escapeChar) { 703 | var results = []; 704 | var escapeMode = false; 705 | var currentResult = ""; 706 | for (var pos = 0; pos < value.length; pos++) { 707 | if (!escapeMode) { 708 | if (value[pos] === splitChar) { 709 | results.push(currentResult); 710 | currentResult = ""; 711 | } else if (value[pos] === escapeChar) { 712 | escapeMode = true; 713 | } else { 714 | currentResult += value[pos]; 715 | } 716 | } else { 717 | currentResult += value[pos]; 718 | escapeMode = false; 719 | } 720 | } 721 | if (currentResult !== "") { 722 | results.push(currentResult); 723 | } 724 | return results; 725 | } 726 | // Function authored by Yihui/JJ Allaire 727 | window.HTMLWidgets.evaluateStringMember = function(o, member) { 728 | var parts = splitWithEscape(member, '.', '\\'); 729 | for (var i = 0, l = parts.length; i < l; i++) { 730 | var part = parts[i]; 731 | // part may be a character or 'numeric' member name 732 | if (o !== null && typeof o === "object" && part in o) { 733 | if (i == (l - 1)) { // if we are at the end of the line then evalulate 734 | if (typeof o[part] === "string") 735 | o[part] = eval("(" + o[part] + ")"); 736 | } else { // otherwise continue to next embedded object 737 | o = o[part]; 738 | } 739 | } 740 | } 741 | }; 742 | 743 | // Retrieve the HTMLWidget instance (i.e. the return value of an 744 | // HTMLWidget binding's initialize() or factory() function) 745 | // associated with an element, or null if none. 746 | window.HTMLWidgets.getInstance = function(el) { 747 | return elementData(el, "init_result"); 748 | }; 749 | 750 | // Finds the first element in the scope that matches the selector, 751 | // and returns the HTMLWidget instance (i.e. the return value of 752 | // an HTMLWidget binding's initialize() or factory() function) 753 | // associated with that element, if any. If no element matches the 754 | // selector, or the first matching element has no HTMLWidget 755 | // instance associated with it, then null is returned. 756 | // 757 | // The scope argument is optional, and defaults to window.document. 758 | window.HTMLWidgets.find = function(scope, selector) { 759 | if (arguments.length == 1) { 760 | selector = scope; 761 | scope = document; 762 | } 763 | 764 | var el = scope.querySelector(selector); 765 | if (el === null) { 766 | return null; 767 | } else { 768 | return window.HTMLWidgets.getInstance(el); 769 | } 770 | }; 771 | 772 | // Finds all elements in the scope that match the selector, and 773 | // returns the HTMLWidget instances (i.e. the return values of 774 | // an HTMLWidget binding's initialize() or factory() function) 775 | // associated with the elements, in an array. If elements that 776 | // match the selector don't have an associated HTMLWidget 777 | // instance, the returned array will contain nulls. 778 | // 779 | // The scope argument is optional, and defaults to window.document. 780 | window.HTMLWidgets.findAll = function(scope, selector) { 781 | if (arguments.length == 1) { 782 | selector = scope; 783 | scope = document; 784 | } 785 | 786 | var nodes = scope.querySelectorAll(selector); 787 | var results = []; 788 | for (var i = 0; i < nodes.length; i++) { 789 | results.push(window.HTMLWidgets.getInstance(nodes[i])); 790 | } 791 | return results; 792 | }; 793 | 794 | var postRenderHandlers = []; 795 | function invokePostRenderHandlers() { 796 | while (postRenderHandlers.length) { 797 | var handler = postRenderHandlers.shift(); 798 | if (handler) { 799 | handler(); 800 | } 801 | } 802 | } 803 | 804 | // Register the given callback function to be invoked after the 805 | // next time static widgets are rendered. 806 | window.HTMLWidgets.addPostRenderHandler = function(callback) { 807 | postRenderHandlers.push(callback); 808 | }; 809 | 810 | // Takes a new-style instance-bound definition, and returns an 811 | // old-style class-bound definition. This saves us from having 812 | // to rewrite all the logic in this file to accomodate both 813 | // types of definitions. 814 | function createLegacyDefinitionAdapter(defn) { 815 | var result = { 816 | name: defn.name, 817 | type: defn.type, 818 | initialize: function(el, width, height) { 819 | return defn.factory(el, width, height); 820 | }, 821 | renderValue: function(el, x, instance) { 822 | return instance.renderValue(x); 823 | }, 824 | resize: function(el, width, height, instance) { 825 | return instance.resize(width, height); 826 | } 827 | }; 828 | 829 | if (defn.find) 830 | result.find = defn.find; 831 | if (defn.renderError) 832 | result.renderError = defn.renderError; 833 | if (defn.clearError) 834 | result.clearError = defn.clearError; 835 | 836 | return result; 837 | } 838 | })(); 839 | 840 | -------------------------------------------------------------------------------- /docs/link.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 12 | 13 | -------------------------------------------------------------------------------- /docs/pkgdown.css: -------------------------------------------------------------------------------- 1 | /* Sticky footer */ 2 | 3 | /** 4 | * Basic idea: https://philipwalton.github.io/solved-by-flexbox/demos/sticky-footer/ 5 | * Details: https://github.com/philipwalton/solved-by-flexbox/blob/master/assets/css/components/site.css 6 | * 7 | * .Site -> body > .container 8 | * .Site-content -> body > .container .row 9 | * .footer -> footer 10 | * 11 | * Key idea seems to be to ensure that .container and __all its parents__ 12 | * have height set to 100% 13 | * 14 | */ 15 | 16 | html, body { 17 | height: 100%; 18 | } 19 | 20 | body > .container { 21 | display: flex; 22 | height: 100%; 23 | flex-direction: column; 24 | 25 | padding-top: 60px; 26 | } 27 | 28 | body > .container .row { 29 | flex: 1 0 auto; 30 | } 31 | 32 | footer { 33 | margin-top: 45px; 34 | padding: 35px 0 36px; 35 | border-top: 1px solid #e5e5e5; 36 | color: #666; 37 | display: flex; 38 | flex-shrink: 0; 39 | } 40 | footer p { 41 | margin-bottom: 0; 42 | } 43 | footer div { 44 | flex: 1; 45 | } 46 | footer .pkgdown { 47 | text-align: right; 48 | } 49 | footer p { 50 | margin-bottom: 0; 51 | } 52 | 53 | img.icon { 54 | float: right; 55 | } 56 | 57 | img { 58 | max-width: 100%; 59 | } 60 | 61 | /* Fix bug in bootstrap (only seen in firefox) */ 62 | summary { 63 | display: list-item; 64 | } 65 | 66 | /* Typographic tweaking ---------------------------------*/ 67 | 68 | .contents .page-header { 69 | margin-top: calc(-60px + 1em); 70 | } 71 | 72 | /* Section anchors ---------------------------------*/ 73 | 74 | a.anchor { 75 | margin-left: -30px; 76 | display:inline-block; 77 | width: 30px; 78 | height: 30px; 79 | visibility: hidden; 80 | 81 | background-image: url(./link.svg); 82 | background-repeat: no-repeat; 83 | background-size: 20px 20px; 84 | background-position: center center; 85 | } 86 | 87 | .hasAnchor:hover a.anchor { 88 | visibility: visible; 89 | } 90 | 91 | @media (max-width: 767px) { 92 | .hasAnchor:hover a.anchor { 93 | visibility: hidden; 94 | } 95 | } 96 | 97 | 98 | /* Fixes for fixed navbar --------------------------*/ 99 | 100 | .contents h1, .contents h2, .contents h3, .contents h4 { 101 | padding-top: 60px; 102 | margin-top: -40px; 103 | } 104 | 105 | /* Static header placement on mobile devices */ 106 | @media (max-width: 767px) { 107 | .navbar-fixed-top { 108 | position: absolute; 109 | } 110 | .navbar { 111 | padding: 0; 112 | } 113 | } 114 | 115 | 116 | /* Sidebar --------------------------*/ 117 | 118 | #sidebar { 119 | margin-top: 30px; 120 | } 121 | #sidebar h2 { 122 | font-size: 1.5em; 123 | margin-top: 1em; 124 | } 125 | 126 | #sidebar h2:first-child { 127 | margin-top: 0; 128 | } 129 | 130 | #sidebar .list-unstyled li { 131 | margin-bottom: 0.5em; 132 | } 133 | 134 | .orcid { 135 | height: 16px; 136 | vertical-align: middle; 137 | } 138 | 139 | /* Reference index & topics ----------------------------------------------- */ 140 | 141 | .ref-index th {font-weight: normal;} 142 | 143 | .ref-index td {vertical-align: top;} 144 | .ref-index .icon {width: 40px;} 145 | .ref-index .alias {width: 40%;} 146 | .ref-index-icons .alias {width: calc(40% - 40px);} 147 | .ref-index .title {width: 60%;} 148 | 149 | .ref-arguments th {text-align: right; padding-right: 10px;} 150 | .ref-arguments th, .ref-arguments td {vertical-align: top;} 151 | .ref-arguments .name {width: 20%;} 152 | .ref-arguments .desc {width: 80%;} 153 | 154 | /* Nice scrolling for wide elements --------------------------------------- */ 155 | 156 | table { 157 | display: block; 158 | overflow: auto; 159 | } 160 | 161 | /* Syntax highlighting ---------------------------------------------------- */ 162 | 163 | pre { 164 | word-wrap: normal; 165 | word-break: normal; 166 | border: 1px solid #eee; 167 | } 168 | 169 | pre, code { 170 | background-color: #f8f8f8; 171 | color: #333; 172 | } 173 | 174 | pre code { 175 | overflow: auto; 176 | word-wrap: normal; 177 | white-space: pre; 178 | } 179 | 180 | pre .img { 181 | margin: 5px 0; 182 | } 183 | 184 | pre .img img { 185 | background-color: #fff; 186 | display: block; 187 | height: auto; 188 | } 189 | 190 | code a, pre a { 191 | color: #375f84; 192 | } 193 | 194 | a.sourceLine:hover { 195 | text-decoration: none; 196 | } 197 | 198 | .fl {color: #1514b5;} 199 | .fu {color: #000000;} /* function */ 200 | .ch,.st {color: #036a07;} /* string */ 201 | .kw {color: #264D66;} /* keyword */ 202 | .co {color: #888888;} /* comment */ 203 | 204 | .message { color: black; font-weight: bolder;} 205 | .error { color: orange; font-weight: bolder;} 206 | .warning { color: #6A0366; font-weight: bolder;} 207 | 208 | /* Clipboard --------------------------*/ 209 | 210 | .hasCopyButton { 211 | position: relative; 212 | } 213 | 214 | .btn-copy-ex { 215 | position: absolute; 216 | right: 0; 217 | top: 0; 218 | visibility: hidden; 219 | } 220 | 221 | .hasCopyButton:hover button.btn-copy-ex { 222 | visibility: visible; 223 | } 224 | 225 | /* mark.js ----------------------------*/ 226 | 227 | mark { 228 | background-color: rgba(255, 255, 51, 0.5); 229 | border-bottom: 2px solid rgba(255, 153, 51, 0.3); 230 | padding: 1px; 231 | } 232 | 233 | /* vertical spacing after htmlwidgets */ 234 | .html-widget { 235 | margin-bottom: 10px; 236 | } 237 | -------------------------------------------------------------------------------- /docs/pkgdown.js: -------------------------------------------------------------------------------- 1 | /* http://gregfranko.com/blog/jquery-best-practices/ */ 2 | (function($) { 3 | $(function() { 4 | 5 | $("#sidebar") 6 | .stick_in_parent({offset_top: 40}) 7 | .on('sticky_kit:bottom', function(e) { 8 | $(this).parent().css('position', 'static'); 9 | }) 10 | .on('sticky_kit:unbottom', function(e) { 11 | $(this).parent().css('position', 'relative'); 12 | }); 13 | 14 | $('body').scrollspy({ 15 | target: '#sidebar', 16 | offset: 60 17 | }); 18 | 19 | $('[data-toggle="tooltip"]').tooltip(); 20 | 21 | var cur_path = paths(location.pathname); 22 | var links = $("#navbar ul li a"); 23 | var max_length = -1; 24 | var pos = -1; 25 | for (var i = 0; i < links.length; i++) { 26 | if (links[i].getAttribute("href") === "#") 27 | continue; 28 | // Ignore external links 29 | if (links[i].host !== location.host) 30 | continue; 31 | 32 | var nav_path = paths(links[i].pathname); 33 | 34 | var length = prefix_length(nav_path, cur_path); 35 | if (length > max_length) { 36 | max_length = length; 37 | pos = i; 38 | } 39 | } 40 | 41 | // Add class to parent
  • , and enclosing
  • if in dropdown 42 | if (pos >= 0) { 43 | var menu_anchor = $(links[pos]); 44 | menu_anchor.parent().addClass("active"); 45 | menu_anchor.closest("li.dropdown").addClass("active"); 46 | } 47 | }); 48 | 49 | function paths(pathname) { 50 | var pieces = pathname.split("/"); 51 | pieces.shift(); // always starts with / 52 | 53 | var end = pieces[pieces.length - 1]; 54 | if (end === "index.html" || end === "") 55 | pieces.pop(); 56 | return(pieces); 57 | } 58 | 59 | // Returns -1 if not found 60 | function prefix_length(needle, haystack) { 61 | if (needle.length > haystack.length) 62 | return(-1); 63 | 64 | // Special case for length-0 haystack, since for loop won't run 65 | if (haystack.length === 0) { 66 | return(needle.length === 0 ? 0 : -1); 67 | } 68 | 69 | for (var i = 0; i < haystack.length; i++) { 70 | if (needle[i] != haystack[i]) 71 | return(i); 72 | } 73 | 74 | return(haystack.length); 75 | } 76 | 77 | /* Clipboard --------------------------*/ 78 | 79 | function changeTooltipMessage(element, msg) { 80 | var tooltipOriginalTitle=element.getAttribute('data-original-title'); 81 | element.setAttribute('data-original-title', msg); 82 | $(element).tooltip('show'); 83 | element.setAttribute('data-original-title', tooltipOriginalTitle); 84 | } 85 | 86 | if(ClipboardJS.isSupported()) { 87 | $(document).ready(function() { 88 | var copyButton = ""; 89 | 90 | $(".examples, div.sourceCode").addClass("hasCopyButton"); 91 | 92 | // Insert copy buttons: 93 | $(copyButton).prependTo(".hasCopyButton"); 94 | 95 | // Initialize tooltips: 96 | $('.btn-copy-ex').tooltip({container: 'body'}); 97 | 98 | // Initialize clipboard: 99 | var clipboardBtnCopies = new ClipboardJS('[data-clipboard-copy]', { 100 | text: function(trigger) { 101 | return trigger.parentNode.textContent; 102 | } 103 | }); 104 | 105 | clipboardBtnCopies.on('success', function(e) { 106 | changeTooltipMessage(e.trigger, 'Copied!'); 107 | e.clearSelection(); 108 | }); 109 | 110 | clipboardBtnCopies.on('error', function() { 111 | changeTooltipMessage(e.trigger,'Press Ctrl+C or Command+C to copy'); 112 | }); 113 | }); 114 | } 115 | })(window.jQuery || window.$) 116 | -------------------------------------------------------------------------------- /docs/pkgdown.yml: -------------------------------------------------------------------------------- 1 | pandoc: 2.0.4 2 | pkgdown: 1.3.0 3 | pkgdown_sha: ~ 4 | articles: [] 5 | 6 | -------------------------------------------------------------------------------- /docs/reference/attrs.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Attributes — attrs • horizon 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 51 | 52 | 53 | 54 | 55 | 56 |
    57 |
    58 | 92 | 93 | 94 |
    95 | 96 |
    97 |
    98 | 103 | 104 |
    105 |

    Attributes to draw horizon plot.

    106 |
    107 | 108 |
    attrs(x, y, ...)
    109 | 110 |

    Arguments

    111 | 112 | 113 | 114 | 115 | 118 | 119 |
    x, y, ...

    List of name value pairs giving attributes to map to 116 | variables. The names for x and y aspects are typically omitted because 117 | they are so common; all other aspects must be named.

    120 | 121 |

    Coordinates

    122 | 123 | 124 |

    Valid coordinates (depending on layer).

      125 |
    • x, y

    • 126 |
    • val

    • 127 |
    128 | 129 |
    130 | 138 |
    139 | 140 |
    141 | 144 | 145 |
    146 |

    Site built with pkgdown 1.3.0.

    147 |
    148 |
    149 |
    150 | 151 | 152 | 153 | 154 | 155 | 156 | -------------------------------------------------------------------------------- /docs/reference/horizon-shiny.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Shiny bindings for horizon — horizon-shiny • horizon 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 52 | 53 | 54 | 55 | 56 | 57 |
    58 |
    59 | 93 | 94 | 95 |
    96 | 97 |
    98 |
    99 | 104 | 105 |
    106 |

    Output and render functions for using horizon within Shiny 107 | applications and interactive Rmd documents.

    108 |
    109 | 110 |
    horizonOutput(outputId, width = "100%", height = "400px")
    111 | 
    112 | renderHorizon(expr, env = parent.frame(), quoted = FALSE)
    113 | 114 |

    Arguments

    115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 139 | 140 |
    outputId

    output variable to read from

    width, height

    Must be a valid CSS unit (like '100%', 124 | '400px', 'auto') or a number, which will be coerced to a 125 | string and have 'px' appended.

    expr

    An expression that generates a horizon

    env

    The environment in which to evaluate expr.

    quoted

    Is expr a quoted expression (with quote())? This 138 | is useful if you want to save an expression in a variable.

    141 | 142 | 143 |
    144 | 151 |
    152 | 153 |
    154 | 157 | 158 |
    159 |

    Site built with pkgdown 1.3.0.

    160 |
    161 |
    162 |
    163 | 164 | 165 | 166 | 167 | 168 | 169 | -------------------------------------------------------------------------------- /docs/reference/horizon.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Initialise — horizon • horizon 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 51 | 52 | 53 | 54 | 55 | 56 |
    57 |
    58 | 92 | 93 | 94 |
    95 | 96 |
    97 |
    98 | 103 | 104 |
    105 |

    Initialise an horizon plot.

    106 |
    107 | 108 |
    horizon(data, ..., utc = TRUE, military = TRUE, bands = 4L,
    109 |   mode = c("offset", "mirror"), normalize = FALSE, scale = 1L,
    110 |   positive_colors = c("white", "midnightBlue"),
    111 |   negative_colors = c("white", "crimson"), interpolation_curve = NULL,
    112 |   ruler = TRUE, zoom = FALSE, transition = FALSE,
    113 |   positive_colors_stops = NULL, negative_colors_stops = NULL,
    114 |   aggregation = NULL, extent = NULL, width = "100%",
    115 |   height = NULL, elementId = NULL)
    116 | 117 |

    Arguments

    118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 147 | 148 | 149 | 150 | 156 | 157 | 158 | 159 | 163 | 164 | 165 | 166 | 168 | 169 | 170 | 171 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 196 | 197 | 198 | 199 | 202 | 203 | 204 | 205 | 209 | 210 | 211 | 212 | 215 | 216 | 217 | 218 | 219 | 220 |
    data

    A data.frame or tibble.

    ...

    Attributes, specified using attrs.

    utc

    Whether to use the local timezone (FALSE) 131 | or UTC (TRUE).

    military

    Whether to show time in 24h or 12h (am/pm) format.

    bands

    Number of horizon bands to use.

    mode

    Mode used to represent negative values. 144 | offset renders the negative values from the top of the chart 145 | downwards, while /codemirror represents them upwards as if they 146 | were positive values, albeit with a different color.

    normalize

    Whether to normalize all series Y axis to the same extent, 151 | in order to compare series in absolute terms. It defines the behavior of the 152 | dynamic calculation of the max Y, when extent is not explicitly set. 153 | If TRUE, the extent is calculated as the global maximum value of all 154 | the data points combined across all series. If FALSE, each series extent 155 | will be based on their own local maximum.

    scale

    Set the y-axis scale exponent. Only values > 0 are supported. 160 | An exponent of 1 (default) represents a linear Y scale. A function 161 | (JS) receives the series ID as input and should return a 162 | numeric value. A number sets the same scale exponent for all the series.

    positive_colors, negative_colors

    Colors to use for the positive and 167 | negative value bands.

    interpolation_curve

    Interpolation curve function used to draw lines between points. 172 | Should be a d3 curve function. A falsy value 173 | sets linear interpolation (curveLinear).

    ruler

    Whether to show the ruler.

    zoom

    Whether to enable pointer-based zoom interactions on the chart, along the time 182 | (x-axis) dimension.

    transition

    Duration (in milliseconds) of the transitions between data states.

    positive_colors_stops, negative_colors_stops

    Each stop represents an 191 | interpolation ratio and only values between ]0, 1[ (excluding) are permitted. 192 | The stops are used to interpolate the middle colors in *_colors and are 193 | only applicable if there are more than 2 colors. If the number of stops is less 194 | than number of middle colors, linear interpolation is used to populate the remaining 195 | stops. A value of NULL (default) results in complete linear interpolation.

    aggregation

    A function (JS) to reduce multiple values 200 | to a single number, in case there is more than one y value per unique x 201 | and group (in attrs).

    extent

    JavaScript function or number to set the y-axis maximum absolute value. 206 | By default (NULL), the max Y is calculated dynamically from the data. 207 | A function (JS) receives the series ID as input and should return 208 | a numeric value. A numeric sets the same extent for all the series.

    width, height

    Must be a valid CSS unit (like '100%', 213 | '400px', 'auto') or a number, which will be coerced to a 214 | string and have 'px' appended.

    elementId

    Id of element.

    221 | 222 | 223 |

    Examples

    224 |
    horizon(tsdata, attrs(x = dates, y = value, group = grp))
    225 |
    226 | 234 |
    235 | 236 |
    237 | 240 | 241 |
    242 |

    Site built with pkgdown 1.3.0.

    243 |
    244 |
    245 |
    246 | 247 | 248 | 249 | 250 | 251 | 252 | -------------------------------------------------------------------------------- /docs/reference/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Function reference • horizon 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 49 | 50 | 51 | 52 | 53 | 54 |
    55 |
    56 | 90 | 91 | 92 |
    93 | 94 |
    95 |
    96 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 114 | 115 | 116 | 117 | 120 | 121 | 122 | 123 | 126 | 127 | 128 | 129 | 132 | 133 | 134 | 135 | 138 | 139 | 140 | 141 |
    111 |

    All functions

    112 |

    113 |
    118 |

    attrs()

    119 |

    Attributes

    124 |

    horizonOutput() renderHorizon()

    125 |

    Shiny bindings for horizon

    130 |

    horizon()

    131 |

    Initialise

    136 |

    tsdata

    137 |

    Random data

    142 |
    143 | 144 | 150 |
    151 | 152 |
    153 | 156 | 157 |
    158 |

    Site built with pkgdown 1.3.0.

    159 |
    160 |
    161 |
    162 | 163 | 164 | 165 | 166 | 167 | 168 | -------------------------------------------------------------------------------- /docs/reference/pipe.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Pipe operator — %>% • horizon 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 51 | 52 | 53 | 54 | 55 | 56 |
    57 |
    58 | 92 | 93 | 94 |
    95 | 96 |
    97 |
    98 | 103 | 104 |
    105 |

    See magrittr::%>% for details.

    106 |
    107 | 108 |
    lhs %>% rhs
    109 | 110 | 111 | 112 |
    113 | 119 |
    120 | 121 |
    122 | 125 | 126 |
    127 |

    Site built with pkgdown 1.3.0.

    128 |
    129 |
    130 |
    131 | 132 | 133 | 134 | 135 | 136 | 137 | -------------------------------------------------------------------------------- /docs/reference/tsdata.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Random data — tsdata • horizon 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 51 | 52 | 53 | 54 | 55 | 56 |
    57 |
    58 | 92 | 93 | 94 |
    95 | 96 |
    97 |
    98 | 103 | 104 |
    105 |

    Generated data for examples.

    106 |
    107 | 108 |
    tsdata
    109 | 110 | 111 |

    Format

    112 | 113 |

    An data.frame object with 5000 rows and 3 columns

    114 |
    date

    Date sequence

    115 |
    value

    A number

    116 |
    group

    A group name

    117 |
    118 | 119 |
    120 | 127 |
    128 | 129 | 138 |
    139 | 140 | 141 | 142 | 143 | 144 | 145 | -------------------------------------------------------------------------------- /inst/htmlwidgets/horizonplot.js: -------------------------------------------------------------------------------- 1 | HTMLWidgets.widget({ 2 | 3 | name: 'horizonplot', 4 | 5 | type: 'output', 6 | 7 | factory: function(el, width, height) { 8 | 9 | var chart, 10 | dom, 11 | data; 12 | 13 | return { 14 | 15 | renderValue: function(x) { 16 | 17 | for (i = 0; i < x.data.length; i++) { 18 | x.data[i][x.ts] = new Date(x.data[i][x.ts]) 19 | } 20 | 21 | console.log(x.data) 22 | 23 | dom = document.getElementById(el.id); 24 | chart = HorizonTSChart(); 25 | chart 26 | .data(x.data) 27 | .width(dom.offsetWidth) 28 | .height(dom.offsetHeight) 29 | .series(x.series) 30 | .ts(x.ts) 31 | .val(x.val) 32 | .useUtc(x.useUtc) 33 | .use24h(x.use24h) 34 | .horizonBands(x.horizonBands) 35 | .horizonMode(x.horizonMode) 36 | .yNormalize(x.yNormalize) 37 | .yScaleExp(x.yScaleExp) 38 | .positiveColors(x.positiveColors) 39 | .negativeColors(x.negativeColors) 40 | .showRuler(x.showRuler) 41 | .enableZoom(x.enableZoom) 42 | .transitionDuration(x.transitionDuration) 43 | (dom); 44 | 45 | if(x.hasOwnProperty(x.yAggregation)) 46 | chart.yAggregation(x.yAggregation) 47 | 48 | if(x.hasOwnProperty(x.positiveColorStops)) 49 | chart.positiveColorStops(x.positiveColorStops) 50 | 51 | if(x.hasOwnProperty(x.negativeColorStops)) 52 | chart.negativeColorStops(x.negativeColorStops) 53 | 54 | if(x.hasOwnProperty(x.yExtent)) 55 | chart.yExtent(x.yExtent) 56 | 57 | if(x.hasOwnProperty(x.interpolationCurve)) 58 | chart.interpolationCurve(x.interpolationCurve) 59 | }, 60 | 61 | resize: function(width, height) { 62 | 63 | console.log(dom.offsetHeight); 64 | 65 | chart 66 | .width(dom.offsetWidth) 67 | .height(dom.offsetHeight); 68 | 69 | } 70 | 71 | }; 72 | } 73 | }); -------------------------------------------------------------------------------- /inst/htmlwidgets/horizonplot.yaml: -------------------------------------------------------------------------------- 1 | dependencies: 2 | - name: horizonplot 3 | version: 1.6.1 4 | src: htmlwidgets/lib/horizon 5 | script: horizon-timeseries-chart.min.js 6 | -------------------------------------------------------------------------------- /man/attrs.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/attrs.R 3 | \name{attrs} 4 | \alias{attrs} 5 | \title{Attributes} 6 | \usage{ 7 | attrs(x, y, ...) 8 | } 9 | \arguments{ 10 | \item{x, y, ...}{List of name value pairs giving attributes to map to 11 | variables. The names for x and y aspects are typically omitted because 12 | they are so common; all other aspects must be named.} 13 | } 14 | \description{ 15 | Attributes to draw horizon plot. 16 | } 17 | \section{Coordinates}{ 18 | 19 | Valid coordinates (depending on layer). 20 | \itemize{ 21 | \item{\code{x}, \code{y}} 22 | \item{\code{val}} 23 | } 24 | } 25 | 26 | -------------------------------------------------------------------------------- /man/figures/README-example-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohnCoene/horizonplot/75b052f6a93a673ccab1d3268a8b552339d876c3/man/figures/README-example-1.png -------------------------------------------------------------------------------- /man/horizon-shiny.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/horizon.R 3 | \name{horizon-shiny} 4 | \alias{horizon-shiny} 5 | \alias{horizonOutput} 6 | \alias{renderHorizon} 7 | \title{Shiny bindings for horizon} 8 | \usage{ 9 | horizonOutput(outputId, width = "100\%", height = "400px") 10 | 11 | renderHorizon(expr, env = parent.frame(), quoted = FALSE) 12 | } 13 | \arguments{ 14 | \item{outputId}{output variable to read from} 15 | 16 | \item{width, height}{Must be a valid CSS unit (like \code{'100\%'}, 17 | \code{'400px'}, \code{'auto'}) or a number, which will be coerced to a 18 | string and have \code{'px'} appended.} 19 | 20 | \item{expr}{An expression that generates a horizon} 21 | 22 | \item{env}{The environment in which to evaluate \code{expr}.} 23 | 24 | \item{quoted}{Is \code{expr} a quoted expression (with \code{quote()})? This 25 | is useful if you want to save an expression in a variable.} 26 | } 27 | \description{ 28 | Output and render functions for using horizon within Shiny 29 | applications and interactive Rmd documents. 30 | } 31 | -------------------------------------------------------------------------------- /man/horizon.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/horizon.R 3 | \name{horizon} 4 | \alias{horizon} 5 | \title{Initialise} 6 | \usage{ 7 | horizon( 8 | data, 9 | ..., 10 | utc = TRUE, 11 | military = TRUE, 12 | bands = 4L, 13 | mode = c("offset", "mirror"), 14 | normalize = FALSE, 15 | scale = 1L, 16 | positive_colors = c("white", "midnightBlue"), 17 | negative_colors = c("white", "crimson"), 18 | interpolation_curve = NULL, 19 | ruler = TRUE, 20 | zoom = FALSE, 21 | transition = FALSE, 22 | positive_colors_stops = NULL, 23 | negative_colors_stops = NULL, 24 | aggregation = NULL, 25 | extent = NULL, 26 | width = "100\%", 27 | height = NULL, 28 | elementId = NULL 29 | ) 30 | } 31 | \arguments{ 32 | \item{data}{A \code{data.frame} or \link[tibble]{tibble}.} 33 | 34 | \item{...}{Attributes, specified using \code{\link{attrs}}.} 35 | 36 | \item{utc}{Whether to use the local timezone (\code{FALSE}) 37 | or UTC (\code{TRUE}).} 38 | 39 | \item{military}{Whether to show time in 24h or 12h (am/pm) format.} 40 | 41 | \item{bands}{Number of horizon bands to use.} 42 | 43 | \item{mode}{Mode used to represent negative values. 44 | \code{offset} renders the negative values from the top of the chart 45 | downwards, while /code{mirror} represents them upwards as if they 46 | were positive values, albeit with a different color.} 47 | 48 | \item{normalize}{Whether to normalize all series Y axis to the same extent, 49 | in order to compare series in absolute terms. It defines the behavior of the 50 | dynamic calculation of the max Y, when \code{extent} is not explicitly set. 51 | If \code{TRUE}, the extent is calculated as the global maximum value of all 52 | the data points combined across all series. If \code{FALSE}, each series extent 53 | will be based on their own local maximum.} 54 | 55 | \item{scale}{Set the y-axis scale exponent. Only values > 0 are supported. 56 | An exponent of 1 (default) represents a linear Y scale. A function 57 | (\link[htmlwidgets]{JS}) receives the series ID as input and should return a 58 | numeric value. A number sets the same scale exponent for all the series.} 59 | 60 | \item{positive_colors, negative_colors}{Colors to use for the positive and 61 | negative value bands.} 62 | 63 | \item{interpolation_curve}{Interpolation curve function used to draw lines between points. 64 | Should be a \href{https://github.com/d3/d3-shape#curves}{d3 curve function}. A falsy value 65 | sets linear interpolation (\href{https://github.com/d3/d3-shape#curveLinear}{curveLinear}).} 66 | 67 | \item{ruler}{Whether to show the ruler.} 68 | 69 | \item{zoom}{Whether to enable pointer-based zoom interactions on the chart, along the time 70 | (x-axis) dimension.} 71 | 72 | \item{transition}{Duration (in milliseconds) of the transitions between data states.} 73 | 74 | \item{positive_colors_stops, negative_colors_stops}{Each stop represents an 75 | interpolation ratio and only values between \code{]0, 1[} (excluding) are permitted. 76 | The stops are used to interpolate the middle colors in \code{*_colors} and are 77 | only applicable if there are more than 2 colors. If the number of stops is less 78 | than number of middle colors, linear interpolation is used to populate the remaining 79 | stops. A value of \code{NULL} (default) results in complete linear interpolation.} 80 | 81 | \item{aggregation}{A function (\link[htmlwidgets]{JS}) to reduce multiple values 82 | to a single number, in case there is more than one \code{y} value per unique \code{x} 83 | and \code{group} (in \code{\link{attrs}}).} 84 | 85 | \item{extent}{JavaScript function or number to set the y-axis maximum absolute value. 86 | By default (\code{NULL}), the max Y is calculated dynamically from the data. 87 | A function (\link[htmlwidgets]{JS}) receives the series ID as input and should return 88 | a numeric value. A numeric sets the same extent for all the series.} 89 | 90 | \item{width, height}{Must be a valid CSS unit (like \code{'100\%'}, 91 | \code{'400px'}, \code{'auto'}) or a number, which will be coerced to a 92 | string and have \code{'px'} appended.} 93 | 94 | \item{elementId}{Id of element.} 95 | } 96 | \description{ 97 | Initialise an horizon plot. 98 | } 99 | \examples{ 100 | horizon(tsdata, attrs(x = dates, y = value, group = grp)) 101 | 102 | } 103 | -------------------------------------------------------------------------------- /man/pipe.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utils-pipe.R 3 | \name{\%>\%} 4 | \alias{\%>\%} 5 | \title{Pipe operator} 6 | \usage{ 7 | lhs \%>\% rhs 8 | } 9 | \description{ 10 | See \code{magrittr::\link[magrittr:pipe]{\%>\%}} for details. 11 | } 12 | \keyword{internal} 13 | -------------------------------------------------------------------------------- /man/tsdata.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/data.R 3 | \docType{data} 4 | \name{tsdata} 5 | \alias{tsdata} 6 | \title{Random data} 7 | \format{ 8 | An data.frame object with 5000 rows and 3 columns 9 | \describe{ 10 | \item{\code{date}}{ Date sequence} 11 | \item{\code{value}}{ A number} 12 | \item{\code{group}}{ A group name} 13 | } 14 | } 15 | \usage{ 16 | tsdata 17 | } 18 | \description{ 19 | Generated data for examples. 20 | } 21 | \keyword{datasets} 22 | --------------------------------------------------------------------------------