Skip to content

Commit 85cbca3

Browse files
authored
Merge pull request #1283 from ropensci/mathjax2
MathJax support
2 parents 17870c7 + 06eabb4 commit 85cbca3

File tree

11 files changed

+219
-38
lines changed

11 files changed

+219
-38
lines changed

NAMESPACE

+1
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ S3method(to_basic,default)
8181
S3method(transmute_,plotly)
8282
S3method(ungroup,plotly)
8383
export("%>%")
84+
export(TeX)
8485
export(add_annotations)
8586
export(add_area)
8687
export(add_bars)

NEWS.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
* The `plot_geo()` function gains a `offline` argument for rendering `"scattergeo"` traces with or without an internet connection (#356). Leveraging this argument requires the new **plotlyGeoAssets** package.
2323
* Instead of an error, `ggplotly(NULL, "message")` and `plotly_build(NULL, "message")` now returns `htmltools::div("message")`, making it easier to relay messages in shiny when data isn't yet ready to plot (#1116).
2424
* The `animation_button()` function gains a `label` argument, making it easier to control the label of an animation button generated through the `frame` API (#1205).
25-
* Support for async rendering of inside **shiny** apps using the [promises](https://rstudio.github.io/promises/) package (#1209).
25+
* Support for async rendering of inside **shiny** apps using the [promises](https://rstudio.github.io/promises/) package (#1209). For an example, run `plotly_example("shiny", "async")`.
2626

2727
## CHANGES
2828

@@ -36,6 +36,7 @@
3636

3737
### Other changes relevant for all **plotly** objects
3838

39+
* All axis objects now default to `automargin = TRUE`. The majority of the time this should make axis labels more readable, but may have un-intended consequences in some rare cases (#1252).
3940
* The `elementId` field is no longer populated, which fixes the "Ignoring explicitly provided widget ID" warning in shiny applications (#985).
4041

4142
## BUG FIXES

R/layout.R

+29-5
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,13 @@ rangeslider <- function(p, start = NULL, end = NULL, ...) {
9191
#' @param collaborate include the collaborate mode bar button (unique to the R pkg)?
9292
#' @param cloud include the send data to cloud button?
9393
#' @param locale locale to use. See [here](https://github.com/plotly/plotly.js/tree/master/dist#to-include-localization) for more info.
94+
#' @param mathjax add [MathJax rendering support](https://github.com/plotly/plotly.js/tree/master/dist#to-support-mathjax).
95+
#' If `"cdn"`, mathjax is loaded externally (meaning an internet connection is needed for
96+
#' TeX rendering). If `"local"`, the PLOTLY_MATHJAX_PATH environment variable must be
97+
#' set to the location (a local file path) of MathJax. IMPORTANT: plotly uses SVG-based
98+
#' mathjax rendering which doesn't play nicely with HTML-based rendering (e.g., rmarkdown documents).
99+
#' In this case, consider `<iframe>`-ing your plotly graph(s) into the larger document
100+
#' (see [here](https://github.com/ropensci/plotly/blob/master/inst/examples/rmd/MathJax/index.Rmd) for an example).
94101
#' @author Carson Sievert
95102
#' @export
96103
#' @examples
@@ -103,21 +110,23 @@ rangeslider <- function(p, start = NULL, end = NULL, ...) {
103110
#' # remove the plotly logo and collaborate button from modebar
104111
#' config(p, displaylogo = FALSE, collaborate = FALSE)
105112
#'
113+
#' # enable mathjax
114+
#' # see more examples at https://plot.ly/r/LaTeX/
115+
#' plot_ly(x = c(1, 2, 3, 4), y = c(1, 4, 9, 16)) %>%
116+
#' layout(title = TeX("\\text{Some mathjax: }\\alpha+\\beta x")) %>%
117+
#' config(mathjax = "cdn")
118+
#'
106119
#' # japanese
107120
#' config(p, locale = "ja")
108121
#' # german
109122
#' config(p, locale = "de")
110-
#' # swiss-german
111-
#' config(p, locale = "de-CH")
112123
#' # spanish
113124
#' config(p, locale = "es")
114-
#' # french
115-
#' config(p, locale = "fr")
116125
#' # chinese
117126
#' config(p, locale = "zh-CN")
118127
#'
119128

120-
config <- function(p, ..., collaborate = TRUE, cloud = FALSE, locale = NULL) {
129+
config <- function(p, ..., collaborate = TRUE, cloud = FALSE, locale = NULL, mathjax = NULL) {
121130

122131
if (!is.null(locale)) {
123132
p$dependencies <- c(
@@ -127,6 +136,21 @@ config <- function(p, ..., collaborate = TRUE, cloud = FALSE, locale = NULL) {
127136
p$x$config$locale <- locale
128137
}
129138

139+
if (!is.null(mathjax)) {
140+
mj <- switch(
141+
match.arg(mathjax, c("cdn", "local")),
142+
cdn = mathjax_cdn(),
143+
local = mathjax_local()
144+
)
145+
# if mathjax is already supplied overwrite it; otherwise, prepend it
146+
depNames <- sapply(p$dependencies, "[[", "name")
147+
if (any(idx <- depNames %in% "mathjax")) {
148+
p$dependencies[[which(idx)]] <- mathjax
149+
} else {
150+
p$dependencies <- c(list(mj), p$dependencies)
151+
}
152+
}
153+
130154
p$x$config <- modify_list(p$x$config, list(...))
131155

132156
nms <- sapply(p$x$config[["modeBarButtonsToAdd"]], "[[", "name")

R/mathjax.R

+78
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
#' Render TeX in a plotly graph using MathJax
2+
#'
3+
#' This function makes it slightly easier to render TeX in a plotly graph --
4+
#' it ensures that MathJax is included with the final result and also
5+
#' ensures the provided string is surrounded with `$` (this is what plotly.js
6+
#' uses to declare a string as TeX).
7+
#'
8+
#' @param x a character vector
9+
#' @export
10+
#' @seealso [config]
11+
#' @examples
12+
#'
13+
#' plot_ly(x = c(1, 2, 3, 4), y = c(1, 4, 9, 16)) %>%
14+
#' layout(title = TeX("\\text{Some mathjax: }\\alpha+\\beta x")) %>%
15+
#' config(mathjax = "cdn")
16+
17+
TeX <- function(x) {
18+
startsWithDollar <- grepl("^\\$", x)
19+
endsWithDollar <- grepl("\\$$", x)
20+
x <- paste0(if (!startsWithDollar) "$", x, if (!endsWithDollar) "$")
21+
prefix_class(x, "TeX")
22+
}
23+
24+
is.TeX <- function(x) {
25+
inherits(x, "TeX")
26+
}
27+
28+
mathjax_cdn <- function() {
29+
htmltools::htmlDependency(
30+
name = "mathjax",
31+
version = "2.7.4",
32+
src = c(file = depPath("mathjax")),
33+
script = "cdn.js"
34+
)
35+
}
36+
37+
# TODO: wait until there is a more official way to include query parameters?
38+
# https://github.com/rstudio/htmltools/issues/98
39+
mathjax_local <- function() {
40+
path <- mathjax_path()
41+
42+
mj <- file.path(path, "MathJax.js")
43+
if (!file.exists(mj)) stop("Couldn't locate MathJax.js")
44+
45+
# parse the version
46+
mathjax <- readLines(mj)
47+
pat <- 'MathJax.fileversion="[0-9].[0-9].[0-9]'
48+
ver <- regmatches(mathjax, regexpr(pat, mathjax))
49+
ver <- sub('"', '', strsplit(ver, "=")[[1]][2])
50+
51+
# make sure we have access to the right config
52+
config <- file.path(path, "config", "TeX-AMS-MML_SVG.js")
53+
if (!file.exists(config)) stop("Couldn't locate necessary MathJax config: TeX-AMS-MML_SVG")
54+
55+
htmltools::htmlDependency(
56+
name = "mathjax",
57+
version = ver,
58+
src = path,
59+
script = c("MathJax.js", "config/TeX-AMS-MML_SVG.js")
60+
)
61+
}
62+
63+
64+
mathjax_path <- function() {
65+
path <- Sys.getenv("PLOTLY_MATHJAX_PATH", NA)
66+
67+
if (!is.na(path)) {
68+
mj <- file.path(path, "MathJax.js")
69+
if (!file.exists(mj)) stop("Couldn't find 'MathJax.js' file in local directory")
70+
return(path)
71+
}
72+
73+
stop(
74+
"To use a local version of MathJax with plotly, set the PLOTLY_MATHJAX_PATH",
75+
"environment variable to the location of MathJax.",
76+
call. = FALSE
77+
)
78+
}

R/orca.R

+5-22
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,10 @@
1111
#' Applies to all output images.
1212
#' @param height Sets the image height. If not set, defaults to `layout.height` value.
1313
#' Applies to all output images.
14-
#' @param mathjax whether or not to specify a path to mathjax (required to export LaTeX characters).
15-
#' This should 'just work' in RStudio, but outside RStudio, you may have to set
16-
#' the PLOTLY_MATHJAX_PATH environment variable to the location of MathJax.
14+
#' @param mathjax whether or not to include MathJax (required to render [TeX]).
15+
#' If `TRUE`, the PLOTLY_MATHJAX_PATH environment variable must be set and point
16+
#' to the location of MathJax (this variable is also used to render [TeX] in
17+
#' interactive graphs, see [config]).
1718
#' @param parallel_limit Sets the limit of parallel tasks run.
1819
#' @param verbose Turn on verbose logging on stdout.
1920
#' @param debug Starts app in debug mode and turn on verbose logs on stdout.
@@ -25,9 +26,7 @@
2526
#'
2627
#' \dontrun{
2728
#' p <- plot_ly(z = ~volcano) %>% add_surface()
28-
#' orca(p, "surface-plot.png")
2929
#' orca(p, "surface-plot.svg")
30-
#' orca(p, "surface-plot.pdf")
3130
#' }
3231
#'
3332

@@ -65,25 +64,9 @@ orca <- function(p, file = "plot.png", format = tools::file_ext(file),
6564
if (!is.null(height)) args <- c(args, "--height", height)
6665
if (!is.null(parallel_limit)) args <- c(args, "--parallel-limit", parallel_limit)
6766
if (!is.na(mapbox_token())) args <- c(args, "--mapbox-access-token", mapbox_token())
68-
if (isTRUE(mathjax)) args <- c(args, "--mathjax", mathjax_path())
67+
if (isTRUE(mathjax)) args <- c(args, "--mathjax", file.path(mathjax_path(), "MathJax.js"))
6968

7069
# TODO: point to local topojson? Should this only work if plot_geo(standalone = TRUE)?
7170
try_library("processx", "orca")
7271
invisible(processx::run("orca", args, echo = TRUE, spinner = TRUE))
7372
}
74-
75-
76-
mathjax_path <- function() {
77-
if (is_rstudio()) {
78-
try_library("rmarkdown", "orca")
79-
return(getFromNamespace("pandoc_mathjax_local_path", "rmarkdown")())
80-
}
81-
path <- Sys.getenv("PLOTLY_MATHJAX_PATH", Sys.getenv("RMARKDOWN_MATHJAX_PATH", NA))
82-
if (!is.na(path)) return(normalizePath(path, mustWork = TRUE))
83-
stop(
84-
"Please set either the RMARKDOWN_MATHJAX_PATH or PLOTLY_MATHJAX_PATH ",
85-
"environment variable to the location of MathJax. ",
86-
"On Linux systems you can also install MathJax using your system package manager.",
87-
call. = FALSE
88-
)
89-
}

R/plotly_build.R

+21
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,27 @@ plotly_build.plotly <- function(p, registerFrames = TRUE) {
371371
# box up 'data_array' attributes where appropriate
372372
p <- verify_attr_spec(p)
373373

374+
375+
verify_mathjax <- function(p) {
376+
hasMathjax <- "mathjax" %in% sapply(p$dependencies, "[[", "name")
377+
if (hasMathjax) return(p)
378+
379+
hasTeX <- any(rapply(p$x, is.TeX))
380+
if (!hasTeX) return(p)
381+
382+
# TODO: it would be much better to add the dependency here, but
383+
# htmlwidgets doesn't currently support adding dependencies at print-time!
384+
warning(
385+
"Detected the use of `TeX()`, but mathjax has not been specified. ",
386+
"Try running `config(.Last.value, mathjax = 'cdn')`",
387+
call. = FALSE
388+
)
389+
p
390+
}
391+
392+
# make sure we're including mathjax (if TeX() is used)
393+
p <- verify_mathjax(p)
394+
374395
# if a partial bundle was specified, make sure it supports the visualization
375396
p <- verify_partial_bundle(p)
376397

inst/examples/rmd/MathJax/index.Rmd

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
---
2+
title: "Render plotly MathJax inside rmarkdown"
3+
output: html_document
4+
---
5+
6+
Some HTML-based MathJax:
7+
8+
$$ \alpha+\beta $$
9+
10+
You _could_ print this **plotly** graph with SVG-based rendering, but it would break the HTML-based rendering of **rmarkdown**!
11+
12+
```{r message = FALSE}
13+
library(plotly)
14+
15+
p <- plotly_empty() %>%
16+
add_trace(x = 1, y = 1, text = TeX("\\alpha"), mode = "text", size = I(1000)) %>%
17+
config(mathjax = "cdn")
18+
```
19+
20+
Instead, use something like the **widgetframe** package to create a responsive iframe which ensure the SVG-based rendering that plotly requires is done independently of **rmarkdown**'s HTML-based rendering.
21+
22+
```{r}
23+
widgetframe::frameableWidget(p)
24+
```
25+
26+
Or, do it the old-fashioned way: save your plotly graph to an HTML file via `htmlwidgets::saveWidget()` then use an HTML `<iframe>`
27+
28+
```{r}
29+
htmlwidgets::saveWidget(p, "my-plotly-plot.html")
30+
```
31+
32+
33+
<iframe src="my-plotly-plot.html" width="100%" height="400" id="igraph" scrolling="no" seamless="seamless" frameBorder="0"> </iframe>

inst/htmlwidgets/lib/mathjax/cdn.js

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
var script = document.createElement("script");
2+
script.type = "text/javascript";
3+
script.src = "https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.4/MathJax.js?config=TeX-AMS-MML_SVG";
4+
document.getElementsByTagName("head")[0].appendChild(script);

man/TeX.Rd

+26
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/config.Rd

+16-5
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/orca.Rd

+4-5
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)