cancel command intermittently not executed

17 views
Skip to first unread message

Cleon Teunissen

unread,
Nov 1, 2025, 6:34:34 AM (5 days ago) Nov 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 AM (4 days ago) Nov 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 PM (4 days ago) Nov 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 AM (3 days ago) Nov 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 AM (3 days ago) Nov 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 PM (3 days ago) Nov 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 PM (2 days ago) Nov 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 AM (2 days ago) Nov 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 PM (2 days ago) Nov 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,
1:01 AM (5 hours ago) 1:01 AM
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
Reply all
Reply to author
Forward
0 new messages