Skip to content
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

Plotly cannot be loaded if Mathjax v3 is already loaded #4563

Closed
Bertbk opened this issue Feb 9, 2020 · 12 comments
Closed

Plotly cannot be loaded if Mathjax v3 is already loaded #4563

Bertbk opened this issue Feb 9, 2020 · 12 comments
Labels
bug something broken

Comments

@Bertbk
Copy link

Bertbk commented Feb 9, 2020

Since MathJax v3, Mathjax.Hub does not exist anymore. This makes impossible to load PlotlyJS if Mathjax v3 has already been loaded. I get the following error:

TypeError: MathJax.Hub is undefined

I believe the error comes from these lines plotly.js:

module.exports = function() {
    if(typeof MathJax !== 'undefined') {
        var globalConfig = (window.PlotlyConfig || {}).MathJaxConfig !== 'local';

        if(globalConfig) {
            MathJax.Hub.Config({
                messageStyle: 'none',
                skipStartupTypeset: true,
                displayAlign: 'left',
                tex2jax: {
                    inlineMath: [['$', '$'], ['\\(', '\\)']]
                }
            });
            MathJax.Hub.Configured();
        }
    }
};

A check on MathJax version should probably be made here to use MathJax.Hub or not?

Here is a short example where plotly failed to be loaded: https://codepen.io/bthierry/pen/PoqwXzm

Many thanks!

@glinskyc
Copy link

MathJax v3 and plotly.js can be loaded together as long as the following is set before loading Plotly:

window.PlotlyConfig = {MathJaxConfig: 'local'}

For reference:
https://github.com/plotly/plotly.js/blob/master/dist/README.md#to-support-mathjax

@MiltonCobo
Copy link

Is there any advance on this bug? I would like to load Mathjax v3 in my project, but unfortunately it does work with plotly at the moment. Plotly is using the old Mathjax.Hub object.

Thanks!

@alexcjohnson
Copy link
Collaborator

Curious whether the MathJax power users out there know if v3 avoids the function constructors that v2 has, as noticed by @archmoj in #5383 (comment) - we're trying to improve our compatibility with strict CSP and ATM that means we need to consider MathJax "not our problem," but if the new one avoids that issue we may have more motivation to upgrade.

Of course if anyone else is interested to make a PR upgrading MathJax in plotly.js we'd be happy to help whether or not it improves our CSP compliance :) In principle it would be nice if we can support both v2 and v3 so as to not require these upgrades to be synchronized, and given that MathJax calls in plotly.js are grouped in just a couple locations (essentially just fonts/mathjax_config.js and lib/svg_text_utils.js) that shouldn't be too hard.

@akhmerov
Copy link

Since people seem to be relying on this recipe:

MathJax v3 and plotly.js can be loaded together as long as the following is set before loading Plotly:

window.PlotlyConfig = {MathJaxConfig: 'local'}

It doesn't seem to work. I see errors "can't read property Queue of underfined", the source contains references to nonexistent MathJax.Hub, and the readme clearly states that mathjax 2 is required.

@Nieju
Copy link

Nieju commented Aug 26, 2021

^This

@akhmerov
Copy link

akhmerov commented Jan 2, 2022

For what it's worth, I've made some progress along the lines of the compatibility patch outlined in MathJax docs. After defining all the shims, the figure does render, albeit I don't actually see any latex. This happens because of this line that expects for the mathjax output to have a class MathJax_SVG.

@akhmerov
Copy link

akhmerov commented Jan 2, 2022

I got some promising results (math shows up) with this monstrosity.

