Skip to content

Commit 194d435

Browse files
committed
add support for local MathJax; introduce TeX(); closes #375
1 parent 8cc9b91 commit 194d435

File tree

7 files changed

+159
-30
lines changed

7 files changed

+159
-30
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)

R/layout.R

+20-11
Original file line numberDiff line numberDiff line change
@@ -91,12 +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 whether or not to add [MathJax rendering support](https://github.com/plotly/plotly.js/tree/master/dist#to-support-mathjax).
95-
#' Note that plotly uses SVG-based mathjax rendering which won't play nicely with
96-
#' HTML-based rendering (e.g., rmarkdown documents). In this case, you may want to
97-
#' consider `<iframe>`-ing your plotly graph(s) into the larger document.
98-
#' Currently mathjax is loaded externally (meaning an internet
99-
#' connection is needed to view the graph). Future versions may not have this requirement.
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 to
96+
#' view the graph). If `local`, mathjax must be available locally. IMPORTANT: plotly uses
97+
#' SVG-based mathjax rendering which doesn't play nicely with HTML-based rendering
98+
#' (e.g., rmarkdown documents). In this case, you may want to consider `<iframe>`-ing
99+
#' 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.
100101
#' @author Carson Sievert
101102
#' @export
102103
#' @examples
@@ -125,7 +126,7 @@ rangeslider <- function(p, start = NULL, end = NULL, ...) {
125126
#' config(p, locale = "zh-CN")
126127
#'
127128

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

130131
if (!is.null(locale)) {
131132
p$dependencies <- c(
@@ -135,11 +136,19 @@ config <- function(p, ..., collaborate = TRUE, cloud = FALSE, locale = NULL, mat
135136
p$x$config$locale <- locale
136137
}
137138

138-
if (!identical(mathjax, FALSE)) {
139-
p$dependencies <- c(
140-
list(mathjax_cdn()),
141-
p$dependencies
139+
if (!is.null(mathjax)) {
140+
mj <- switch(
141+
match.arg(mathjax, c("cdn", "local")),
142+
cdn = mathjax_cdn(),
143+
local = mathjax_local()
142144
)
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+
}
143152
}
144153

145154
p$x$config <- modify_list(p$x$config, list(...))

R/mathjax.R

+50-12
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,30 @@
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+
128
mathjax_cdn <- function() {
229
htmltools::htmlDependency(
330
name = "mathjax",
@@ -7,34 +34,45 @@ mathjax_cdn <- function() {
734
)
835
}
936

10-
# TODO: wait until there is a more official way to include query parameters
37+
# TODO: wait until there is a more official way to include query parameters?
1138
# https://github.com/rstudio/htmltools/issues/98
1239
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+
1355
htmltools::htmlDependency(
14-
name = "mathjax",
15-
version = "2.7.4",
16-
src = c(file = mathjax_path()),
17-
script = "MathJax.js?config=TeX-AMS-MML_SVG"
56+
name = "mathjax",
57+
version = ver,
58+
src = path,
59+
script = c("MathJax.js", "config/TeX-AMS-MML_SVG.js")
1860
)
1961
}
2062

2163

2264
mathjax_path <- function() {
2365
path <- Sys.getenv("PLOTLY_MATHJAX_PATH", NA)
66+
2467
if (!is.na(path)) {
2568
mj <- file.path(path, "MathJax.js")
2669
if (!file.exists(mj)) stop("Couldn't find 'MathJax.js' file in local directory")
2770
return(path)
2871
}
2972

30-
if (system.file(package = "rmarkdown") != "") {
31-
# Note, this should throw an informative error if the path isn't found
32-
return(getFromNamespace("pandoc_mathjax_local_path", "rmarkdown")())
33-
}
34-
3573
stop(
36-
"To use a local version of MathJax with plotly, either set the PLOTLY_MATHJAX_PATH",
37-
"environment variable to the location of MathJax or install rmarkdown.",
74+
"To use a local version of MathJax with plotly, set the PLOTLY_MATHJAX_PATH",
75+
"environment variable to the location of MathJax.",
3876
call. = FALSE
3977
)
4078
}

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 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 = "$\\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. This ensures the SVG-based rendering that plotly requires is done in isolation (i.e., independent of **rmarkdown**'s HTML-based rendering).
21+
22+
```{r}
23+
widgetframe::frameableWidget(p)
24+
```
25+
26+
Or, do it the old-fashioned way by saving your plotly graph to an HTML file and use an HTML `<iframe>` to
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>

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

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

0 commit comments

Comments
 (0)