|
| 1 | +#' Use a partial bundle of plotly.js |
| 2 | +#' |
| 3 | +#' |
| 4 | +#' @param p a plotly object. |
| 5 | +#' @param type name of the (partial) bundle. The default `'auto'` attempts to |
| 6 | +#' @param version version number (e.g. 1.30.0). See [here](https://github.com/plotly/plotly.js/releases) |
| 7 | +#' for a list of valid versions and changelogs. |
| 8 | +#' @param local either "local", "cdn", or "rawgit". |
| 9 | +#' @param minified whether or not to use a minified js file (non-minified file can be useful for debugging plotly.js) |
| 10 | +#' @export |
| 11 | +#' @author Carson Sievert |
| 12 | +#' @references <https://github.com/plotly/plotly.js/blob/master/dist/README.md> |
| 13 | +#' @seealso `[plotly_bundle_info]()` |
| 14 | +#' |
| 15 | +#' @examples |
| 16 | +#' |
| 17 | +#' library(plotly) |
| 18 | +#' p1 <- plot_ly(x = 1:10, y = 1:10) %>% add_markers() |
| 19 | +#' p2 <- partial_bundle(p1) |
| 20 | +#' f1 <- tempfile(fileext = ".html") |
| 21 | +#' f2 <- tempfile(fileext = ".html") |
| 22 | +#' |
| 23 | +#' save_widget <- function(p, f) { |
| 24 | +#' owd <- setwd(dirname(f)) |
| 25 | +#' on.exit(setwd(owd)) |
| 26 | +#' htmlwidgets::saveWidget(p, f) |
| 27 | +#' mb <- round(file.info(f)$size / 1e6, 3) |
| 28 | +#' message("File is: ", mb," MB") |
| 29 | +#' } |
| 30 | +#' save_widget(p1, f1) |
| 31 | +#' save_widget(p2, f2) |
| 32 | +#' |
| 33 | + |
| 34 | + |
| 35 | +# TODO: implement type = 'auto' which would attempts to find the smallest partial bundle that can render `p`. |
| 36 | +# this would require, however, knowing the bundle -> trace mapping *for `p`'s plotly.js version* |
| 37 | +partial_bundle <- function(p, type = "auto", local = TRUE, minified = TRUE) { |
| 38 | + |
| 39 | + if (!is.plotly(p)) stop("The first argument to `partial_bundle()` must be a plotly object", call. = FALSE) |
| 40 | + |
| 41 | + # Amongst all the 'print-time' htmlwidget dependencies, |
| 42 | + # find the plotly.js dependency and attach some meta-info |
| 43 | + idx <- plotlyjsBundleIDX(p) |
| 44 | + p$dependencies[[idx]]$local <- local |
| 45 | + p$dependencies[[idx]]$minified <- minified |
| 46 | + p$dependencies[[idx]]$partial_bundle <- match.arg(type, c("auto", "main", names(bundleTraceMap))) |
| 47 | + |
| 48 | + plotly_build(p) |
| 49 | +} |
| 50 | + |
| 51 | +verify_partial_bundle <- function(p) { |
| 52 | + # return early if we're using the main bundle (the default) |
| 53 | + currentBundle <- plotlyjsBundle(p) |
| 54 | + bundleType <- currentBundle$partial_bundle %||% "main" |
| 55 | + if (identical(bundleType, "main")) return(p) |
| 56 | + |
| 57 | + # grab all the required trace types |
| 58 | + types <- unique(vapply(p$x$data, function(x) x[["type"]] %||% "scatter", character(1))) |
| 59 | + |
| 60 | + if (identical(bundleType, "auto")) { |
| 61 | + |
| 62 | + # resolve an auto bundle by using the 1st bundle that supports all the types |
| 63 | + # (ordering of bundleTraceMap is important!) |
| 64 | + for (i in seq_along(bundleTraceMap)) { |
| 65 | + if (all(types %in% bundleTraceMap[[i]])) { |
| 66 | + bundleType <- names(bundleTraceMap)[[i]] |
| 67 | + break |
| 68 | + } |
| 69 | + } |
| 70 | + |
| 71 | + if (identical(bundleType, "auto")) { |
| 72 | + message( |
| 73 | + "Couldn't find a single partial bundle that would support this plotly", |
| 74 | + "visualization. Using the main (full) bundle instead." |
| 75 | + ) |
| 76 | + p$dependencies[[plotlyjsBundleIDX(p)]] <- plotlyMainBundle() |
| 77 | + return(p) |
| 78 | + } |
| 79 | + |
| 80 | + } |
| 81 | + |
| 82 | + # verify that this partial bundle actually supports this viz |
| 83 | + # (at this point, bundleType should never be 'auto' or 'main') |
| 84 | + missingTypes <- setdiff(types, bundleTraceMap[[bundleType]]) |
| 85 | + if (length(missingTypes)) { |
| 86 | + msg <- sprint( |
| 87 | + "The '%s' bundle supports the following trace types: '%s'.\n\n", |
| 88 | + "This plotly visualization contains the following trace types: '%s'", |
| 89 | + bundle, paste(missingTypes, collapse = "', '"), paste(missingTypes, collapse = "', '") |
| 90 | + ) |
| 91 | + stop(msg, call. = FALSE) |
| 92 | + } |
| 93 | + |
| 94 | + idx <- plotlyjsBundleIDX(p) |
| 95 | + bundle_name <- sprintf("plotly-%s", bundleType) |
| 96 | + bundle_script <- sprintf( |
| 97 | + "plotly-%s-%s.%sjs", |
| 98 | + bundleType, currentBundle$version, |
| 99 | + if (isTRUE(currentBundle$minified)) "min." else "" |
| 100 | + ) |
| 101 | + |
| 102 | + p$dependencies[[idx]]$name <- bundle_name |
| 103 | + p$dependencies[[idx]]$script <- bundle_script |
| 104 | + p$dependencies[[idx]]$src <- list(href = "https://cdn.plot.ly") |
| 105 | + |
| 106 | + # download the relevant bundle |
| 107 | + if (isTRUE(p$dependencies[[idx]]$local)) { |
| 108 | + # TODO: implement a caching mechanism? |
| 109 | + try_library("curl", "partial_bundle") |
| 110 | + tmpfile <- file.path(tempdir(), bundle_script) |
| 111 | + p$dependencies[[idx]]$src$file <- dirname(tmpfile) |
| 112 | + if (!file.exists(tmpfile)) { |
| 113 | + curl::curl_download(paste0("https://cdn.plot.ly/", bundle_script), tmpfile) |
| 114 | + } |
| 115 | + } |
| 116 | + |
| 117 | + p |
| 118 | +} |
| 119 | + |
| 120 | +plotlyjsBundleIDX <- function(p) { |
| 121 | + depNames <- sapply(p$dependencies, "[[", "name") |
| 122 | + bundleNames <- paste0("plotly-", c("main", "auto", names(bundleTraceMap))) |
| 123 | + idx <- which(depNames %in% bundleNames) |
| 124 | + if (length(idx) != 1) stop("Couldn't find the plotlyjs bundle") |
| 125 | + idx |
| 126 | +} |
| 127 | + |
| 128 | +plotlyjsBundle <- function(p) { |
| 129 | + p$dependencies[[plotlyjsBundleIDX(p)]] |
| 130 | +} |
| 131 | + |
| 132 | + |
| 133 | +# TODO: create this object from the dist/README.md |
| 134 | +bundleTraceMap <- list( |
| 135 | + basic = c( |
| 136 | + "scatter", |
| 137 | + "bar", |
| 138 | + "pie" |
| 139 | + ), |
| 140 | + cartesian = c( |
| 141 | + "scatter", |
| 142 | + "bar", |
| 143 | + "pie", |
| 144 | + "box", |
| 145 | + "heatmap", |
| 146 | + "histogram", |
| 147 | + "histogram2d", |
| 148 | + "histogram2dcontour", |
| 149 | + "contour", |
| 150 | + "scatterternary", |
| 151 | + "violin" |
| 152 | + ), |
| 153 | + geo = c( |
| 154 | + "scatter", |
| 155 | + "scattergeo", |
| 156 | + "choropleth" |
| 157 | + ), |
| 158 | + gl3d = c( |
| 159 | + "scatter", |
| 160 | + "scatter3d", |
| 161 | + "surface", |
| 162 | + "mesh3d", |
| 163 | + "cone" |
| 164 | + ), |
| 165 | + gl2d = c( |
| 166 | + "scatter", |
| 167 | + "scattergl", |
| 168 | + "splom", |
| 169 | + "pointcloud", |
| 170 | + "heatmapgl", |
| 171 | + "contourgl", |
| 172 | + "parcoords" |
| 173 | + ), |
| 174 | + mapbox = c( |
| 175 | + "scatter", |
| 176 | + "scattermapbox" |
| 177 | + ), |
| 178 | + finance = c( |
| 179 | + "scatter", |
| 180 | + "bar", |
| 181 | + "pie", |
| 182 | + "histogram", |
| 183 | + "ohlc", |
| 184 | + "candlestick" |
| 185 | + ) |
| 186 | +) |
| 187 | + |
| 188 | + |
| 189 | + |
| 190 | +##' List trace types supported by a particular bundle |
| 191 | +##' |
| 192 | +##' @export |
| 193 | +#partial_bundle_info <- function() { |
| 194 | +# for (i in seq_along(bundle_traces)) { |
| 195 | +# bundle_name <- names(bundle_traces)[[i]] |
| 196 | +# msg <- sprintf( |
| 197 | +# "The '%s' bundle size is %s MB and contains the '%s' traces", |
| 198 | +# bundle_name, |
| 199 | +# round(file.info(bundle_file(bundle_name))$size / 1000000, 3), |
| 200 | +# paste(bundle_traces[[i]], collapse = "', '") |
| 201 | +# ) |
| 202 | +# message(msg) |
| 203 | +# } |
| 204 | +#} |
| 205 | +# |
| 206 | +#bundle_file <- function(bundle = "basic") { |
| 207 | +# bundle <- match.arg(bundle, names(bundle_traces)) |
| 208 | +# system.file("lib", paste0("plotly-", bundle, ".min.js"), package = "plotlyDepends") |
| 209 | +#} |
0 commit comments