Skip to content

MathJax support #1283

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Jun 7, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ S3method(to_basic,default)
S3method(transmute_,plotly)
S3method(ungroup,plotly)
export("%>%")
export(TeX)
export(add_annotations)
export(add_area)
export(add_bars)
Expand Down
3 changes: 2 additions & 1 deletion NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
* 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.
* 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).
* 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).
* Support for async rendering of inside **shiny** apps using the [promises](https://rstudio.github.io/promises/) package (#1209).
* 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")`.

## CHANGES

Expand All @@ -36,6 +36,7 @@

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

* 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).
* The `elementId` field is no longer populated, which fixes the "Ignoring explicitly provided widget ID" warning in shiny applications (#985).

## BUG FIXES
Expand Down
34 changes: 29 additions & 5 deletions R/layout.R
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,13 @@ rangeslider <- function(p, start = NULL, end = NULL, ...) {
#' @param collaborate include the collaborate mode bar button (unique to the R pkg)?
#' @param cloud include the send data to cloud button?
#' @param locale locale to use. See [here](https://github.com/plotly/plotly.js/tree/master/dist#to-include-localization) for more info.
#' @param mathjax add [MathJax rendering support](https://github.com/plotly/plotly.js/tree/master/dist#to-support-mathjax).
#' If `"cdn"`, mathjax is loaded externally (meaning an internet connection is needed for
#' TeX rendering). If `"local"`, the PLOTLY_MATHJAX_PATH environment variable must be
#' set to the location (a local file path) of MathJax. IMPORTANT: plotly uses SVG-based
#' mathjax rendering which doesn't play nicely with HTML-based rendering (e.g., rmarkdown documents).
#' In this case, consider `<iframe>`-ing your plotly graph(s) into the larger document
#' (see [here](https://github.com/ropensci/plotly/blob/master/inst/examples/rmd/MathJax/index.Rmd) for an example).
#' @author Carson Sievert
#' @export
#' @examples
Expand All @@ -103,21 +110,23 @@ rangeslider <- function(p, start = NULL, end = NULL, ...) {
#' # remove the plotly logo and collaborate button from modebar
#' config(p, displaylogo = FALSE, collaborate = FALSE)
#'
#' # enable mathjax
#' # see more examples at https://plot.ly/r/LaTeX/
#' plot_ly(x = c(1, 2, 3, 4), y = c(1, 4, 9, 16)) %>%
#' layout(title = TeX("\\text{Some mathjax: }\\alpha+\\beta x")) %>%
#' config(mathjax = "cdn")
#'
#' # japanese
#' config(p, locale = "ja")
#' # german
#' config(p, locale = "de")
#' # swiss-german
#' config(p, locale = "de-CH")
#' # spanish
#' config(p, locale = "es")
#' # french
#' config(p, locale = "fr")
#' # chinese
#' config(p, locale = "zh-CN")
#'

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

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

if (!is.null(mathjax)) {
mj <- switch(
match.arg(mathjax, c("cdn", "local")),
cdn = mathjax_cdn(),
local = mathjax_local()
)
# if mathjax is already supplied overwrite it; otherwise, prepend it
depNames <- sapply(p$dependencies, "[[", "name")
if (any(idx <- depNames %in% "mathjax")) {
p$dependencies[[which(idx)]] <- mathjax
} else {
p$dependencies <- c(list(mj), p$dependencies)
}
}

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

nms <- sapply(p$x$config[["modeBarButtonsToAdd"]], "[[", "name")
Expand Down
78 changes: 78 additions & 0 deletions R/mathjax.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
#' Render TeX in a plotly graph using MathJax
#'
#' This function makes it slightly easier to render TeX in a plotly graph --
#' it ensures that MathJax is included with the final result and also
#' ensures the provided string is surrounded with `$` (this is what plotly.js
#' uses to declare a string as TeX).
#'
#' @param x a character vector
#' @export
#' @seealso [config]
#' @examples
#'
#' plot_ly(x = c(1, 2, 3, 4), y = c(1, 4, 9, 16)) %>%
#' layout(title = TeX("\\text{Some mathjax: }\\alpha+\\beta x")) %>%
#' config(mathjax = "cdn")

TeX <- function(x) {
startsWithDollar <- grepl("^\\$", x)
endsWithDollar <- grepl("\\$$", x)
x <- paste0(if (!startsWithDollar) "$", x, if (!endsWithDollar) "$")
prefix_class(x, "TeX")
}

is.TeX <- function(x) {
inherits(x, "TeX")
}

mathjax_cdn <- function() {
htmltools::htmlDependency(
name = "mathjax",
version = "2.7.4",
src = c(file = depPath("mathjax")),
script = "cdn.js"
)
}

# TODO: wait until there is a more official way to include query parameters?
# https://github.com/rstudio/htmltools/issues/98
mathjax_local <- function() {
path <- mathjax_path()

mj <- file.path(path, "MathJax.js")
if (!file.exists(mj)) stop("Couldn't locate MathJax.js")

# parse the version
mathjax <- readLines(mj)
pat <- 'MathJax.fileversion="[0-9].[0-9].[0-9]'
ver <- regmatches(mathjax, regexpr(pat, mathjax))
ver <- sub('"', '', strsplit(ver, "=")[[1]][2])

# make sure we have access to the right config
config <- file.path(path, "config", "TeX-AMS-MML_SVG.js")
if (!file.exists(config)) stop("Couldn't locate necessary MathJax config: TeX-AMS-MML_SVG")

htmltools::htmlDependency(
name = "mathjax",
version = ver,
src = path,
script = c("MathJax.js", "config/TeX-AMS-MML_SVG.js")
)
}


mathjax_path <- function() {
path <- Sys.getenv("PLOTLY_MATHJAX_PATH", NA)

if (!is.na(path)) {
mj <- file.path(path, "MathJax.js")
if (!file.exists(mj)) stop("Couldn't find 'MathJax.js' file in local directory")
return(path)
}

stop(
"To use a local version of MathJax with plotly, set the PLOTLY_MATHJAX_PATH",
"environment variable to the location of MathJax.",
call. = FALSE
)
}
27 changes: 5 additions & 22 deletions R/orca.R
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@
#' Applies to all output images.
#' @param height Sets the image height. If not set, defaults to `layout.height` value.
#' Applies to all output images.
#' @param mathjax whether or not to specify a path to mathjax (required to export LaTeX characters).
#' This should 'just work' in RStudio, but outside RStudio, you may have to set
#' the PLOTLY_MATHJAX_PATH environment variable to the location of MathJax.
#' @param mathjax whether or not to include MathJax (required to render [TeX]).
#' If `TRUE`, the PLOTLY_MATHJAX_PATH environment variable must be set and point
#' to the location of MathJax (this variable is also used to render [TeX] in
#' interactive graphs, see [config]).
#' @param parallel_limit Sets the limit of parallel tasks run.
#' @param verbose Turn on verbose logging on stdout.
#' @param debug Starts app in debug mode and turn on verbose logs on stdout.
Expand All @@ -25,9 +26,7 @@
#'
#' \dontrun{
#' p <- plot_ly(z = ~volcano) %>% add_surface()
#' orca(p, "surface-plot.png")
#' orca(p, "surface-plot.svg")
#' orca(p, "surface-plot.pdf")
#' }
#'

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

# TODO: point to local topojson? Should this only work if plot_geo(standalone = TRUE)?
try_library("processx", "orca")
invisible(processx::run("orca", args, echo = TRUE, spinner = TRUE))
}


mathjax_path <- function() {
if (is_rstudio()) {
try_library("rmarkdown", "orca")
return(getFromNamespace("pandoc_mathjax_local_path", "rmarkdown")())
}
path <- Sys.getenv("PLOTLY_MATHJAX_PATH", Sys.getenv("RMARKDOWN_MATHJAX_PATH", NA))
if (!is.na(path)) return(normalizePath(path, mustWork = TRUE))
stop(
"Please set either the RMARKDOWN_MATHJAX_PATH or PLOTLY_MATHJAX_PATH ",
"environment variable to the location of MathJax. ",
"On Linux systems you can also install MathJax using your system package manager.",
call. = FALSE
)
}
21 changes: 21 additions & 0 deletions R/plotly_build.R
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,27 @@ plotly_build.plotly <- function(p, registerFrames = TRUE) {
# box up 'data_array' attributes where appropriate
p <- verify_attr_spec(p)


verify_mathjax <- function(p) {
hasMathjax <- "mathjax" %in% sapply(p$dependencies, "[[", "name")
if (hasMathjax) return(p)

hasTeX <- any(rapply(p$x, is.TeX))
if (!hasTeX) return(p)

# TODO: it would be much better to add the dependency here, but
# htmlwidgets doesn't currently support adding dependencies at print-time!
warning(
"Detected the use of `TeX()`, but mathjax has not been specified. ",
"Try running `config(.Last.value, mathjax = 'cdn')`",
call. = FALSE
)
p
}

# make sure we're including mathjax (if TeX() is used)
p <- verify_mathjax(p)

# if a partial bundle was specified, make sure it supports the visualization
p <- verify_partial_bundle(p)

Expand Down
33 changes: 33 additions & 0 deletions inst/examples/rmd/MathJax/index.Rmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
---
title: "Render plotly MathJax inside rmarkdown"
output: html_document
---

Some HTML-based MathJax:

$$ \alpha+\beta $$

You _could_ print this **plotly** graph with SVG-based rendering, but it would break the HTML-based rendering of **rmarkdown**!

```{r message = FALSE}
library(plotly)

p <- plotly_empty() %>%
add_trace(x = 1, y = 1, text = TeX("\\alpha"), mode = "text", size = I(1000)) %>%
config(mathjax = "cdn")
```

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.

```{r}
widgetframe::frameableWidget(p)
```

Or, do it the old-fashioned way: save your plotly graph to an HTML file via `htmlwidgets::saveWidget()` then use an HTML `<iframe>`

```{r}
htmlwidgets::saveWidget(p, "my-plotly-plot.html")
```


<iframe src="my-plotly-plot.html" width="100%" height="400" id="igraph" scrolling="no" seamless="seamless" frameBorder="0"> </iframe>
4 changes: 4 additions & 0 deletions inst/htmlwidgets/lib/mathjax/cdn.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
var script = document.createElement("script");
script.type = "text/javascript";
script.src = "https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.4/MathJax.js?config=TeX-AMS-MML_SVG";
document.getElementsByTagName("head")[0].appendChild(script);
26 changes: 26 additions & 0 deletions man/TeX.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 16 additions & 5 deletions man/config.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 4 additions & 5 deletions man/orca.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.