cancel command intermittently not executed

34 views
Skip to first unread message

Cleon Teunissen

unread,
Nov 1, 2025, 6:34:34 AMNov 1
to MathJax Users
I'm testing rendering an expression with the \cancel command in it.

I'm using MathJax 4
Inside the 'script' tags this is the call:

I'm seing an intermittent failure, on this page on my website
(to jump to the equation: use the search function to find the string '(2.6)'   )
the \cancel instruction is intermittently not recognized, and hence not processed 

On the other hand:
When testing with the page served via 'localhost': the equation renders correctly always.


After noticing the intermittent failureI looked up the documentation for the \cancel command:

In accordance with the instructions there: ahead of the MathJax call I have inside 'script' tags:
window.MathJax = { loader: {load: ['[tex]/cancel']}, tex: {packages: {'[+]': ['cancel']}} };

Notably: 
With the initial testing - with the web page served via 'localhost' - the 'loader' instruction wasn't there, and the equation rendered correctly anyway.

So it's not clear whether the 'loader' instruction is at all necessary, and there is no way to tell whether the loader instruction is processed.


On the webpage as served by my website hosting provider:
Sometimes the equation renders correctly, sometimes it doesn't. There doesn't seem to be any pattern to it. 


The following 2023 question appears to be about a related issue; in the answer the way that MathJax handles the '\cancel command is used as example:


I guess the probablity is large that for anyone else trying this the equation always renders correctly. (As in: when it comes to troubleshooting: intermittent problems are the worst)

Is there anything I can do to increase the probability of the equation being rendered correctly?

Cleon Teunissen

Davide Cervone

unread,
Nov 2, 2025, 11:33:07 AMNov 2
to mathja...@googlegroups.com
Cleon:

There are several issues with the use of MathJax on the page you link to.  First, you are loading MathJax v4, but are using what looks like a v2 configuration block:

<script type="text/x-mathjax-config">
window.MathJax = {
  loader: {load: ['[tex]/cancel']},
  tex: {packages: {'[+]': ['cancel']}}
};
</script>