mathjax_shim.js
window.MathJax = {
    startup: {
      //
      //  Mapping of old extension names to new ones
      //
      requireMap: {
        AMSmath: 'ams',
        AMSsymbols: 'ams',
        AMScd: 'amscd',
        SVG: 'svg',
        noErrors: 'noerrors',
        noUndefined: 'noundefined'
      },
      ready: function () {
        //
        //  Replace the require command map with a new one that checks for
        //    renamed extensions and converts them to the new names.
        //
        var CommandMap = MathJax._.input.tex.SymbolMap.CommandMap;
        var requireMap = MathJax.config.startup.requireMap;
        var RequireLoad = MathJax._.input.tex.require.RequireConfiguration.RequireLoad;
        var RequireMethods = {
          Require: function (parser, name) {
            var required = parser.GetArgument(name);
            if (required.match(/[^_a-zA-Z0-9]/) || required === '') {
              throw new TexError('BadPackageName', 'Argument for %1 is not a valid package name', name);
            }
            if (requireMap.hasOwnProperty(required)) {
              required = requireMap[required];
            }
            RequireLoad(parser, required);
          }
        };
        new CommandMap('require', {require: 'Require'}, RequireMethods);
        MathJax.Callback = function (args) {
            if (Array.isArray(args)) {
                if (args.length === 1 && typeof(args[0]) === 'function') {
                return args[0];
                } else if (typeof(args[0]) === 'string' && args[1] instanceof Object &&
                        typeof(args[1][args[0]]) === 'function') {
                return Function.bind.apply(args[1][args[0]], args.slice(1));
                } else if (typeof(args[0]) === 'function') {
                return Function.bind.apply(args[0], [window].concat(args.slice(1)));
                } else if (typeof(args[1]) === 'function') {
                return Function.bind.apply(args[1], [args[0]].concat(args.slice(2)));
                }
            } else if (typeof(args) === 'function') {
                return args;
            }
            throw Error("Can't make callback from given data");
        };
        //
        // Add a replacement for MathJax.Hub commands
        //
        MathJax.Hub = {
        Queue: function () {
            for (var i = 0, m = arguments.length; i < m; i++) {
                var fn = MathJax.Callback(arguments[i]);
                MathJax.startup.promise = MathJax.startup.promise.then(fn);
            }
            return MathJax.startup.promise;
        },
        Typeset: function (element, callback) {
            var promise = MathJax.typesetPromise([element]).then(
                () => element.firstElementChild.classList.add("MathJax_SVG")
            );
            if (callback) {
                promise = promise.then(callback);
            }
            return promise;
        },
        Config: function () {console.log('MathJax configurations should be converted for version 3')},
        Configured: function () {console.log('MathJax cannot be configured like this')},
        config: {menuSettings: {renderer: "SVG"}}
        };


        return MathJax.startup.defaultReady();
      }
    },
    tex: {
        inlineMath: [["\\(", "\\)"], ["$", "$"]],
        displayMath: [["\\[", "\\]"], ["$$", "$$"]],
        processEscapes: true,
        processEnvironments: true,
        autoload: {
            color: [],          // don't autoload the color extension
            colorv2: ['color'], // do autoload the colorv2 extension
        }
    }
  };

This page footer contains

<script src="mathjax_shim.js"></script>
<script src="https://polyfill.io/v3/polyfill.min.js?features=es6"></script>
<script src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-svg.js"></script>
<script src="https://cdn.plot.ly/plotly-latest.js"></script>

Now I would only want to know how to switch between chtml and svg rendering modes...

@akhmerov
Copy link

akhmerov commented Jan 3, 2022

Great, I now have a functioning plotly on a site with chtml mathjax. Do get there I combined what I had before with the idea from mathjax/MathJax#2705

Page footer:

<script src="scripts/mathjaxconfig.js"></script>
<script src="https://polyfill.io/v3/polyfill.min.js?features=es6"></script>
<script src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script>
<script src="https://cdn.plot.ly/plotly-latest.js"></script>

scripts/mathjaxconfig.js:

window.PlotlyConfig = {MathJaxConfig: "local"}
window.MathJax = {
    startup: {
      //
      //  Mapping of old extension names to new ones
      //
      requireMap: {
        AMSmath: 'ams',
        AMSsymbols: 'ams',
        AMScd: 'amscd',
        SVG: 'svg',
        noErrors: 'noerrors',
        noUndefined: 'noundefined'
      },
      ready() {
        // Here and later using recipe from https://github.com/mathjax/MathJax/issues/2705
        //
        //  Get the MathJax modules that we need.
        //
        const {mathjax} = MathJax._.mathjax;
        const {SVG} = MathJax._.output.svg_ts;

        // Now using https://docs.mathjax.org/en/v3.2-latest/upgrading/v2.html#version-2-compatibility-example
        //
        //  Replace the require command map with a new one that checks for
        //    renamed extensions and converts them to the new names.
        //
        var CommandMap = MathJax._.input.tex.SymbolMap.CommandMap;
        var requireMap = MathJax.config.startup.requireMap;
        var RequireLoad = MathJax._.input.tex.require.RequireConfiguration.RequireLoad;
        var RequireMethods = {
          Require: function (parser, name) {
            var required = parser.GetArgument(name);
            if (required.match(/[^_a-zA-Z0-9]/) || required === '') {
              throw new TexError('BadPackageName', 'Argument for %1 is not a valid package name', name);
            }
            if (requireMap.hasOwnProperty(required)) {
              required = requireMap[required];
            }
            RequireLoad(parser, required);
          }
        };
        new CommandMap('require', {require: 'Require'}, RequireMethods);
        MathJax.Callback = function (args) {
            if (Array.isArray(args)) {
                if (args.length === 1 && typeof(args[0]) === 'function') {
                return args[0];
                } else if (typeof(args[0]) === 'string' && args[1] instanceof Object &&
                        typeof(args[1][args[0]]) === 'function') {
                return Function.bind.apply(args[1][args[0]], args.slice(1));
                } else if (typeof(args[0]) === 'function') {
                return Function.bind.apply(args[0], [window].concat(args.slice(1)));
                } else if (typeof(args[1]) === 'function') {
                return Function.bind.apply(args[1], [args[0]].concat(args.slice(2)));
                }
            } else if (typeof(args) === 'function') {
                return args;
            }
            throw Error("Can't make callback from given data");
        };
        //
        // Add a replacement for MathJax.Hub commands
        //
        MathJax.Hub = {
            Queue: function () {
                for (var i = 0, m = arguments.length; i < m; i++) {
                    var fn = MathJax.Callback(arguments[i]);
                    MathJax.startup.promise = MathJax.startup.promise.then(fn);
                }
                return MathJax.startup.promise;
            },
            Typeset: function (element, callback) {
                var promise = MathJax.typesetSVGPromise([element]).then(
                    () => {
                        element.firstElementChild.classList.add("MathJax_SVG");
                    }
                );
                if (callback) {
                    promise = promise.then(callback);
                }
                return promise;
            },
            Config: function () {console.log('MathJax configurations should be converted for version 3')},
            Configured: function () {console.log('MathJax cannot be configured like this')},
            config: {menuSettings: {renderer: "SVG"}}
        };

        MathJax.startup.defaultReady();

        // Continuing from https://github.com/mathjax/MathJax/issues/2705
        //
        //  Create an SVG output jax and a new MathDocument that uses it.
        //
        const svgOutput = new SVG(MathJax.config.svg);
        const svgDocument = mathjax.document(document, {
            ...MathJax.config.options,
            InputJax: MathJax.startup.input,
            OutputJax: svgOutput
        });
        //
        //  Define the SVG-based conversion methods
        //
        MathJax.svgStylesheet = () => svgOutput.styleSheet(svgDocument);
        MathJax.typesetSVGPromise = (elements) => {
            svgDocument.options.elements = elements;
            svgDocument.reset();
            return mathjax.handleRetriesFor(() => {
                svgDocument.render();
            });
        };
        })

      }
    },
    loader: {load: ["output/svg"]},
    tex: {
        inlineMath: [["\\(", "\\)"], ["$", "$"]],
        displayMath: [["\\[", "\\]"], ["$$", "$$"]],
        processEscapes: true,
        processEnvironments: true,
        autoload: {
            color: [],          // don't autoload the color extension
            colorv2: ['color'], // do autoload the colorv2 extension
        }
    }
  };

@akhmerov
Copy link

akhmerov commented Jan 3, 2022

Finally, also to return to @alexcjohnson remark: MathJax3 doesn't seem to use new Function (or so it seemed after searching through the source), so that should offer advantages for CSP. Together with promise-based asynchronous API and the ability to isolate rendering in a separate virtual document like in my example above this may provide a motivation to update :)

@alexcjohnson
Copy link
Collaborator

@akhmerov did you see @archmoj's new PR adding MathJax v3 support #6073? Would you like to test it and give feedback on it?

@akhmerov
Copy link

akhmerov commented Jan 3, 2022

Thanks for letting me know. Setting up a dev environment is a bit too much work for me, so I'm good on testing for now. I did give feedback though :)

@archmoj
Copy link
Contributor

archmoj commented Mar 31, 2022

Resolved by #6073.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug something broken
Projects
None yet
Development

No branches or pull requests

8 participants