fast-reflexes / better-react-mathjax

MIT License
124 stars 16 forks source link

How to work with plotly? #44

Closed ziyuang closed 1 year ago

ziyuang commented 1 year ago

Or how to work with the <text> node inside <svg>? The following code doesn't render the TeX in the plot title:

import React from 'react';

import { MathJaxContext, MathJax } from 'better-react-mathjax'
import Plot from 'react-plotly.js'

export function App(props) {
  return (
    <MathJaxContext>
      <MathJax>
        <Plot
          data={[
            {
              x: [1, 2, 3],
              y: [1, 4, 9],
              mode: 'lines+markers',
            }
          ]}
          layout={{ width: 640, height: 320, title: '\\(\\beta=\\alpha^2\\)' }}
        />
      </MathJax>
    </MathJaxContext>
  );
}

newplot

Passing a config to MathJaxContext will give a warning No MathJax version: undefined and renders nothing:

import React from 'react';

import { MathJaxContext, MathJax } from 'better-react-mathjax'
import Plot from 'react-plotly.js'

export function App(props) {
  const config = {
    tex: {
      inlineMath: [['$', '$']],
      displayMath: [['$$', '$$']]
    }
  }
  return (
    <MathJaxContext config={config}>
      <MathJax>
        <Plot
          data={[
            {
              x: [1, 2, 3],
              y: [1, 4, 9],
              mode: 'lines+markers',
            }
          ]}
          layout={{ width: 640, height: 320, title: '$\\beta=\\alpha^2$' }}
        />
      </MathJax>
    </MathJaxContext>
  );
}
fast-reflexes commented 1 year ago

The text used for the title is inside an <svg> element and furthermore inside an SVG <text> element. Even though typesetting works as intended, it results in HTML elements or SVG elements, neither of which are allowed inside an SVG <text> which is why nothing is shown.

I suggest you use HTML to typeset the title separately and put it in front of the diagram, like:

<MathJaxContext version={3} config={config}>
    <div style={{ position: "relative", display: "inline-block" }}>
        <Plot ... />
        <div
            style={{
                position: "absolute",
                left: 0,
                right: 0,
                top: "40px",
                textAlign: "center",
                height: "75px"
            }}
        >
            <MathJax>
                <p>{"$\\beta=\\alpha^2$"}</p>
            </MathJax>
        </div>
    </div>
</MathJaxContext>

You can adapt the details as needed.

Also, I don't know what you mean when you say that the config does not work. Please provide a sandbox example of this if you want me to check on it.

Here is a sandbox with a working sample of your input problem: https://codesandbox.io/s/better-react-mathjax-44-ll79wy

Good luck! :)

ziyuang commented 1 year ago

Thanks! And here is the sandbox with the second part of the code, where no plot is shown: https://playcode.io/1501320

fast-reflexes commented 1 year ago

Ok so it turns out that Plotly has built in support for generating MathJax given that MathJax has been loaded with support for SVG output.

This implies that Plotly looks for MathJax on startup and if it finds it, it tries to use it. In the situation when you add your own config, there is a race condition where, in this specific sandbox (it is not the same in the sandbox provider I use), during the extra time it takes to apply a custom config, Plotly finds MathJax but since it hasn't finished configuring, it results in the error and the warning being printed. Plotly basically tries to use a function inside MathJax before MathJax is done with its internal configuration and then MathJax itself prints the WARN: No MathJax version: undefined message.

You can fix this by not showing the plot until MathJax is loaded:

<MathJaxContext onLoad={() => setShowPlot(true) } />

... then you will get rid of the warning.

I could spend a lot of time looking into and explaining all the different outcomes of different setups and usecases connected with this, it all makes sense, but the bottom cause is that Plotly uses MathJax outside of the intended use via better-react-mathjax which in turn creates conflicts and race conditions. To get everything to work, simply use this:

import React, { useState } from 'react';

import { MathJaxContext } from 'better-react-mathjax'
import Plot from 'react-plotly.js'

export function App(props) {
  const [showPlot, setShowPlot] = useState(false)
  return (
    <MathJaxContext 
      src={"https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-svg.js"}
      onLoad={() => setShowPlot(true)}
     >
      { showPlot ?
        <Plot
          data={[
            {
              x: [1, 2, 3],
              y: [1, 4, 9],
              mode: 'lines+markers',
            }
          ]}
          layout={{ width: 640, height: 320, title: '$\\beta=\\alpha^2$' }}
        />
        :
        null
      }
    </MathJaxContext>
  );
}

This will make the MathJaxContext responsible for downloading the correct MathJax version (with SVG output support) and make sure that Plotly is not using MathJax until it is properly loaded. It also seems like Plotly itself assumes the $-$ and $$-$$ MathJax delimiters as opposed to the standard ones. Important to understand here is that Plotly manually uses MathJax's type setting function text2Svg to perform the conversion and thus does not realy on any automatic typesetting done by MathJax.

You can dig into Plotly source files to find where this takes place if you're interested.

Feel free to close this issue if you're happy with the result. :)

ziyuang commented 1 year ago

Thanks. In the end I used your method with the shipped mathjax (with vite):

import mathJaxURL from "mathjax-full/es5/tex-svg.js?url";

<MathJaxContext src={mathJaxURL}>...</MathJaxContext>