(but this would not actually work in v2, either, since the type="text/x-mathjax-config" would mean that it would not be executed until after MathJax is loaded, and then it would replace the MathJax object that has been set up (and includes all the MathJax functions) with this configuration object, rendering MathJax inoperable.

For v3 and v4, you don't use type="text/x-mathjax-config" but instead use a plain <script> tag, like the one that follows the above tag:

<script> MathJax = {chtml: {displayAlign: 'left'}}; </script>

Note that this doesn't load or configure the cancel package.  For v4, I would also use the generic output block rather than the chtml block, in case anyone switched the render to the SVG output.  So you might use

<script>
MathJax = {
  loader: {load: ['[tex]/cancel']},
  tex: {packages: {'[+]': ['cancel']}},
  output: {displayAlign: 'left'},
};
</script>

instead.  Because the cancel package is autoloaded when it is first used, you don't technically have to load it explicitly (though doing so correctly probably would resolve your problem).

I see that you are also leading jsxgraphcore.js, part of the JSXGraph package.  It turns out that this package includes calls to MathJax to perform typesetting, and so there is a race condition between MathJax itself and JSXGRaph as to who does the typesetting first.  The result depends on the state of the browser cache and the timing of network requests, which is why you may only see this problem sometimes.  If MathJax does the typesetting, then it should work out, but if JSXGraph does, then that can lead to the problem you are seeing.

 When MathJax v3/v4 is available, jsxgraphcore.js calls MathJax.typeset() to perform the typesetting, but this is the non-promise-based typesetting call that doesn't handle dynamic loading of extensions, and instead throws a "retry" error that signals the caller that it needs to wait for the dynamic load to complete before trying again.  The typeset() call in jsxgraphcore.js is enclosed in a try/catch structure that traps the retry error but doesn't properly respond to it, and instead issues a "MathJax (not yet) loaded" console error (I generally see 3 of these on your page).  An error will occur in the try/catch block if MathJax isn't available yet, but also if it needs to load an extension, like the cancel package.

To properly handle this, jsxgraphcore.js should be calling MathJax.typesetPromise() instead, and you may be able to resolve the issue by changing that call in the jsxgraphcore.js file.  I didn't look further to see if jsxgraphcore.js uses the result of the typesetting, but if so, then this change would require more work, as other parts of the code would have to deal with promises that currently don't; that takes care and an understanding of their workflow that I don't have.

Pre-loading the cancel extension should allow it to work, so give that a try.

Davide



--
You received this message because you are subscribed to the Google Groups "MathJax Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to mathjax-user...@googlegroups.com.
To view this discussion visit https://groups.google.com/d/msgid/mathjax-users/d67f5804-bb44-4135-bc24-274bb202f995n%40googlegroups.com.

Cleon Teunissen

unread,
Nov 2, 2025, 1:24:06 PMNov 2
to MathJax Users
Davide:

Thank you for your reply:

As you can tell, this is the first time I'm trying MathJax

Allow me to first get some things out of the way, then I will get to the stuff that matters

I copied the window.MathJax etc. instruction from the MathJax documentation for MathJax 4: 
Your answer makes it clear that for MathJax4 that instruction/syntax is incorrect.
It could be that the information on that documenation page is outdated.
As a novel user I did not have the knowledge which version of MathJax uses which syntax.




Now to the things that are important: JSXGraph

I intend to contact the JSXGraph team, and inform them of the desirability of moving to MathJax.typesetPromise()
(Of course I will first search the JSXGraph issue tracker.)

(I have over the years submitted bug reports to the JSXGraph team; I have a cordial relation with the lead developer. )

For text elements JSXGraph has the option of a setting: {useMathJax: true} 
(default is 'false')
In one graphlet on that page a text element is configured to use MathJax.
I rather expect that JSXGraph calls MathJax only if a text element is configured to be rendered with MathJax.


Cleon

Murray

unread,
Nov 3, 2025, 2:59:35 AMNov 3
to MathJax Users
Cleon and Davide

Interesting timing, as I've been working on similar issues.

JSXGraph's process is basically:

(1) By default, if the developer puts something like "x^2" in a text field, JSXGraph helpfully converts that to x<sup>2</sup> and it works fine for simple expressions.

(2) The option useMathJax: true turns off that simple parsing for the text element and instead tells MathJax to typeset it.

(3) I only once saw the \cancel typesetting fail, and that was the first time I called it. Presumably as it's now in the cache, it results in success each time.

I actually started a conversation with Alfred at JSXGraph today about implementing an autoload feature for loading MathJax if any math occurs in a graph construction.


I had a go at setting up your example in my new jxgFiddle sandbox, here:


It gave me some ideas for a better implementation of incorporating various MathJax config scripts, but for now it's rather cludgy.

My process was:

(1) Add the config script for \cancel in the HTML box.

(2) Move it into the <head>

(3) My script then sees the math involving cancelled dt's, and autoloads MathJax, after adding the inlineMath directive to the MathJax.tex object..

(4) In the JavaScript section of the fiddle (which is processed after the above happens), it implements the suggestion of calling 

MathJax.typesetPromise();
MathJax.whenReady(() => {

     [ ...Do the JSXGraph stuff here ...]

});

It works (for me, anyway), but for the "Export" to work properly, I needed to wait for the 'load' eventListener. That exported page worked mostly with that listener active, but there were some cases where I saw cross origin errors, which were fixed by running it on localhost, rather than as a file:C:\\ ..

So there's more I have to do there (any suggestions welcome, Davide!), but for now I think it illustrates the concept of waiting for MathJax to be ready, then calling the JSXGraph instructions.

I'll work with Alfred to see if we can set up a reliable autoload and Promise setup for MathJax.

Regards
Murray

Davide Cervone

unread,
Nov 3, 2025, 9:05:52 AMNov 3
to mathja...@googlegroups.com
Cleon:

I copied the window.MathJax etc. instruction from the MathJax documentation for MathJax 4: 

Yes, that information is correct.

Your answer makes it clear that for MathJax4 that instruction/syntax is incorrect.

No, that's not right.  The instructions and syntax are correct.  The problem is that the configuration was put into the wrong type of <script> tag.  A tag <script type="text/x-mathjax-config"> will not be executed by the browser because of the type="text/x-mathjax-config", because that tells the browsers the contents isn't javascript to be executed.  MathJax v2 would look for these script tags and execute them itself during its configuration process (and they were expected to contain MathJax.Hub.Config() calls, not assignments to the MathJax variable).  But MathJax v3/v4 no longer works that way.  It expects configuration to be done by setting the MathJax variable to the configuration before MathJax is loaded, as is done in the second script in my message below.  (Note that it doesn't have type="text/x-mathjax-config" and so is executed by the browser.)

I don't know if you have put the configuration into that script yourself, or if you are using a content-creation system that has a MathJax plugin that is doing that for you, but that type attribute is the problem with that configuration.  The actual javascript code for the configuration is correct, hit is just the script tag that surrounds it that is wrong.

It could be that the information on that documenation page is outdated.

No the information is accurate.

As a novel user I did not have the knowledge which version of MathJax uses which syntax.

The documentation version is listed at the top of the sidebar on the left and in the floating menu at the lower right on the documentation website, so you can use that to tell you which version s the information relates to.

I intend to contact the JSXGraph team, and inform them of the desirability of moving to MathJax.typesetPromise()
(Of course I will first search the JSXGraph issue tracker.)

Thanks for doing that!

(I have over the years submitted bug reports to the JSXGraph team; I have a cordial relation with the lead developer. )

It looks like you and Murray will have this in good hands, then.

Davide


On Sunday, November 2, 2025 at 5:33:07 PM UTC+1 Davide Cervone wrote:
Cleon:

There are several issues with the use of MathJax on the page you link to.  First, you are loading MathJax v4, but are using what looks like a v2 configuration block:

<script type="text/x-mathjax-config">
window.MathJax = {
  loader: {load: ['[tex]/cancel']},
  tex: {packages: {'[+]': ['cancel']}}
};
</script>

(but this would not actually work in v2, either, since the type="text/x-mathjax-config" would mean that it would not be executed until after MathJax is loaded, and then it would replace the MathJax object that has been set up (and includes all the MathJax functions) with this configuration object, rendering MathJax inoperable.

For v3 and v4, you don't use type="text/x-mathjax-config" but instead use a plain <script> tag, like the one that follows the above tag:

<script> MathJax = {chtml: {displayAlign: 'left'}}; </script>

Note that this doesn't load or configure the cancel package.  For v4, I would also use the generic output block rather than the chtml block, in case anyone switched the render to the SVG output.  So you might use

<script>
MathJax = {
  loader: {load: ['[tex]/cancel']},
  tex: {packages: {'[+]': ['cancel']}},
  output: {displayAlign: 'left'},
};
</script>

instead.  Because the cancel package is autoloaded when it is first used, you don't technically have to load it explicitly (though doing so correctly probably would resolve your problem).

I see that you are also leading jsxgraphcore.js, part of the JSXGraph package.  It turns out that this package includes calls to MathJax to perform typesetting, and so there is a race condition between MathJax itself and JSXGRaph as to who does the typesetting first.  The result depends on the state of the browser cache and the timing of network requests, which is why you may only see this problem sometimes.  If MathJax does the typesetting, then it should work out, but if JSXGraph does, then that can lead to the problem you are seeing.

 When MathJax v3/v4 is available, jsxgraphcore.js calls MathJax.typeset() to perform the typesetting, but this is the non-promise-based typesetting call that doesn't handle dynamic loading of extensions, and instead throws a "retry" error that signals the caller that it needs to wait for the dynamic load to complete before trying again.  The typeset() call in jsxgraphcore.js is enclosed in a try/catch structure that traps the retry error but doesn't properly respond to it, and instead issues a "MathJax (not yet) loaded" console error (I generally see 3 of these on your page).  An error will occur in the try/catch block if MathJax isn't available yet, but also if it needs to load an extension, like the cancel package.

To properly handle this, jsxgraphcore.js should be calling MathJax.typesetPromise() instead, and you may be able to resolve the issue by changing that call in the jsxgraphcore.js file.  I didn't look further to see if jsxgraphcore.js uses the result of the typesetting, but if so, then this change would require more work, as other parts of the code would have to deal with promises that currently don't; that takes care and an understanding of their workflow that I don't have.

Pre-loading the cancel extension should allow it to work, so give that a try.

Davide

-- 
You received this message because you are subscribed to the Google Groups "MathJax Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to mathjax-user...@googlegroups.com.

Murray

unread,
Nov 3, 2025, 4:16:29 PMNov 3
to MathJax Users
Davide

I produced 2 example pages for Alfred and the JSXGraph team.

Could you please run your eyes over what I'm suggesting and let me know if it can be done better. I don't want to suggest something that's not the best way of doing it.

I haven't allowed for the case where MathJax fails to load - currently that would result in an empty graph.

Here's my reply on their forum with the 2 examples:


Regards
Murray

Davide Cervone

unread,
Nov 3, 2025, 6:59:32 PMNov 3
to mathja...@googlegroups.com
Murray:

There is some redundancy in your code that can be reduced out, and I think it is more complicated than it needs to be.  Here is an alternative version of the main script:

<script>
// User sets global Option value
JXG.Options.text.useMathJax = true;

window.addEventListener('load', () => {
  //
  // If not using MathJax, just do the user code
  //
  if (!JXG.Options.text.useMathJax) {
    userCode();
    return;
  }
  //
  // If user hasn't provided a config, set one up.
  //
  window.MathJax ??= {};
  //
  // MathJax already loaded, no need to wait.
  //
  if (MathJax.version) {
    console.log("[useMJ] MathJax already loaded");
    MathJax.startup.promise.then(userCode);
    return;
  }
  //
  // Cache the user's ready function, if any, and add our own.
  //
  window.MathJax.startup ??= {};
  const ready = MathJax.startup.ready;
  MathJax.startup.ready = () => {
    (ready || MathJax.startup.defaultReady)();
    console.log("MathJax setup.ready");
    MathJax.startup.promise.then(() => {
      console.log("MathJax startup.promise resolved");
      userCode();
    });
  }
  //
  // Load MathJax
  //
  const script = document.createElement('script');
  document.head.appendChild(script);
  console.log("MathJax script added");
});

function userCode() {

  const board = JXG.JSXGraph.initBoard('jxgbox', {
    boundingbox: [-0.5, 3.5, 5, -2],
    axis: true,
    grid: true,
    showNavigation: true,
    showCopyright: false
  });

  board.create('text',
    [0.1, 2, "\\begin{equation} \\begin{split} e^x & = 1 + x + \\frac{x^2}{2} + \\frac{x^3}{6} + \\cdots \\\\ & = \\sum_{n\\geq 0} \\frac{x^n}{n!} \\end{split} \\end{equation}"],
    {
      fontsize: 18,
      parse: false
    }
  );

  board.create('text',
    [1, -1, "$y=x^2$"],
    {
      fontsize: 20,
      parse: false
    }
  );

  console.log("Board and text defined");
}
</script>

It doesn't quite print all the messages yours does concerning the presence/absence of a configuration, as I'm not using as many if-then statements as you do.

This code also allows for the fact that the user's MathJax configuration may already include a startup block, and may already have a ready() function of their own.  My approach is to cache their ready() function and call it in mine.

This example uses the userCode() call in several places, which is fine if there is a function, but it might be that you want only one place where it is called.  In that case, you would need to restore the promise that you had used around the load event handler.  Something like

window.addEventListener('load', () => {
  new Promise((resolve) => {
    //
    // If not using MathJax, just do the user code
    //
    if (!JXG.Options.text.useMathJax) {
      resolve();
      return;
    }
    //
    // If user hasn't provided a config, set one up.
    //
    window.MathJax ??= {};
    //
    // MathJax already loaded, no need to wait.
    //
    if (MathJax.version) {
      console.log("[useMJ] MathJax already loaded");
      MathJax.startup.promise.then(resolve);
      return;
    }
    //
    // Cache the user's ready function, if any, and add our own.
    //
    window.MathJax.startup ??= {};
    const ready = MathJax.startup.ready;
    MathJax.startup.ready = () => {
      (ready || MathJax.startup.defaultReady)();
      console.log("MathJax setup.ready");
      MathJax.startup.promise.then(() => {
        console.log("MathJax startup.promise resolved");
        resolve();
      });
    }
    //
    // Load MathJax
    //
    const script = document.createElement('script');
    document.head.appendChild(script);
    console.log("MathJax script added");
  })
  .then(userCode);
});

could be used.

Of course, the real issue is just loading MathJax if it isn't already loaded, as you don't seem to need to wait for MathJax before creating the diagram.  For example


<script>
(() => {

  let board = JXG.JSXGraph.initBoard('jxgbox', {
    boundingbox: [-0.5, 3.5, 5, -2],
    axis: true,
    grid: true,
    showNavigation: true,
    showCopyright: false
  });

  let mj_txt = board.create('text',
    [0.1, 2, "\\begin{equation} \\begin{split} e^x & = 1 + x + \\frac{x^2}{2} + \\frac{x^3}{6} + \\cdots \\\\ & = \\sum_{n\\geq 0} \\frac{x^n}{n!} \\end{split} \\end{equation}"],
    {
      fontsize: 18,
      parse: false
    }
  );

  let mj_txt_2 = board.create('text',
    [1, -1, "$y=x^2$"],
    {
      fontsize: 20,
      parse: false
    }
  );

  console.log("Board and text defined");
})();
</script>

<script>
// User sets global Option value
JXG.Options.text.useMathJax = true;

window.addEventListener('load', () => {
  //
  // If not using MathJax, we're done 
  //
  if (!JXG.Options.text.useMathJax) return;

  //
  // Load MathJax if it isn't already configured or loaded.
  // (This assumes that if there is a MathJax configuration,
  // the page will also load MathJax itself.)
  //
  if (!window.MathJax) {
    const script = document.createElement('script');
    document.head.appendChild(script);
    console.log("loading MathJax");
  }
});
</script>

works for me with or without loading MathJax earlier.

I hope some of this is helpful.

Davide


Murray

unread,
Nov 4, 2025, 1:48:14 AMNov 4
to MathJax Users
Very, very useful. Thank you so much, Davide. You're right - I was making it far too complicated.

I actually found your second suggestion didn't seem to work if there was no user config.

Your suggestions for caching the user's config made total sense.


Anyway, hopefully this is now a useful suggestion for the JSXGraph people.

Regards
Murray

Davide Cervone

unread,
Nov 4, 2025, 6:45:42 PMNov 4
to mathja...@googlegroups.com
Murray:

Glad my ideas have helped you.  I have looked at your new versions, and I'm a little confused about the need for the new promise.  You are not using the promise in any way (no then() or catch(), an no await), so I don't know what its purpose is.   The only real need for it would be if you wanted to check for MathJax and wait for it to load before doing something else.  That doesn't seem to be the case, here, though perhaps you have something else in mind that is not included in this example.

If you don't plan to wait for MathJax, then all you really need is the following:

<script>
const board = JXG.JSXGraph.initBoard('jxgbox', {
  boundingbox: [-0.5, 3.5, 5, -2],
  axis: true,
  grid: true,
  showNavigation: true,
  showCopyright: false
});

board.create('text',
  [0.1, 2, "\\begin{equation} \\begin{split} e^x & = 1 + x + \\frac{x^2}{2} + \\frac{x^3}{6} + \\cdots \\\\ & = \\sum_{n\\geq 0} \\frac{x^n}{n!} \\end{split} \\end{equation}"],
  {
    fontsize: 18,
    parse: false
  }
);

board.create('text',
  [1, -1, "$y=x^2$"],
  {
    fontsize: 20,
    parse: false
  }
);

// User sets global Option value
JXG.Options.text.useMathJax = true;

window.addEventListener('load', () => {
  //
  // If using MathJax and it is not already loaded, load it.
  //
  if (JXG.Options.text.useMathJax && !window.MathJax?.version) {
    const script = document.createElement('script');
    document.head.appendChild(script);
  }
});
</script>

If you do need to wait for MathJax before doing something, here is a reduced version that allows for that.

<script>
const board = JXG.JSXGraph.initBoard('jxgbox', {
  boundingbox: [-0.5, 3.5, 5, -2],
  axis: true,
  grid: true,
  showNavigation: true,
  showCopyright: false
});

board.create('text',
  [0.1, 2, "\\begin{equation} \\begin{split} e^x & = 1 + x + \\frac{x^2}{2} + \\frac{x^3}{6} + \\cdots \\\\ & = \\sum_{n\\geq 0} \\frac{x^n}{n!} \\end{split} \\end{equation}"],
  {
    fontsize: 18,
    parse: false
  }
);

board.create('text',
  [1, -1, "$y=x^2$"],
  {
    fontsize: 20,
    parse: false
  }
);

// User sets global Option value
JXG.Options.text.useMathJax = true;

window.addEventListener('load', () => {

  new Promise((resolve, reject) => {
    //
    // If not using MathJax or it is already loaded,
    //   resolve the promise,
    // otherwise
    //   load MathJax.
    //
    if (!JXG.Options.text.useMathJax || window.MathJax?.version) {
      resolve();
    } else {
      const script = document.createElement('script');
      script.onerror = reject;
      script.onload = () => MathJax.startup.promise.then(resolve);
      document.head.appendChild(script);
    }
  })
  .then(() => {
    console.log("MathJax ready");  // do your MathJax-based stuff here
  })
  .catch((err) => {
    console.error("Can't load MathJax");
  });
});
</script>

I added an onerror handler to the script that loads MathJax that rejects the promise (so you can catch that and respond to that), and rather than go through the work of overriding the MathJax.startup.ready() function, we just use the unload handler to wait on the MathJax.startup.promise (which should be set up after the script loads and runs).  That makes a bit simpler.  Sorry I didn't think of that earlier.

Davide


Murray

unread,
Nov 6, 2025, 1:01:13 AMNov 6
to MathJax Users
Indeed, no Promise at all..

OK, here is yet another version (I should have posted them all here, rather than clutter up JSXGraph's forum with problematic attempts.)


This version does what it needs to do, I believe:
  • Caters for a user config, or not (I found I had to define MathJax.startup if not already, or things just came to a halt)
  • Caters for MathJax already loaded or not
  • Caters for the global useMathJax = true or not.
  • Caters for individual text elements having  useMathJax = true or not.   
  • Caters for what JSXGraph would do internally regarding typesetting only those elements that should be typeset.
The output is as desired for each of the on/off scenarios described. (I think JSXGraph is actually directing the individual element typeset cases, but I think what I have is correct.)

I commented out this line from your solution given my reorganising. It should be removed, right?

script.onload = () => MathJax.startup.promise.then(resolve);  

I tried to extract a more meaningful error message (it also threw that error if there were coding errors, not only MathJax loading errors), but couldn't.

Thank you so much for looking at all this.

Regards
Murray

Davide Cervone

unread,
Nov 6, 2025, 7:42:16 AMNov 6
to mathja...@googlegroups.com
Murray:

  • Caters for a user config, or not (I found I had to define MathJax.startup if not already, or things just came to a halt)
That is not my experience.  What browser and OS are you using?  The unload handler should be able to replace that.  I've added a console.log() call to that so you can see if it is firing.

  • Caters for what JSXGraph would do internally regarding typesetting only those elements that should be typeset.
This didn't quite work for me.  If the global setting is off, all three MathJax expression are typeset anyway.  This is because loading MathJax cause an initial typeset pass that typesets any math it finds, and that includes any that are inside the diagram but not marked to use MathJax.  In my revised code below, I include a step to add the mathjax_ignore class to any text node that doesn't ask for MathJax.

The output is as desired for each of the on/off scenarios described. (I think JSXGraph is actually directing the individual element typeset cases, but I think what I have is correct.)

I've replaced MathJax.typeset() by MathJax.typesetPromise() in order to allow \require or autoloading of packages.  You can also pass the complete array to MathJax.typesetPromise() rather than looping through it one at a time.


I commented out this line from your solution given my reorganising. It should be removed, right?

script.onload = () => MathJax.startup.promise.then(resolve);  

If you do need to replace the setup configuration, then yes.  But I don't think you should need to do that, unless you can give me a situation where it isn't working.

I tried to extract a more meaningful error message (it also threw that error if there were coding errors, not only MathJax loading errors), but couldn't.

Because the then() comes before the catch(), that means any error in the then() code will be caught by the catch().  I have switched the order of catch() and then() so that you can get the errors from within the then().  The catch() now just throws an error about not being able to load MathJax, which we can catch later by a second catch(), which prints any error from loading or the then() code.

Note also that in your version, if the diagram doesn't need MathJax and it isn't being loaded by the page, then your promise will never resolve.  You won't notice that in a browser, but if used in a node application, of example, it can prevent the application from ending.  I include a final then() call that prints a "Done" message so you can dell whether the promise resolved/rejected or not.  I've added code to make sure the promise always resolves or rejects.  I also adjusted the case when MathJax is already loaded so that we wait on MathJax.startup.promise before resolving.  That way, we know that MathJax is ready to go when the promise resolves.  Finally, I add a test around the MathJax typesetting commands so that they only run if MathJax is loaded (since the promise resolves if MathJax isn't needed and is not loaded).

Here is my adjusted version.

  // Array to hold single text elements for processing, if useMathJax: true
  const procWithMjArr = [];
  
  new Promise((resolve, reject) => {
    
    //
    // Add text nodes that need MathJax to the procWithMjArr array
    // and hide any that don't from MathJax.
    //
    txtArr.forEach( (txt) => {
      console.log(txt.id, "useMathJax ?", txt.visProp.usemathjax);
      if (txt.visProp.usemathjax) {
        procWithMjArr.push(txt.baseElement.rendNode);
      } else {
        txt.baseElement.rendNode.classList.add('mathjax_ignore');
      }
    });
    console.log("procWithMjArr", procWithMjArr)
    
    if (JXG.Options.text.useMathJax || procWithMjArr.length > 0) {
      console.log("MathJax needed because");
      if (JXG.Options.text.useMathJax) {
        console.log("  MathJax global option is true");
      }
      if (procWithMjArr.length > 0) {
        console.log("  1 or more text eles need processing");
      }
      if (window.MathJax?.version) {
        console.log("MathJax already loaded, so wait for it");
        MathJax.startup.promise.then(resolve);
      } else {
        console.log("Loading MathJax");
        const script = document.createElement('script');
        script.onerror = reject;
        script.onload = () => {
          console.log("MathJax loded");
          MathJax.startup.promise.then(resolve)
        };
        document.head.appendChild(script);
      }
    } else {
      console.log("MathJax not needed");
      resolve();
    }
    
  })
  .catch((err) => {
    throw Error("Can't load or run MathJax.");
  })
  .then(() => {
    if (window.MathJax?.version) {
      console.log("MathJax ready, then...");
      //
      // do your MathJax-based stuff here
      //
      return MathJax.typesetPromise(procWithMjArr);
    }
  })
  .catch((err) => console.log(err))
  .then(() => console.log('done'));

Davide

Murray

unread,
Nov 8, 2025, 12:04:20 AMNov 8
to MathJax Users
Hi Davide

Thank you once again!

(1) On your first point, I was using Chrome on Windows to develop version 2. If I didn't have MathJax.startup ??= {}; the script would just stop. Any console log after that would not appear and MathJax wouldn't load. no errors, it just stopped.

Now in version 2 (after probably changing several things before posting it) on each of Chrome, Firefox, Edge, I have a new experience, whereby MathJax doesn't load, and I get the error "Error: Can't find or load MathJax, or some other unspecified error."

(BTW here is a possible bug report. When I used Edge just now, on first load it complained about not finding speech-worker.js, then on a hard refresh, I got:

(speech-worker.js)
Uncaught (in promise) TypeError: Cannot convert undefined or null to object (speech-worker.js)

I keep getting this error on each soft or hard refresh. But not so on other browsers. When I revisited later, it didn't give the error.)

(2) Regarding your item 2 (with global set to false), that was working as desired for me in my v2, because I had set MathJax.startup.typeset = false; and then only typeset the 2nd expression (or whatever text element was actually set to useMathJax = true) at the end.

So I reinstated these lines (into your latest iteration) because if they're not there, the expected "global false" behaviour is not correct (that is, everything gets typeset, as you experienced).

//
// If user hasn't provided a config, set one up.
//
window.MathJax ??= {};

// If user hasn't defined startup
MathJax.startup ??= {};

  // Pause typesetting
MathJax.startup.typeset = false;

I found the 3rd line needs both the first and second if the user hasn't provided a config. Otherwise it throws "Error: Can't find or load MathJax."

(3) You said:

       I've replaced MathJax.typeset() by MathJax.typesetPromise() in order to allow \require or autoloading of packages.  

OK, got it.

         You can also pass the complete array to MathJax.typesetPromise() rather than looping through it one at a time.

Yes, that was a doh! on my part. I forgot you could do that.

(4) Thank you for sorting the .then() and .catch() order.

However, any coding errors in the
  • User's construction appropriately get caught by that last catch; but
  • Any errors in the JSXGraph internals portion were giving the "Error: Can't find or load MathJax." error from the first catch(). I changed it to be the same as the final catch() and now it gives meaningful errors.
(5) So I think (hope) we're done.

Version 3 now behaves on all browsers I've tested it on.


1. Global true
  • All math is typeset except the final one, as expected
  • Works correctly whether MathJax is already loaded, or we are loading it.
2. Global false
  • Only the 2nd math expression is typeset, as expected
  • Works correctly whether MathJax is already loaded, or we are loading it.
3. No user config
  • If global false, nothing is typeset, as expected (since $...$ is not defined)
  • If global true, only first expression is typeset, as expected  (since $...$ is not defined)
  • Works correctly whether MathJax is already loaded, or we are loading it.
4. Final math expression
  • Handled by JSXGraph appropriately
  • Parse:true means we get a subscript
  • Parse false means we just get text
Once again, thank you so much!
Regards
Murray

Davide Cervone

unread,
Nov 8, 2025, 8:07:29 AMNov 8
to mathja...@googlegroups.com
(1) On your first point, I was using Chrome on Windows to develop version 2. If I didn't have MathJax.startup ??= {}; the script would just stop. Any console log after that would not appear and MathJax wouldn't load. no errors, it just stopped.

Yes, if you are setting MathJax.startup.ready, you do need to have MathJax.startup defined, so that line would be necessary.

Now in version 2 (after probably changing several things before posting it) on each of Chrome, Firefox, Edge, I have a new experience, whereby MathJax doesn't load, and I get the error "Error: Can't find or load MathJax, or some other unspecified error."

Because the code that sets up MathJax.startup.ready is within the promise that has a catch() that prints that error message, any error within the promise code (in particular, not having MathJax.startup defined when you try to set Mathjax.startup.ready) would result in that error being produced.

(BTW here is a possible bug report. When I used Edge just now, on first load it complained about not finding speech-worker.js, then on a hard refresh, I got:

(speech-worker.js)
Uncaught (in promise) TypeError: Cannot convert undefined or null to object (speech-worker.js)

I keep getting this error on each soft or hard refresh. But not so on other browsers. When I revisited later, it didn't give the error.)

You probably had a corrupt version in the cache.  Note that soft and hard resets generally don't clear external javascript files from the cache.  For that, you have to explicitly clear the cache using the browser's settings panel.  To you would continue to use the corrupted cached version until later.

(2) Regarding your item 2 (with global set to false), that was working as desired for me in my v2, because I had set MathJax.startup.typeset = false; and then only typeset the 2nd expression (or whatever text element was actually set to useMathJax = true) at the end.

So I reinstated these lines (into your latest iteration) because if they're not there, the expected "global false" behaviour is not correct (that is, everything gets typeset, as you experienced).

//
// If user hasn't provided a config, set one up.
//
window.MathJax ??= {};

// If user hasn't defined startup
MathJax.startup ??= {};

   // Pause typesetting
MathJax.startup.typeset = false;

I found the 3rd line needs both the first and second if the user hasn't provided a config. Otherwise it throws "Error: Can't find or load MathJax."

I think this is not a good solution, for several reasons:

  1. It will prevent any other math in the page from being typeset, and if the user has landed MahtJax explicitly, that will be confusing, since they will expect the math to be processed.  In general, it is bad to change user preferences behind their back.
  2. If the page is a dynamic one where it calls MathJax.typeset() or MathJax.typesetPromise() later after adding more content to the page, then the math that was initially not processed in the diagram will now be processed.

That is why I suggested marking the texts that aren't to be processed using the mathjax-ignore class.  That will mean you don't need to change the user preferences, and the diagram texts will be protected from future typeset calls, if any.  (The only possible down side is if the user has changed the ignoreHtmlClass option to not include mathjax-ignore any longer.  One could look to see, but since that option is a regular expression, it could be a bit tricky to decide what the proper class to use is.)

This was done in my code by the "else" clause in

      if (txt.visProp.usemathjax) {
        procWithMjArr.push(txt.baseElement.rendNode);
      } else {
        txt.baseElement.rendNode.classList.add('mathjax_ignore');
      }

With that you can remove the code that sets MathJax.startup.typeset, and won't be modifying the configuration at all.

(4) Thank you for sorting the .then() and .catch() order.

However, any coding errors in the
  • User's construction appropriately get caught by that last catch; but

That doesn't make sense to me, and I am not able to reproduce it.  The construction of the graph takes place before the promise is even created (your log message about the graph being built shows that).  So I don't see how the catch will have anything to do with the user's graph being constructed.  The catch only applies to the code within the promise itself (and any code it calls explicitly), so I just don't see how that could be true.  Can you provide an example that illustrates this behavior?

  • Any errors in the JSXGraph internals portion were giving the "Error: Can't find or load MathJax." error from the first catch(). I changed it to be the same as the final catch() and now it gives meaningful errors.
Yes, errors within the promise would lead to that message.  Once assumes that (in a production version), the code within the promise would not produce errors.  But if you want to handle any such errors, your change will do that, but you should also change the onerror function to be

script.onerror = () => reject(Error("Can't load MathJax"));

so that a meaningful message is generated for that case.  Your code would show an error object as a literal object, not an error.

(5) So I think (hope) we're done.

Almost, I think.  I would still make the changes I suggest above.

Davide


Murray

unread,
Nov 9, 2025, 2:25:03 AMNov 9
to MathJax Users
Thank you again.

(a) Sorry - I completely missed the bit in your previous solution about setting class mathjax_ignore. So it was self-induced pain on my part. I also thought the startup thingy was not such a good idea for the same reasons, but it worked, so I went with it.

That's all fixed now.

(b) Regarding the errors in the user's construction, I realise now I was seeing a default javascript error message - not one produced by our catch(es). Please ignore.

(c) I updated the script.onerror line.

Here is (I believe) the final version (v4):


Thank you and sorry to have taken up your time with my bumbling attempts.
Regards
Murray

Cleon Teunissen

unread,
Nov 9, 2025, 2:44:34 AMNov 9
to MathJax Users
Davide, Murray,

It was most fascinating to see the investigations proceed.

For me as a non-programmer what is fascinating is the question what it is that makes the issue difficult. 

JSXGraph and MathJax both have optimizations. (And optimization never ends; it is to be expected that in the future additional optimizations will be implemented.) As I understand it, instead of acting blindly there are instructions in place to not load when not needed, and other optimizations. To me as a non-programmer it appears that the optimizations are interacting, leading to unpredictable results.

Also interesting: as we know: browsers have two tiers of reload: F5 and CTRL-F5, with the first only re-rendering the page, and the second flushing the local cache of the page source. Davide pointed out: that doesn't necessary flush external javascript files. 

A general pattern appears: optimization interaction issues tend to be test-evasive.

As it appears to me: a javascript library is a moving target.

My course of action:
On the page where I first reported the interaction issues I removed the '{useMathJax:true'} instruction from the diagram source code, going back to having JSXGraph render the text elements as html strings.


Cleon

Davide Cervone

unread,
Nov 9, 2025, 7:56:03 AMNov 9
to mathja...@googlegroups.com
OK, this looks good.  Only two minor comments:

First,

if(txt.visProp.usemathjax) {
let txtNode = txt.baseElement.rendNode;
procWithMjArr.push(txtNode);
} else {
txt.baseElement.rendNode.classList.add('mathjax_ignore');
}

could be

const txtNode = txt.baseElement.rendNode;
if (txt.visProp.usemathjax) {
procWithMjArr.push(txtNode);
} else {
txtNode.classList.add('mathjax_ignore');
}

since you use txt.baseElement.rendNode in both parts.

Second, the comments

//
// If not using MathJax or it is already loaded,
//   resolve the promise,
// otherwise
//   load MathJax.
//

may want to be updated, as they reflect an earlier iteration of the code.

Glad you have gotten things to this point, and I think we went through some useful ideas along the way.

Davide


--
You received this message because you are subscribed to the Google Groups "MathJax Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to mathjax-user...@googlegroups.com.

Murray

unread,
Nov 10, 2025, 5:41:56 AMNov 10
to MathJax Users
@Davide: I updated those few bits and have posted the final version to the JSXGraph forum.

@Cleon: I believe it would be possible to get your page to reliably typeset your \cancel case, even before JSXGraph is updated to better time the typesetting step.

But I understand why you've given it away :-)

Now that I've updated jxgFiddle with a proper MathJax autoload script, my attempt at emulating your situation appears to be reliable:


The iframe that contains the graph has an autoload approach and the timing works well. (Of course, this is more involved than your situation - I'm just pointing out the timing can be made to work, even with the current JSXGraph setup.)

In the "export" version, I'm using a variant of the autoloader we've been discussing here.

@Davide: A new issue has appeared with the latest autoload script I put in the iframe script in jxgFiddle. What I see now only on a phone is the math is typeset correctly for a few seconds then it gives a yellow background "Math input error" message.

I don't see it at all on a desktop, any browser. I also don't see it in Cleon's example (link above) when on a phone.

The frame does an initial load then is updated when it receives a message from the parent script, telling it the dimensions of the frame on the parent page. (This bit is working OK. There is another message on resize, so the frame script gets run again, and that's where the yellow error occurs. Is there a way to find out what it's complaining about?

I've run out of brain cells to look at it any more tonight. 

Regards
Murray

Davide Cervone

unread,
Nov 10, 2025, 10:44:30 AMNov 10
to mathja...@googlegroups.com
I'm not able to reproduce the issue you are having on mobile browsers (on my Android phone).

The error message that produced the "Math input error" is available as a tooltip on the error message, but you probably can't access that by "hovering" over the message on  a mobile device.  Note sure how tooltips are triggered there, to be honest.

On the other hand, there is a configuration option to trap input errors that you could use to report it in other ways.


for example, you could do

MathJax = {
  options: {
    compileError(document, math, error) {
      alert(`Error: "${error.message}" in`, '\n', math.math);
      document.compileError(math, error);
    }
  }
}

to get the message displayed in an alert.

Davide


Murray

unread,
Nov 20, 2025, 11:39:12 PM (13 days ago) Nov 20
to MathJax Users
I now only see those errors intermittently, and haven't concluded anything about them yet.

One thing I have noticed is the \cancel script doesn't load (in time?) reasonably often. Seems like the CDN is not as friendly towards it as it is towards the main MathJax script, which is not surprising given the relative use of each.

The first version or two in this process included a regex check for math, but I stopped considering that when getting the other parts to work.

So here's a version that's aimed at a newbie JSXGraph user who doesn't want to fiddle with any options - just enter the math in the text field and have JSXGraph handle the rest.


It actually has 3 steps that I would probably allow for in a different way if I were the JSXGraph authors, but I'm not:

a. It requires the user to include a global JXG.Options.text.parse = false;  and only set parsing true if they specifically want it (like my example 5) (It would be a breaking change to have this as the default)

b. It requires double backslashes on the math (which trips up a lot of users) (There should be a way around this...?)

c. Back ticks are used for template literals of course, and some people use them for quotation marks, so this approach could cause problems for them.

The approach still requires a user config if the user wants single $ delimiters, which I think is appropriate.

Some questions:

(1) Have I set up the loader properly?

(2) Anything I've missed?

Thanks a lot.
Regards
Murray

Davide Cervone

unread,
Nov 22, 2025, 7:59:52 AM (12 days ago) Nov 22
to mathja...@googlegroups.com
Murray:

I now only see those errors intermittently, and haven't concluded anything about them yet.

Did you use the compileError() option that I suggested in my previous message?  That could shed some light on the situation.

Since it is intermittent, the only thing I can think of is that there is a race condition at play, and the only one I can see is the one between the user-loaded MathJax and your loading it in the unload handler.  Does this only happen when you have the earlier script for MathJax uncommented?  If that's the case, then it might be that your load handler is running before MathJax has been loaded and run, and so window.MathJax.version hasn't been set up, and so you try to load it again.  That may mean that there are effectively two MathJax instances running, and that they are both trying to process the same TeX expressions, and that leads to conflicts that lead to an internal error in one of them.  That is the only thing I can think of.

One thing I have noticed is the \cancel script doesn't load (in time?) reasonably often.

I don't think there is a timeout for the loading in play (at least in MathJax), and so I'm not sure how that would cause the result you are having (unless the browser itself decides to cancel the load).  That should lead to an error message in the developer's console, but I'm not sure you can access that in a mobile device (I think you have to weather the phone to a desktop computer and use remove debugging for that).  The compileError() option is probably easier.

So here's a version that's aimed at a newbie JSXGraph user who doesn't want to fiddle with any options - just enter the math in the text field and have JSXGraph handle the rest.
b. It requires double backslashes on the math (which trips up a lot of users) (There should be a way around this...?)
c. Back ticks are used for template literals of course, and some people use them for quotation marks, so this approach could cause problems for them.

I will say more about this in a minute.

(1) Have I set up the loader properly?

A couple of thoughts, here.  

First, your adjustments to the loader.load array are inside the loop over the text areas, so f more than one uses AsciiMath, then you are pushing the components multiple times.  It may also be that the user's configuration already includes AsciiMath.  So you might want to check for it being in the loader.load array before adding it in again.

Second, if the user has loaded MathJax themselves, it likely will already have been loaded by now, so making changes to the configuration won't have any effect.  If they haven't included input/asciimath in their configuration, this won't load it (but if they are using AsciiMath notation, you can argue that they need to add it themselves, which is reasonable).

Third, you don't need to push output/html or ui/menu.  You are loading tex-chtml.js, which includes both of those already, so no need to include them, and if the user has already loaded MathJax, you want to use their selected output, which might be SVG.

(2) Anything I've missed?

That is all I see.

I mentioned that I would say more about the backslash issue.  I was going to suggest template literals for that as really the only way to do it.  You are right the the use of back-tics for quotation marks would be more complicated.  Once can use ` ... ${"`"} ... ` to include one or more backticks inside a template literal, though it is awkward.  It would also be possible to use "smart-quotes" like “ or ‘ instead, or to define \lq to be ` and the use \lq instead, or to use \U{0060} in MathJax v4.  None is ideal, but they would work.

So something like

txtArr.push(board.create('text',
  [0.1, 5.5, String.raw`Uses <code>\begin{...}</code> so MathJax: \begin{equation} \begin{split} e^x & = 1 + x + \frac{x^2}{2} + \frac{x^3}{6} + \cdots \\ & = \sum_{n\geq 0} \frac{x^n}{n!} \end{split} \end{equation}`],
  {fontsize: 17}
));

would work.  But String.raw is more verbose than you might need, so doing

const raw = String.raw;

would allow

txtArr.push(board.create('text',
  [0.1, 5.5, raw`Uses <code>\begin{...}</code> so MathJax: \begin{equation} \begin{split} e^x & = 1 + x + \frac{x^2}{2} + \frac{x^3}{6} + \cdots \\ & = \sum_{n\geq 0} \frac{x^n}{n!} \end{split} \end{equation}`],
  {fontsize: 17}
));

An alternative would be to define something like

tex = {
  $: (strings, ...values) => '\\(' + String.raw(strings, ...values) + '\\)',
  $$: (strings, ...values) => '\\[' + String.raw(strings, ...values) + '\\]',
};

and then use tex.$`...` to get an inline expression and tex.$$`...` to get a display one.  (Or you could define tex to get inline and dtex for display, or tex.inline and tex.display instead.)  Then you can do

txtArr.push(board.create('text',
  [0.1, 5.5,`Uses in-line math ${tex.$`\frac{\sqrt{1-x^2}}{1-x}`} in a sentence.`],
  {fontsize: 17}
));

or

txtArr.push(board.create('text',
  [0.1, 5.5,'Uses in-line math ' + tex.$`\frac{\sqrt{1-x^2}}{1-x}` + ' in a sentence.'],
  {fontsize: 17}
));

These are just some ideas about how it could be done.

Davide


Murray

unread,
Nov 24, 2025, 1:45:55 AM (10 days ago) Nov 24
to MathJax Users
Davide

(1) I haven't had a chance to test things much since I've had to go on to other things. The loading errors from memory were mostly when I went to other browsers to check things were OK, and I suspect some of the issues were due to the \cancel package not being in cache.

Just now I went to https://bourne2learn.com/math/jsxgraph/jxg-fiddle/mbourne/OTk3d32a using Edge and everything seemed to load OK but it gave these 304 cache errors (not the same as the "not loaded" errors seen for \cancel).

  OTk3d32a:486   GET https://cdn.jsdelivr.net/npm/mathjax@4/tex-chtml.js net::ERR_CACHE_READ_FAILURE 304 (Not Modified)
  (anonymous) @ OTk3d32a:486
  ...
  70268aec-a8ad-4f91-9852-a84513b0fba1:3  Uncaught NetworkError: Failed to execute 'importScripts' on 'WorkerGlobalScope': The script at   'https://cdn.jsdelivr.net/npm/mathjax@4/sre/speech-worker.js' failed to load.
    at 70268aec-a8ad-4f91-9852-a84513b0fba1:3:7
  (anonymous) @ 70268aec-a8ad-4f91-9852-a84513b0fba1:3
  cdn.jsdelivr.net/npm/@mathjax/mathjax-newcm-font/chtml/woff2/mjx-ncm-n.woff2:1   GET https://cdn.jsdelivr.net/npm/@mathjax/mathjax-newcm-font/chtml/woff2/mjx-ncm-n.woff2       
  net::ERR_CACHE_READ_FAILURE 304 (Not Modified)

I don't see such errors in other browsers, so as I said, it's intermittent and I haven't had a chance to test thoroughly. If I see the problem with the \cancel package again, I'll use your compileError() option to investigate further.

(2) For these latest examples, I'm assuming the user is new to JSXGraph, MathJax and programming in general, and just wants to create a graph and put some typeset math on it.

So I'm assuming they're not loading MathJax themselves and don't even know how to do a MathJax config.

Of course, the script has to allow for power uses as well who may already have MathJax loaded, and may have their own user config.

So this version "for noobs" has:
  • The JSXGraph parser would be set to false
  • We add a variable to handle the raw string situation, as you suggested.
There's an example here that uses a template literal, to convince myself it could work in amongst the other back ticks. It's messy (for a newbie) but it works.


I doubt the JSXGraph people will go this far in terms of automation, but it's been an interesting exercise. I'll still send it to them.

Regards
Murray

Davide Cervone

unread,
Nov 24, 2025, 7:49:30 PM (9 days ago) Nov 24
to mathja...@googlegroups.com
(1) I haven't had a chance to test things much since I've had to go on to other things. The loading errors from memory were mostly when I went to other browsers to check things were OK, and I suspect some of the issues were due to the \cancel package not being in cache.

It certainly seem to be related to the cache, but I don't think it means that something ISN'T in the cache, but that a cached file is corrupt.  In this case, it may be the speech-worker.js file (a rather large one).  Have you tried clearing the browser cache?  Restarting the browser?  (If you use the browser for a long time without restarting, its memory usage may get corrupt.)  Restarting the computer?  (If you run for weeks without restarting, the memory map may get corrupt.)  Do a hardware check to see if there is any bad memory, or a disk check?  Any of those things could cause corrupt data, but clearing the browser cache will probably take care of it.

There's an example here that uses a template literal, to convince myself it could work in amongst the other back ticks. It's messy (for a newbie) but it works.

Note that templates perform variable substitution, so it is not necessary to go in and out of the template for

rawMath `(3) Uses <code>\[...\]</code>, so MathJax: \[y= ` + `${a * b}` + rawMath `\sin(x^2)\]`

as you can just do

rawMath `(3) Uses <code>\[...\]</code>, so MathJax: \[y= ${a * b}\sin(x^2)\]`

This makes things easier.

Thanks for all your efforts on this.

Davide


Reply all
Reply to author
Forward
0 new messages