LaTeX displayed as raw text instead of math formulae in plotly
Describe the bug
Plotly uses MathJax to render LaTeX mathematical formulas and notation.
But marimo doesn't use MathJax
This makes LaTeX displayed as raw text instead of math formulae in plotly
In Jupyter
In marimo
Thoughts:
Implement a logic to render LaTeX correctly in plotly formatter
Environment
{
"marimo": "0.8.0",
"OS": "Darwin",
"OS Version": "22.3.0",
"Processor": "arm",
"Python Version": "3.12.4",
"Binaries": {
"Browser": "--",
"Node": "v22.5.1"
},
"Requirements": {
"click": "8.1.7",
"importlib-resources": "missing",
"jedi": "0.19.1",
"markdown": "3.7",
"pymdown-extensions": "10.9",
"pygments": "2.18.0",
"tomlkit": "0.13.2",
"uvicorn": "0.30.6",
"starlette": "0.38.2",
"websockets": "12.0",
"typing-extensions": "missing",
"ruff": "0.6.1"
}
}
Code to reproduce
# import marimo as mo
import plotly.express as px
px.line(
x=[1, 2, 3, 4],
y=[1, 4, 9, 16],
title=r"$\alpha_{1c} = 352 \pm 11 \text{ km s}^{-1}$",
).update_layout(
xaxis_title=r"$\sqrt{(n_\text{c}(t|{T_\text{early}}))}$",
yaxis_title=r"$d, r \text{ (solar radius)}$",
)
Thanks for finding this! Do you know how jupyter is handling this / hooking in to the formatter?
Hi! Did you try this? https://stackoverflow.com/questions/44333065/string-variable-as-latex-in-pyplot
@rgouveiamendes Thanks for pointing this out. Yeah, this works for matplotlib, but doesn't work for plotly.
Thanks for finding this! Do you know how jupyter is handling this / hooking in to the formatter?
Sorry, I have limited knowledge of Jupyter, have no idea
I think Jupyter actually does nothing for plotly, at least I can't find any relevant code in their repo.
The reason might be that, in Jupyter user needs to call fig.show() to display the figure, but in marimo user doesn't need to call fig.show(), just fig can do the job.
However, if calling fig.show() in marimo, the jumped figure renders LaTeX correctly.
So it's about fig.show() and fig I suppose
thanks for the investigation @metaboulie!
it might be our custom formatter for plotly (maybe missing config, or not converting to the plotly json correctly) marimo/_output/formatters/plotly_formatters.py
I tried to modify plotly_formatters as below:
html_content = build_stateless_plugin(
component_name="marimo-plotly",
args={"figure": json, "config": resolved_config},
)
# Include MathJax script for LaTeX rendering
mathjax_script = (
'<script type="text/javascript" async '
'src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.7/MathJax.js?config=TeX-MML-AM_CHTML">'
'</script>'
)
return Html(mathjax_script + html_content)
because pio.to_html(include_mathjax="cdn") appears to do the same thing, adding a script tag that references a MathJax CDN location to the output.
However, it doesn't work
https://plotly.com/python-api-reference/generated/plotly.io.to_html.html
This works as a quick fix.
import marimo as mo
import plotly.express as px
# no async so it executes immediately
script = '''<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.7/MathJax.js?config=TeX-MML-AM_CHTML"></script>'''
fig_html = px.line(
x=[1, 2, 3, 4],
y=[1, 4, 9, 16],
title=r"$\alpha_{1c} = 352 \pm 11 \text{ km s}^{-1}$",
).update_layout(
xaxis_title=r"$\sqrt{(n_\text{c}(t|{T_\text{early}}))}$",
yaxis_title=r"$d, r \text{ (solar radius)}$",
).to_html()
mo.iframe(script + fig_html)
@metaboulie
I tried to modify
plotly_formattersas below: return Html(mathjax_script + html_content)
Yeah, the scripts aren't executed when returned from the backend. I tried returning a console.log script and it would not execute. Not sure why as I see the script tag on the frontend. 🤔
@Light2Dark
I think it has something to do with _build_attr
https://github.com/marimo-team/marimo/blob/c461eb592df52917cc7bf3981a16f34e392b86dd/marimo/_plugins/core/web_component.py#L45-L54
where backslashes and dollar sign are encoded, which may let mathjax_script not recognize the latex string.
you can print the result of build_stateless_plugin and the latex part is formatted as below
"title": {"text": "$\\alpha_{1c} = 352 \\pm 11 \\text{ km s}^{-1}$"}, "dragmode": "zoom"
which, I think mathjax can't recognize as valid latex
In the plotly formatter, build_stateless_plugin is evaluated to build the html plugin
https://github.com/marimo-team/marimo/blob/995cd935473ef56c2c7194936eb884e80b7dcc4e/marimo/_output/formatters/plotly_formatters.py#L56-L73
and build_stateless_plugin calls _build_attr
https://github.com/marimo-team/marimo/blob/c461eb592df52917cc7bf3981a16f34e392b86dd/marimo/_plugins/core/web_component.py#L101-L119
hmm, I thought the issue was the mathjax library is missing, if I return this in plotly_formatters.py:68, the script wouldn't be executed.
return Html(
'<script>console.log("Hello!")</script>' +
build_stateless_plugin(
component_name="marimo-plotly",
args={"figure": json, "config": resolved_config},
)
)
If you added this line to marimo/frontend/index.html, it would work.
<script type="text/javascript" async src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.7/MathJax.js?config=TeX-MML-AM_CHTML"></script>
~Update: this works (plotly_formatters.py:68)~
return Html(
h.iframe(src='https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.7/MathJax.js?config=TeX-MML-AM_CHTML') +
build_stateless_plugin(
component_name="marimo-plotly",
args={"figure": json, "config": resolved_config},
)
)
@Light2Dark
Yeah, adding that line to marimo/frontend/index.html resolves the problem.
Maybe you should make a PR for this solution
I'm not so keen on adding a script tag to index.html, it will add another dependency that's only needed for some use cases imo. Ideally the script would be added in plotly_formatters when LaTeX code is detected. If you know of any examples where the Python returns a script that's executed, maybe that could help.
In any case, this is a workaround
import marimo as mo
import plotly.express as px
script = '<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/mathjax@2/MathJax.js?config=TeX-MML-AM_CHTML"></script>'
fig_html = px.line(
x=[1, 2, 3, 4],
y=[1, 4, 9, 16],
title=r"$\alpha_{1c} = 352 \pm 11 \text{ km s}^{-1}$",
).update_layout(
xaxis_title=r"$\sqrt{(n_\text{c}(t|{T_\text{early}}))}$",
yaxis_title=r"$d, r \text{ (solar radius)}$",
).to_html()
mo.iframe(script + fig_html)
Maybe you can add this workaround to the doc
https://docs.marimo.io/guides/working_with_data/plotting.html#plotly
Albeit this is the doc for mo.ui.plotly
good idea, let me work on that :)
thanks for investigating @Light2Dark and @metaboulie. i think it would be ok to lazy-load mathjax inside the PlotlyPlugin (which does get lazy-loaded as well).
We already have a dependency on this react-hooks library and we can use useScript https://usehooks.com/usescript
Ideally we can optionally depend on it, if it fails, then fallback to what it was before.
Right now, i think it is ok to use a cdn, but we can later have marimo serve this locally.