From 716bac162d7c558d632521f65de5104ebcde4ac4 Mon Sep 17 00:00:00 2001
From: Emily KL <4672118+emilykl@users.noreply.github.com>
Date: Sun, 9 Aug 2026 12:26:38 -0400
Subject: [PATCH 1/5] ensure that plot still renders in the case where an
unsupported MathJax version is present
---
src/lib/svg_text_utils.js | 26 +++++++++++++++++---------
1 file changed, 17 insertions(+), 9 deletions(-)
diff --git a/src/lib/svg_text_utils.js b/src/lib/svg_text_utils.js
index 6e133611075..5b26613c14e 100644
--- a/src/lib/svg_text_utils.js
+++ b/src/lib/svg_text_utils.js
@@ -32,6 +32,7 @@ exports.convertToTspans = function(_context, gd, _callback) {
var tex = (!_context.attr('data-notex')) &&
gd && gd._context.typesetMath &&
(typeof MathJax !== 'undefined') &&
+ isMathJaxVersionSupported() &&
matchTex(str);
var parent = d3.select(_context.node().parentNode);
@@ -204,18 +205,25 @@ function cleanEscapesForTex(s) {
// and reused for subsequent calls.
var mathjaxSVGDocument = null;
-function texToSVG(_texString, _config, _callback) {
- const MathJaxVersion = parseInt(
- (MathJax.version || '').split('.')[0]
- );
+// plotly.js is only compatible with MathJax v3 and v4.
+const mathJaxMajorVersion = () => parseInt((MathJax.version || '').split('.')[0]);
- if(
- MathJaxVersion !== 3 &&
- MathJaxVersion !== 4
- ) {
+// Only warn once per page
+var warnedUnsupportedMathJax = false;
+
+function isMathJaxVersionSupported() {
+ const version = mathJaxMajorVersion();
+ if(version === 3 || version === 4) return true;
+
+ if(!warnedUnsupportedMathJax) {
+ warnedUnsupportedMathJax = true;
Lib.warn('Unsupported MathJax version:', MathJax.version);
- return;
}
+ return false;
+}
+
+function texToSVG(_texString, _config, _callback) {
+ const MathJaxVersion = mathJaxMajorVersion();
var tmpDiv;
From dae92a86bde2c817f80235e5ce060d4c0035ddc3 Mon Sep 17 00:00:00 2001
From: Emily KL <4672118+emilykl@users.noreply.github.com>
Date: Sun, 9 Aug 2026 12:27:04 -0400
Subject: [PATCH 2/5] add regression tests
---
test/jasmine/bundle_tests/mathjax_test.js | 44 +++++++++++++++++++++++
test/jasmine/tests/svg_text_utils_test.js | 41 +++++++++++++++++++++
2 files changed, 85 insertions(+)
diff --git a/test/jasmine/bundle_tests/mathjax_test.js b/test/jasmine/bundle_tests/mathjax_test.js
index 2602ff3818c..21704fa8130 100644
--- a/test/jasmine/bundle_tests/mathjax_test.js
+++ b/test/jasmine/bundle_tests/mathjax_test.js
@@ -159,4 +159,48 @@ describe('Test MathJax v' + mathjaxVersion + ':', function() {
.then(done, done.fail);
});
});
+
+ describe('Test tex rendering:', function() {
+ var gd;
+
+ beforeEach(function() {
+ gd = createGraphDiv();
+ });
+
+ afterEach(destroyGraphDiv);
+
+ it('should hand tex titles and tick labels off to MathJax', function(done) {
+ Plotly.newPlot(gd, {
+ data: [{
+ x: ['$\\phi$', '$\\nabla \\cdot \\vec{F}$'],
+ y: [1, 2]
+ }],
+ layout: {
+ title: { text: '$E = mc^2$' }
+ }
+ })
+ .then(function() {
+ var gd3 = d3Select(gd);
+
+ // '.gtitle-math-group' is only added once MathJax has typeset the
+ // string, so its presence is what tells us the tex was rendered
+ expect(gd3.selectAll('.gtitle-math-group').size()).toBe(1, 'title math group');
+
+ // tick label math groups carry the default 'text-math-group' class
+ expect(gd3.selectAll('.text-math-group').size()).toBe(2, 'tick label math groups');
+
+ var rendered = [];
+ gd3.selectAll('[class*=-math-group]').each(function() {
+ expect(this.getAttribute('data-math')).toBe('Y');
+ rendered.push(this.getAttribute('data-unformatted'));
+ });
+ expect(rendered.sort()).toEqual([
+ '$E = mc^2$',
+ '$\\nabla \\cdot \\vec{F}$',
+ '$\\phi$'
+ ]);
+ })
+ .then(done, done.fail);
+ });
+ });
});
diff --git a/test/jasmine/tests/svg_text_utils_test.js b/test/jasmine/tests/svg_text_utils_test.js
index 307f8d8c45c..5bcde86108e 100644
--- a/test/jasmine/tests/svg_text_utils_test.js
+++ b/test/jasmine/tests/svg_text_utils_test.js
@@ -1,8 +1,12 @@
var d3Select = require('../../strict-d3').select;
var d3SelectAll = require('../../strict-d3').selectAll;
+var Plotly = require('../../../lib/index');
var util = require('../../../src/lib/svg_text_utils');
+var createGraphDiv = require('../assets/create_graph_div');
+var destroyGraphDiv = require('../assets/destroy_graph_div');
+
describe('svg+text utils', function() {
'use strict';
@@ -619,3 +623,40 @@ describe('sanitizeHTML', function() {
expect(innerHTML).toEqual('click');
});
});
+
+// regression test for https://github.com/plotly/plotly.js/issues/7926
+describe('convertToTspans with an unsupported MathJax version', function() {
+ 'use strict';
+
+ var gd;
+ var mathJaxBefore;
+
+ beforeEach(function() {
+ gd = createGraphDiv();
+ mathJaxBefore = window.MathJax;
+ // Fake the presence of MathJax v2 by clearing window.MathJax
+ // and setting window.MathJax.version to a v2.x version string
+ window.MathJax = {version: '2.7.9'};
+ });
+
+ afterEach(function() {
+ if(mathJaxBefore === undefined) delete window.MathJax;
+ else window.MathJax = mathJaxBefore;
+ destroyGraphDiv();
+ });
+
+ it('draws the plot with the tex left unevaluated', function(done) {
+ Plotly.newPlot(gd, [{y: [1, 2, 3]}], {title: {text: '$x^2$'}})
+ .then(function() {
+ // whole plot should render except for tex string
+ expect(d3SelectAll('.scatterlayer .trace').size()).toBe(1, 'trace');
+ expect(d3SelectAll('.xtick').size()).toBeGreaterThan(0, 'x ticks');
+
+ var title = d3Select('.gtitle');
+ expect(title.size()).toBe(1, 'title');
+ expect(title.text()).toBe('$x^2$', 'raw tex as title');
+ expect(title.node().style.display).not.toBe('none', 'title is visible');
+ })
+ .then(done, done.fail);
+ });
+});
From 64eeb409cd7fd09eac53cedcc48e11f342fabd87 Mon Sep 17 00:00:00 2001
From: Emily KL <4672118+emilykl@users.noreply.github.com>
Date: Sun, 9 Aug 2026 12:31:07 -0400
Subject: [PATCH 3/5] add draftlog
---
draftlogs/7951_fix.md | 1 +
1 file changed, 1 insertion(+)
create mode 100644 draftlogs/7951_fix.md
diff --git a/draftlogs/7951_fix.md b/draftlogs/7951_fix.md
new file mode 100644
index 00000000000..bd25da8fb98
--- /dev/null
+++ b/draftlogs/7951_fix.md
@@ -0,0 +1 @@
+- Fix issue where plot failed to render if unsupported MathJax version was present on page [[#7951](https://github.com/plotly/plotly.js/pull/7951)]
From 7f82b3055275ec1ff9bfd56cd9939372e7d4dd17 Mon Sep 17 00:00:00 2001
From: Emily KL <4672118+emilykl@users.noreply.github.com>
Date: Thu, 13 Aug 2026 14:45:50 -0400
Subject: [PATCH 4/5] clean up MathJax version check
---
src/lib/svg_text_utils.js | 21 +++++++++++++++------
1 file changed, 15 insertions(+), 6 deletions(-)
diff --git a/src/lib/svg_text_utils.js b/src/lib/svg_text_utils.js
index 5b26613c14e..e2e222b2648 100644
--- a/src/lib/svg_text_utils.js
+++ b/src/lib/svg_text_utils.js
@@ -31,10 +31,11 @@ exports.convertToTspans = function(_context, gd, _callback) {
// allow some elements to prohibit it by attaching 'data-notex' to the original
var tex = (!_context.attr('data-notex')) &&
gd && gd._context.typesetMath &&
- (typeof MathJax !== 'undefined') &&
- isMathJaxVersionSupported() &&
matchTex(str);
+ // Only complain about MathJax version once we know there's actually math to render
+ if(tex && !isMathJaxVersionSupported()) tex = null;
+
var parent = d3.select(_context.node().parentNode);
if(parent.empty()) return;
var svgClass = (_context.attr('class')) ? _context.attr('class').split(' ')[0] : 'text';
@@ -205,17 +206,25 @@ function cleanEscapesForTex(s) {
// and reused for subsequent calls.
var mathjaxSVGDocument = null;
-// plotly.js is only compatible with MathJax v3 and v4.
-const mathJaxMajorVersion = () => parseInt((MathJax.version || '').split('.')[0]);
+// Function which returns the major version of MathJax as an integer,
+// or null if MathJax is undefined or MathJax.version is falsy.
+const mathJaxMajorVersion = () => (typeof MathJax !== 'undefined' && MathJax.version) ? parseInt(MathJax.version.split('.')[0]) : null;
-// Only warn once per page
+// Only warn once per page about each of these conditions
+var warnedMissingMathJax = false;
var warnedUnsupportedMathJax = false;
+// plotly.js is only compatible with MathJax v3 and v4.
function isMathJaxVersionSupported() {
const version = mathJaxMajorVersion();
if(version === 3 || version === 4) return true;
- if(!warnedUnsupportedMathJax) {
+ if(version === null) {
+ if(!warnedMissingMathJax) {
+ warnedMissingMathJax = true;
+ Lib.warn('MathJax is not loaded. Math equations will not be rendered.');
+ }
+ } else if(!warnedUnsupportedMathJax) {
warnedUnsupportedMathJax = true;
Lib.warn('Unsupported MathJax version:', MathJax.version);
}
From 6aaf901a50ab6bbf59bf38ef3d266db89875bb44 Mon Sep 17 00:00:00 2001
From: Emily KL <4672118+emilykl@users.noreply.github.com>
Date: Thu, 13 Aug 2026 14:46:15 -0400
Subject: [PATCH 5/5] Update draftlogs/7951_fix.md
Co-authored-by: Cameron DeCoster
---
draftlogs/7951_fix.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/draftlogs/7951_fix.md b/draftlogs/7951_fix.md
index bd25da8fb98..84d80a8f57c 100644
--- a/draftlogs/7951_fix.md
+++ b/draftlogs/7951_fix.md
@@ -1 +1 @@
-- Fix issue where plot failed to render if unsupported MathJax version was present on page [[#7951](https://github.com/plotly/plotly.js/pull/7951)]
+- Fix issue where plot fails to render if unsupported MathJax version is present on page [[#7951](https://github.com/plotly/plotly.js/pull/7951)]