LaTeX in Dash applications: a guide to gorgeous typesetting

Coding

Like many who come from the scientific community, LaTeX is like a second language to me – in fact, I generally eschew text editors that do not have built-in LaTeX rendering, as so much of my work is wrapped up in mathematical reasoning. Matplotlib, the golden oldie of visualisation solutions, pays begrudging respect to LaTeX, allowing you to render LaTeX on plots or even axis labels out of the box. Dash can also render LaTeX, but it takes some coaxing. Below is the solution that worked for me when working on the socially distanced SIR simulator, which required me to embed quite a bit of complex maths in the narrative that details the differential equations that run the model.

What works (and what doesn’t)

Before you embark on making changes to your code, it’s important to know what you can and can’t do at the state of the code.

What definitely works

Once correctly configured, most single-line mathematical typesetting, whether inline or as a separate paragraph, works fine. More complex environments, such as the align environments imported by the amsmath package, will not work. However, as far as ‘vanilla’ LaTeX maths goes, you can render some exquisitely complex stuff with relative ease.

Note: if you write your text in Markdown and import it using dcc.Markdown(), all your code will be parsed through the Markdown renderer – including your LaTeX code. This means that some operators, which may have meaning in LaTeX and Markdown alike, will break LaTeX rendering. There are largely two to look out for: the * operator (replace it with \ast or \star), and closed square bracket operators [0...1], which you might have to get creative to find a workaround for.

You can use valid LaTeX pretty much everywhere. This includes inside plots/axes (on which see later), on buttons, slides &c.

What definitely doesn’t work

There are some things that do not work, at the present state of Plotly Dash. In particular, you cannot dynamically render LaTeX, and LaTeX axis labels need to be refreshed (see below).

Making it work

The overall structure of what we’re going to be doing consists of four principal steps:

  1. Import dash_defer_js_import to defer loading of external Javascript scripts after the Dash React components have loaded, and deferred import the MathJax renderer script.
  2. Integrate the MathJax renderer script by reframing the index template using the app.index_string variable.
  3. Integrate the deferred script import into the layout object.
  4. For LaTeX rendering in dynamic axis labels, deferred import a custom script that regularly re-renders figures (optional).

You may find it useful to refer to a worked example – a recent Dash app I have built to display epidemic dynamics as a function of social distancing implements part of this approach in order to beautifully render the maths underlying the model. You can find the code for this on Github, and you’re welcome to adapt it to your needs.

Importing dash_defer_js_import and importing the MathJax renderer script

First, we add dash_defer_js_import to our dependencies, so that we can implement deferred loading:

import dash_defer_js_import as dji

Then, once the application (app object) has been declared, we import the MathJax renderer script using deferred importing:

mathjax_script = dji.Import(src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.7/latest.js?config=TeX-AMS-MML_SVG")

Integrating MathJax into the template

Since Dash 0.22.0, Dash app object allows the page template to be customised, which is most easily accomplished by setting the variable app.index_string. The details of this functionality are described in the Dash manual, but in essence, this option takes a Jinja formatted template.

We are going to use this to our advantage by inserting the MathJax configuration to the footer of the template:

app.index_string = """
<!DOCTYPE html>
<html>
    <head>
        {%metas%}
        <title>{%title%}</title>
        {%favicon%}
        {%css%}
    </head>
    <body>
        {%app_entry%}
        <footer>
            {%config%}
            {%scripts%}
            <script type="text/x-mathjax-config">
            MathJax.Hub.Config({
                tex2jax: {
                inlineMath: [ ['$','$'],],
                processEscapes: true
                }
            });
            </script>
            {%renderer%}
        </footer>
    </body>
</html>
"""

Integrate the deferred script import into the layout object

Finally, we’ll integrate the mathjax_script object we declared above into our template layout:

app.layout = html.Div([
    dbc.Container(children=[
        # your application content goes here
    ]),
    mathjax_script
])

Rendering and refreshing axis labels

Axis labels are a little more complex as they can change, and therefore need to be constantly refreshed. We accomplish this by using a script that refreshes plot objects regularly (in this case, every second – you can adjust this according to your needs by setting the value in the setTimeout() function).

function redraw(){
  var figs = document.getElementsByClassName("js-plotly-plot")
  for (var i = 0; i < figs.length; i++) {
    Plotly.redraw(figs[i])
  }
}

setTimeout(function(){
    redraw();
}, 1000);

I have uploaded this to Codepen, and you’re welcome to use it. Start by creating a deferred import object, just as we did for the MathJax renderer:

refresh_plots = dji.Import("https://codepen.io/chrisvoncsefalvay/pen/ExPJjWP.js")

Then, add the deferred import object to your layout:

app.layout = html.Div([
    dbc.Container(children=[
        # your application content goes here
    ]),
    refresh_plots,
    mathjax_script
])

This will gradually refresh and re-render MathJax LateX entries in your axis labels.

Conclusion

Until Dash natively implements LaTeX rendering, this workaround is a fairly quick and convenient way of beautifully typesetting maths in your Dash applications. As always, since this leverages the entire functionality of MathJax, there’s a lot more you can do with MathJax than simple maths (the documentation is definitely worth a read!). Currently, this works best with MathJax 2.7.7 (using latest.js from CDNs does not upgrade MathJax 2 to MathJax 3!), but can be easily adapted to work with MathJax 3.x as well.

Was this post useful for you? Did it work? Did it not work? Any tips, suggestions, tricks, hacks? Let me know.

Credits: The axis rendering refresh function is based on this Codepen by Richard Xue.

Chris von Csefalvay
Data scientist by day, computational epidemiologist by night, constantly sleep-deprived husband and dad to the world's most adorable Golden Retriever puppy.

You may also like

Leave a Reply