diff --git a/README.md b/README.md index 68a51412ccf..96e23654268 100644 --- a/README.md +++ b/README.md @@ -95,17 +95,23 @@ While non-minified source files may contain characters outside UTF-8, it is reco > Please note that as of v2 the "plotly-latest" outputs (e.g. https://cdn.plot.ly/plotly-latest.min.js) will no longer be updated on the CDN, and will stay at the last v1 patch v1.58.5. Therefore, to use the CDN with plotly.js v2 and higher, you must specify an exact plotly.js version. ### MathJax -You can load either version two or version three of MathJax files. For example: +plotly.js supports using MathJax v3 or v4 to render mathematical expressions within plots. + +MathJax is not included in the plotly.js bundle, so it must be loaded separately as a script on the webpage. + +For MathJax v4: ```html - + ``` +For MathJax v3: ```html ``` -> When using MathJax version 3, it is also possible to use `chtml` output on the other parts of the page in addition to `svg` output for the plotly graph. -Please refer to `devtools/test_dashboard/index-mathjax3chtml.html` to see an example. +When MathJax is available, any string contained within `$..$` signs will be rendered using the MathJax engine. This applies to plot titles, axis labels, tick label text, and annotations. + +plotly.js's use of the MathJax `svg` renderer should not prevent using other MathJax renderers elsewhere on the page, nor will it affect the global MathJax config. See `devtools/test_dashboard/index-mathjax3chtml.html` for an example of a webpage which uses the MathJax `chtml` renderer in the main page, and also displays Plotly charts containing MathJax equations rendered as `svg`. ### Need to have several WebGL graphs on a page? You may simply load the [virtual-webgl](https://github.com/greggman/virtual-webgl) script for WebGL 1 (not WebGL 2) before loading other scripts. diff --git a/devtools/test_dashboard/index-mathjax3.html b/devtools/test_dashboard/index-mathjax3.html index 510e839fa7f..a6380a65f5d 100644 --- a/devtools/test_dashboard/index-mathjax3.html +++ b/devtools/test_dashboard/index-mathjax3.html @@ -17,7 +17,7 @@
-

MathJax $V^3$ with $svg$ output on the page as well as plotly graphs

+

MathJax \(V^3\) with \(svg\) output on the page as well as plotly graphs

no MathJax: Apple: $2, Orange: $3

@@ -27,18 +27,12 @@

no MathJax: Apple: $2, Orange: $3

- + diff --git a/devtools/test_dashboard/index-mathjax3chtml.html b/devtools/test_dashboard/index-mathjax3chtml.html index 795c5688703..2134087213d 100644 --- a/devtools/test_dashboard/index-mathjax3chtml.html +++ b/devtools/test_dashboard/index-mathjax3chtml.html @@ -17,52 +17,24 @@
-

MathJax $V^3$ with $chtml$ output on the page and svg output on the plotly graphs

+

MathJax \(V^3\) with \(chtml\) output on the page and svg output on the plotly graphs

- - + diff --git a/devtools/test_dashboard/index-mathjax4chtml.html b/devtools/test_dashboard/index-mathjax4chtml.html new file mode 100644 index 00000000000..a3293efe4a7 --- /dev/null +++ b/devtools/test_dashboard/index-mathjax4chtml.html @@ -0,0 +1,40 @@ + + + + Plotly.js Devtools - MathJax v4 loaded with chtml output + + + + + +
+ + + + + +
+ +
+ +

MathJax \(V^4\) with \(chtml\) output on the page and svg output on the plotly graphs

+
+
+
+
+ + + + + + + diff --git a/devtools/test_dashboard/index.html b/devtools/test_dashboard/index.html index de0841cc650..521077098c0 100644 --- a/devtools/test_dashboard/index.html +++ b/devtools/test_dashboard/index.html @@ -27,7 +27,7 @@ - + diff --git a/devtools/test_dashboard/server.mjs b/devtools/test_dashboard/server.mjs index dc16f92e332..27ab7e9df38 100644 --- a/devtools/test_dashboard/server.mjs +++ b/devtools/test_dashboard/server.mjs @@ -11,6 +11,7 @@ var PORT = args.port || 3000; var strict = args.strict; var mathjax3 = args.mathjax3; var mathjax3chtml = args.mathjax3chtml; +var mathjax4chtml = args.mathjax4chtml; if (strict) localDevConfig.entryPoints = ['./lib/index-strict.js']; @@ -60,6 +61,7 @@ function devServer() { let indexName = 'index'; if (mathjax3) indexName += '-mathjax3'; else if (mathjax3chtml) indexName += '-mathjax3chtml'; + else if (mathjax4chtml) indexName += '-mathjax4chtml'; indexName += '.html'; // open up browser window diff --git a/draftlogs/7898_change.md b/draftlogs/7898_change.md new file mode 100644 index 00000000000..18123616578 --- /dev/null +++ b/draftlogs/7898_change.md @@ -0,0 +1 @@ +- **Breaking:** Add support for MathJax v4, and **drop support for v2** [[#7898](https://github.com/plotly/plotly.js/pull/7898)] diff --git a/package-lock.json b/package-lock.json index 33b6740c070..f08cad23e78 100644 --- a/package-lock.json +++ b/package-lock.json @@ -54,8 +54,8 @@ }, "devDependencies": { "@biomejs/biome": "2.2.0", - "@plotly/mathjax-v2": "npm:mathjax@2.7.5", - "@plotly/mathjax-v3": "npm:mathjax@^3.2.2", + "@plotly/mathjax-v3": "npm:mathjax@3.2.2", + "@plotly/mathjax-v4": "npm:mathjax@^4.1.2", "@types/d3": "3.5.34", "@types/node": "^24.10.0", "amdefine": "^1.0.1", @@ -626,6 +626,13 @@ "resolved": "https://registry.npmjs.org/tinyqueue/-/tinyqueue-3.0.0.tgz", "integrity": "sha512-gRa9gwYU3ECmQYv3lslts5hxuIa90veaEcxDYuu3QGOIAEM2mOZkVHp48ANJuu1CURtRdHKUBY5Lm1tHV+sD4g==" }, + "node_modules/@mathjax/mathjax-newcm-font": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@mathjax/mathjax-newcm-font/-/mathjax-newcm-font-4.1.3.tgz", + "integrity": "sha512-gzAB3dFHilHX1l5x2xUqRL+1jDQt3Fyza1DkEMVXWC4E8SvsGdlgEza47HYi2WhVcgfkvf4zgUGzuhbq3Pjlew==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -697,13 +704,6 @@ "elementary-circuits-directed-graph": "^1.0.4" } }, - "node_modules/@plotly/mathjax-v2": { - "name": "mathjax", - "version": "2.7.5", - "resolved": "https://registry.npmjs.org/mathjax/-/mathjax-2.7.5.tgz", - "integrity": "sha512-OzsJNitEHAJB3y4IIlPCAvS0yoXwYjlo2Y4kmm9KQzyIBZt2d8yKRalby3uTRNN4fZQiGL2iMXjpdP1u2Rq2DQ==", - "dev": true - }, "node_modules/@plotly/mathjax-v3": { "name": "mathjax", "version": "3.2.2", @@ -711,6 +711,17 @@ "integrity": "sha512-Bt+SSVU8eBG27zChVewOicYs7Xsdt40qm4+UpHyX7k0/O9NliPc+x77k1/FEsPsjKPZGJvtRZM1vO+geW0OhGw==", "dev": true }, + "node_modules/@plotly/mathjax-v4": { + "name": "mathjax", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/mathjax/-/mathjax-4.1.3.tgz", + "integrity": "sha512-BN/8Pkgn7G1pIDYJqd9md+JHsE/jydSYbyOZnSdSA0WziuVO8mRxdYiWFumkVVly/8U+hm9DpIIoWuvySverzw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@mathjax/mathjax-newcm-font": "^4.1.3" + } + }, "node_modules/@plotly/point-cluster": { "version": "3.1.9", "resolved": "https://registry.npmjs.org/@plotly/point-cluster/-/point-cluster-3.1.9.tgz", diff --git a/package.json b/package.json index 5b3d9044e75..58da8293302 100644 --- a/package.json +++ b/package.json @@ -56,6 +56,7 @@ "b64": "python3 test/image/generate_b64_mocks.py && node devtools/test_dashboard/server.mjs", "mathjax3": "node devtools/test_dashboard/server.mjs --mathjax3", "mathjax3chtml": "node devtools/test_dashboard/server.mjs --mathjax3chtml", + "mathjax4chtml": "node devtools/test_dashboard/server.mjs --mathjax4chtml", "strict": "node devtools/test_dashboard/server.mjs --strict", "start": "node devtools/test_dashboard/server.mjs", "baseline": "node test/image/make_baseline.mjs", @@ -115,8 +116,8 @@ }, "devDependencies": { "@biomejs/biome": "2.2.0", - "@plotly/mathjax-v2": "npm:mathjax@2.7.5", - "@plotly/mathjax-v3": "npm:mathjax@^3.2.2", + "@plotly/mathjax-v3": "npm:mathjax@3.2.2", + "@plotly/mathjax-v4": "npm:mathjax@^4.1.2", "@types/d3": "3.5.34", "@types/node": "^24.10.0", "amdefine": "^1.0.1", diff --git a/src/lib/svg_text_utils.js b/src/lib/svg_text_utils.js index d4bd953684b..6e133611075 100644 --- a/src/lib/svg_text_utils.js +++ b/src/lib/svg_text_utils.js @@ -78,6 +78,10 @@ exports.convertToTspans = function(_context, gd, _callback) { _context.style('display', 'none'); var fontSize = parseInt(_context.node().style.fontSize, 10); var config = {fontSize: fontSize}; + // Capture the text-anchor value now, since the the callback function below + // will run async, and in the meantime the text-anchor may change + // which would result in a wrongly-positioned SVG + var textAnchor = _context.attr('text-anchor'); texToSVG(tex[2], config, function(_svgEl, _glyphDefs, _svgBBox) { parent.selectAll('svg.' + svgClass).remove(); @@ -90,6 +94,13 @@ exports.convertToTspans = function(_context, gd, _callback) { return; } + // Now that the MathJax render has finished, re-hide the source text. + // We hid it earlier, too, but since this callback runs async, + // another function may have made it visible again + // TODO: investigate this issue more deeply, there's probably a better + // solution than this band-aid fix + _context.style('display', 'none'); + var mathjaxGroup = parent.append('g') .classed(svgClass + '-group', true) .attr({ @@ -114,7 +125,11 @@ exports.convertToTspans = function(_context, gd, _callback) { height: h0, preserveAspectRatio: 'xMinYMin meet' }) - .style({overflow: 'visible', 'pointer-events': 'none'}); + .style({ + overflow: 'visible', + 'pointer-events': 'none', + 'font-size': config.fontSize + 'px', + }); var fill = _context.node().style.fill || 'black'; var g = newSvg.select('g'); @@ -151,11 +166,9 @@ exports.convertToTspans = function(_context, gd, _callback) { x = 0; y = dy; } else { - var anchor = _context.attr('text-anchor'); - x = x - w * ( - anchor === 'middle' ? 0.5 : - anchor === 'end' ? 1 : 0 + textAnchor === 'middle' ? 0.5 : + textAnchor === 'end' ? 1 : 0 ); y = y + dy - h / 2; } @@ -185,154 +198,91 @@ function cleanEscapesForTex(s) { .replace(GT_MATCH, '\\gt '); } -var inlineMath = [['$', '$'], ['\\(', '\\)']]; +// Create a dedicated MathDocument just for Plotly.js, +// to avoid interfering with any other MathJax on the page. +// mathjaxSVGDocument is initialized on the first call to texToSVG(), +// and reused for subsequent calls. +var mathjaxSVGDocument = null; function texToSVG(_texString, _config, _callback) { - var MathJaxVersion = parseInt( + const MathJaxVersion = parseInt( (MathJax.version || '').split('.')[0] ); if( - MathJaxVersion !== 2 && - MathJaxVersion !== 3 + MathJaxVersion !== 3 && + MathJaxVersion !== 4 ) { - Lib.warn('No MathJax version:', MathJax.version); + Lib.warn('Unsupported MathJax version:', MathJax.version); return; } - var originalRenderer, - originalConfig, - originalProcessSectionDelay, - tmpDiv; - - var setConfig2 = function() { - originalConfig = Lib.extendDeepAll({}, MathJax.Hub.config); - - originalProcessSectionDelay = MathJax.Hub.processSectionDelay; - if(MathJax.Hub.processSectionDelay !== undefined) { - // MathJax 2.5+ but not 3+ - MathJax.Hub.processSectionDelay = 0; - } - - return MathJax.Hub.Config({ - messageStyle: 'none', - tex2jax: { - inlineMath: inlineMath - }, - displayAlign: 'left', - }); - }; - - var setConfig3 = function() { - originalConfig = Lib.extendDeepAll({}, MathJax.config); - - if(!MathJax.config.tex) { - MathJax.config.tex = {}; - } - - MathJax.config.tex.inlineMath = inlineMath; - }; - - var setRenderer2 = function() { - originalRenderer = MathJax.Hub.config.menuSettings.renderer; - if(originalRenderer !== 'SVG') { - return MathJax.Hub.setRenderer('SVG'); - } - }; - - var setRenderer3 = function() { - originalRenderer = MathJax.config.startup.output; - if(originalRenderer !== 'svg') { - MathJax.config.startup.output = 'svg'; + var tmpDiv; + + const initiateMathJax = function() { + if(!mathjaxSVGDocument) { + const SVG = MathJax._.output.svg_ts.SVG; + // fontCache 'local' keeps each rendered svg self-contained + const svgConfig = Lib.extendFlat({}, MathJax.config.svg, {fontCache: 'local'}); + // MathJax v4 enables automatic inline linebreaking by default, which + // messes up our layout assumptions. Disabling it gives behavior consistent with v3. + if(MathJaxVersion === 4) { + svgConfig.linebreaks = Lib.extendFlat({}, svgConfig.linebreaks, {inline: false}); + } + mathjaxSVGDocument = MathJax._.mathjax.mathjax.document(document, Lib.extendFlat({}, MathJax.config.options, { + InputJax: MathJax.startup.input, + OutputJax: new SVG(svgConfig) + })); } - }; - var initiateMathJax = function() { - var randomID = 'math-output-' + Lib.randstr({}, 64); + const randomID = 'math-output-' + Lib.randstr({}, 64); tmpDiv = d3.select('body').append('div') .attr({id: randomID}) .style({ visibility: 'hidden', position: 'absolute', 'font-size': _config.fontSize + 'px' - }) - .text(cleanEscapesForTex(_texString)); - - var tmpNode = tmpDiv.node(); + }); - return MathJaxVersion === 2 ? - MathJax.Hub.Typeset(tmpNode) : - MathJax.typeset([tmpNode]); + // Strip $…$ delimiters and clean up escape charaters, + // then pass the result to MathDocument.convert() to get an SVG element back + const texMath = cleanEscapesForTex(_texString).replace(/^\$+|\$+$/g, ''); + // handleRetriesFor() automatically retries a MathJax function if it fails + // due to a transient error (docs: https://docs.mathjax.org/en/v4.0/web/retry.html) + // handleRetriesFor() returns a promise which resolves to the result of the provided function + return MathJax._.mathjax.mathjax.handleRetriesFor(function() { + return mathjaxSVGDocument.convert(texMath, { + display: false + }); + }).then(function(node) { + tmpDiv.node().appendChild(node); + }); }; - var finalizeMathJax = function() { - var sel = tmpDiv.select( - MathJaxVersion === 2 ? '.MathJax_SVG' : '.MathJax' - ); + const finalizeMathJax = function() { + const sel = tmpDiv.select('.MathJax'); - var node = !sel.empty() && tmpDiv.select('svg').node(); + const node = !sel.empty() && tmpDiv.select('svg').node(); if(!node) { Lib.log('There was an error in the tex syntax.', _texString); _callback(); } else { - var nodeBBox = node.getBoundingClientRect(); - var glyphDefs; - if(MathJaxVersion === 2) { - glyphDefs = d3.select('body').select('#MathJax_SVG_glyphs'); - } else { - glyphDefs = sel.select('defs'); - } + const nodeBBox = node.getBoundingClientRect(); + const glyphDefs = sel.select('defs'); _callback(sel, glyphDefs, nodeBBox); } tmpDiv.remove(); }; - var resetRenderer2 = function() { - if(originalRenderer !== 'SVG') { - return MathJax.Hub.setRenderer(originalRenderer); - } - }; - - var resetRenderer3 = function() { - if(originalRenderer !== 'svg') { - MathJax.config.startup.output = originalRenderer; - } - }; - - var resetConfig2 = function() { - if(originalProcessSectionDelay !== undefined) { - MathJax.Hub.processSectionDelay = originalProcessSectionDelay; - } - return MathJax.Hub.Config(originalConfig); - }; - - var resetConfig3 = function() { - MathJax.config = originalConfig; - }; - - if(MathJaxVersion === 2) { - MathJax.Hub.Queue( - setConfig2, - setRenderer2, - initiateMathJax, - finalizeMathJax, - resetRenderer2, - resetConfig2 - ); - } else if(MathJaxVersion === 3) { - setConfig3(); - setRenderer3(); - MathJax.startup.defaultReady(); - - MathJax.startup.promise.then(function() { - initiateMathJax(); - finalizeMathJax(); - - resetRenderer3(); - resetConfig3(); + Promise.resolve() + .then(initiateMathJax) + .then(finalizeMathJax) + .catch((err) => { + Lib.log('MathJax typesetting failed.', _texString, err); + if(tmpDiv) tmpDiv.remove(); + _callback(); }); - } } var TAG_STYLES = { diff --git a/src/plot_api/plot_config.js b/src/plot_api/plot_config.js index f8f293a95e3..28fbab70b21 100644 --- a/src/plot_api/plot_config.js +++ b/src/plot_api/plot_config.js @@ -27,7 +27,7 @@ var configAttributes = { dflt: true, description: [ 'Determines whether math should be typeset or not,', - 'when MathJax (either v2 or v3) is present on the page.' + 'when MathJax (either v3 or v4) is present on the page.' ].join(' ') }, diff --git a/src/plot_api/subroutines.js b/src/plot_api/subroutines.js index 7cb735ce338..666f3e7b6a5 100644 --- a/src/plot_api/subroutines.js +++ b/src/plot_api/subroutines.js @@ -684,7 +684,7 @@ exports.doLegend = function(gd) { }; exports.doTicksRelayout = function(gd) { - Axes.draw(gd, 'redraw'); + var drawPromise = Axes.draw(gd, 'redraw'); if(gd._fullLayout._hasOnlyLargeSploms) { Registry.subplotsRegistry.splom.updateGrid(gd); @@ -693,7 +693,9 @@ exports.doTicksRelayout = function(gd) { } exports.drawMainTitle(gd); - return Plots.previousPromises(gd); + return Promise.resolve(drawPromise).then(function() { + return Plots.previousPromises(gd); + }); }; exports.doModeBar = function(gd) { diff --git a/src/types/generated/schema.d.ts b/src/types/generated/schema.d.ts index 92fdc3e79d7..c7ed1fabf77 100644 --- a/src/types/generated/schema.d.ts +++ b/src/types/generated/schema.d.ts @@ -16375,7 +16375,7 @@ export interface ConfigBase { */ topojsonURL?: string; /** - * Determines whether math should be typeset or not, when MathJax (either v2 or v3) is present on the page. + * Determines whether math should be typeset or not, when MathJax (either v3 or v4) is present on the page. * @default true */ typesetMath?: boolean; diff --git a/test/image/baselines/legend_mathjax_title_and_items.png b/test/image/baselines/legend_mathjax_title_and_items.png index 0b3181e088d..ff1af32bde5 100644 Binary files a/test/image/baselines/legend_mathjax_title_and_items.png and b/test/image/baselines/legend_mathjax_title_and_items.png differ diff --git a/test/image/baselines/mathjax-font-size.png b/test/image/baselines/mathjax-font-size.png new file mode 100644 index 00000000000..114ec468715 Binary files /dev/null and b/test/image/baselines/mathjax-font-size.png differ diff --git a/test/image/baselines/mathjax.png b/test/image/baselines/mathjax.png index dc454c412b8..e7a2fdb1f56 100644 Binary files a/test/image/baselines/mathjax.png and b/test/image/baselines/mathjax.png differ diff --git a/test/image/baselines/mathjax3___mathjax.png b/test/image/baselines/mathjax3___mathjax.png index d8c6aafc1ed..72e676ce047 100644 Binary files a/test/image/baselines/mathjax3___mathjax.png and b/test/image/baselines/mathjax3___mathjax.png differ diff --git a/test/image/baselines/mathjax3___table_latex_multitrace_scatter.png b/test/image/baselines/mathjax3___table_latex_multitrace_scatter.png index c45a197734c..e4214a24546 100644 Binary files a/test/image/baselines/mathjax3___table_latex_multitrace_scatter.png and b/test/image/baselines/mathjax3___table_latex_multitrace_scatter.png differ diff --git a/test/image/baselines/mathjax3___ternary-mathjax-title-place-subtitle.png b/test/image/baselines/mathjax3___ternary-mathjax-title-place-subtitle.png index 3d06239168e..fa1dfe63bd8 100644 Binary files a/test/image/baselines/mathjax3___ternary-mathjax-title-place-subtitle.png and b/test/image/baselines/mathjax3___ternary-mathjax-title-place-subtitle.png differ diff --git a/test/image/baselines/mathjax3___ternary-mathjax.png b/test/image/baselines/mathjax3___ternary-mathjax.png index a145da8021b..58f3b9302b7 100644 Binary files a/test/image/baselines/mathjax3___ternary-mathjax.png and b/test/image/baselines/mathjax3___ternary-mathjax.png differ diff --git a/test/image/baselines/parcats_grid_subplots.png b/test/image/baselines/parcats_grid_subplots.png index 0925b959212..27899d0ff86 100644 Binary files a/test/image/baselines/parcats_grid_subplots.png and b/test/image/baselines/parcats_grid_subplots.png differ diff --git a/test/image/baselines/table_latex_multitrace_scatter.png b/test/image/baselines/table_latex_multitrace_scatter.png index 973dba8886c..0628fa06092 100644 Binary files a/test/image/baselines/table_latex_multitrace_scatter.png and b/test/image/baselines/table_latex_multitrace_scatter.png differ diff --git a/test/image/baselines/table_plain_birds.png b/test/image/baselines/table_plain_birds.png index 37a4a5533a5..bfe81d92d95 100644 Binary files a/test/image/baselines/table_plain_birds.png and b/test/image/baselines/table_plain_birds.png differ diff --git a/test/image/baselines/table_wrapped_birds.png b/test/image/baselines/table_wrapped_birds.png index fc53fb898f5..b1d5edd261e 100644 Binary files a/test/image/baselines/table_wrapped_birds.png and b/test/image/baselines/table_wrapped_birds.png differ diff --git a/test/image/baselines/ternary-mathjax-title-place-subtitle.png b/test/image/baselines/ternary-mathjax-title-place-subtitle.png index 980a5beb679..8d8586c6572 100644 Binary files a/test/image/baselines/ternary-mathjax-title-place-subtitle.png and b/test/image/baselines/ternary-mathjax-title-place-subtitle.png differ diff --git a/test/image/baselines/ternary-mathjax.png b/test/image/baselines/ternary-mathjax.png index 0eb463e26eb..2f30323640c 100644 Binary files a/test/image/baselines/ternary-mathjax.png and b/test/image/baselines/ternary-mathjax.png differ diff --git a/test/image/make_baseline.py b/test/image/make_baseline.py index 0c0934adc2c..8bce6c7e5ba 100644 --- a/test/image/make_baseline.py +++ b/test/image/make_baseline.py @@ -36,8 +36,6 @@ print("output to", dirOut) -mathjax_version = 2 -mathjax = None if "mathjax3" in sys.argv or "mathjax3=" in sys.argv: # until https://github.com/plotly/Kaleido/issues/124 is addressed # we are uanble to use local mathjax v3 installed in node_modules @@ -46,6 +44,11 @@ mathjax_version = 3 print("Kaleido using MathJax v3") +else: + mathjax_version = 4 + # Kaleido still defaults to MathJax v2, so we need to explicitly specify the path to MathJax v4 + mathjax = "https://cdn.jsdelivr.net/npm/mathjax@4.1.2/tex-svg.js" + print("Kaleido using MathJax v4") virtual_webgl_version = 0 # i.e. virtual-webgl is not used if "virtual-webgl" in sys.argv or "virtual-webgl=" in sys.argv: diff --git a/test/image/make_exports.py b/test/image/make_exports.py index de23ac74271..5d28d078d59 100644 --- a/test/image/make_exports.py +++ b/test/image/make_exports.py @@ -8,7 +8,8 @@ dirOut = os.path.join(root, "build", "test_images") pio.templates.default = "none" -pio.kaleido.scope.plotlyjs = os.path.join(root, "build", "plotly.js") +pio.defaults.plotlyjs = os.path.join(root, "build", "plotly.js") +pio.defaults.mathjax = "https://cdn.jsdelivr.net/npm/mathjax@4.1.2/tex-svg.js" allFormats = ["svg", "jpg", "jpeg", "webp", "pdf"] # 'png' is tested by image-test @@ -31,7 +32,7 @@ "zsmooth_methods", ] -failed = 0 +failed = [] for name in allNames: for fmt in allFormats: print(name + " --> " + fmt) @@ -59,8 +60,15 @@ ) except Exception as e: + import traceback print(e) - failed += 1 + traceback.print_exc() -if failed > 0: + failed.append((name, fmt)) + +if len(failed) > 0: + print("\nFailed on:") + for name, fmt in failed: + print(f" {name} ({fmt})") + print("") sys.exit(1) diff --git a/test/image/mocks/mathjax-font-size.json b/test/image/mocks/mathjax-font-size.json new file mode 100644 index 00000000000..aea78f4d9b6 --- /dev/null +++ b/test/image/mocks/mathjax-font-size.json @@ -0,0 +1,230 @@ +{ + "data": [], + "layout": { + "annotations": [ + { + "text": "Font size 6", + "x": 0.4, + "y": 0.9, + "xref": "paper", + "yref": "paper", + "xanchor": "right", + "yanchor": "middle", + "showarrow": false, + "font": { + "size": 6 + } + }, + { + "text": "$f=6 (\\frac{a^2+b^2}{c})$", + "x": 0.5, + "y": 0.9, + "xref": "paper", + "yref": "paper", + "xanchor": "left", + "yanchor": "middle", + "showarrow": false, + "font": { + "size": 6 + } + }, + { + "text": "Font size 10", + "x": 0.4, + "y": 0.8, + "xref": "paper", + "yref": "paper", + "xanchor": "right", + "yanchor": "middle", + "showarrow": false, + "font": { + "size": 10 + } + }, + { + "text": "$f=10 (\\frac{a^2+b^2}{c})$", + "x": 0.5, + "y": 0.8, + "xref": "paper", + "yref": "paper", + "xanchor": "left", + "yanchor": "middle", + "showarrow": false, + "font": { + "size": 10 + } + }, + { + "text": "Font size 12", + "x": 0.4, + "y": 0.7, + "xref": "paper", + "yref": "paper", + "xanchor": "right", + "yanchor": "middle", + "showarrow": false, + "font": { + "size": 12 + } + }, + { + "text": "$f=12 (\\frac{a^2+b^2}{c})$", + "x": 0.5, + "y": 0.7, + "xref": "paper", + "yref": "paper", + "xanchor": "left", + "yanchor": "middle", + "showarrow": false, + "font": { + "size": 12 + } + }, + { + "text": "Font size 16", + "x": 0.4, + "y": 0.6, + "xref": "paper", + "yref": "paper", + "xanchor": "right", + "yanchor": "middle", + "showarrow": false, + "font": { + "size": 16 + } + }, + { + "text": "$f=16 (\\frac{a^2+b^2}{c})$", + "x": 0.5, + "y": 0.6, + "xref": "paper", + "yref": "paper", + "xanchor": "left", + "yanchor": "middle", + "showarrow": false, + "font": { + "size": 16 + } + }, + { + "text": "Font size 20", + "x": 0.4, + "y": 0.5, + "xref": "paper", + "yref": "paper", + "xanchor": "right", + "yanchor": "middle", + "showarrow": false, + "font": { + "size": 20 + } + }, + { + "text": "$f=20 (\\frac{a^2+b^2}{c})$", + "x": 0.5, + "y": 0.5, + "xref": "paper", + "yref": "paper", + "xanchor": "left", + "yanchor": "middle", + "showarrow": false, + "font": { + "size": 20 + } + }, + { + "text": "Font size 24", + "x": 0.4, + "y": 0.4, + "xref": "paper", + "yref": "paper", + "xanchor": "right", + "yanchor": "middle", + "showarrow": false, + "font": { + "size": 24 + } + }, + { + "text": "$f=24 (\\frac{a^2+b^2}{c})$", + "x": 0.5, + "y": 0.4, + "xref": "paper", + "yref": "paper", + "xanchor": "left", + "yanchor": "middle", + "showarrow": false, + "font": { + "size": 24 + } + }, + { + "text": "Font size 28", + "x": 0.4, + "y": 0.3, + "xref": "paper", + "yref": "paper", + "xanchor": "right", + "yanchor": "middle", + "showarrow": false, + "font": { + "size": 28 + } + }, + { + "text": "$f=28 (\\frac{a^2+b^2}{c})$", + "x": 0.5, + "y": 0.3, + "xref": "paper", + "yref": "paper", + "xanchor": "left", + "yanchor": "middle", + "showarrow": false, + "font": { + "size": 28 + } + }, + { + "text": "Font size 32", + "x": 0.4, + "y": 0.2, + "xref": "paper", + "yref": "paper", + "xanchor": "right", + "yanchor": "middle", + "showarrow": false, + "font": { + "size": 32 + } + }, + { + "text": "$f=32 (\\frac{a^2+b^2}{c})$", + "x": 0.5, + "y": 0.2, + "xref": "paper", + "yref": "paper", + "xanchor": "left", + "yanchor": "middle", + "showarrow": false, + "font": { + "size": 32 + } + } + ], + "width": 600, + "height": 600, + "xaxis": { + "range": [0, 1], + "showgrid": false, + "zeroline": false, + "showticklabels": false + }, + "yaxis": { + "range": [0, 1], + "showgrid": false, + "zeroline": false, + "showticklabels": false + }, + "margin": {"l": 0, "r": 0, "b": 0, "t": 0} + } +} diff --git a/test/jasmine/bundle_tests/mathjax_config_test.js b/test/jasmine/bundle_tests/mathjax_config_test.js index 1d80b6ca933..6172a103ca4 100644 --- a/test/jasmine/bundle_tests/mathjax_config_test.js +++ b/test/jasmine/bundle_tests/mathjax_config_test.js @@ -16,46 +16,32 @@ describe('Test MathJax v' + mathjaxVersion + ' config test:', function() { beforeAll(function(done) { gd = createGraphDiv(); - if(mathjaxVersion === 3) { - window.MathJax = { - startup: { - output: 'chtml', - tex: { - inlineMath: [['|', '|']] - } - } - }; - } + window.MathJax = { + startup: { + output: 'svg' + }, + tex: { + inlineMath: [['|', '|']] + }, + svg: { + fontCache: 'none' + } + }; - var src = mathjaxVersion === 3 ? + const src = mathjaxVersion === 3 ? '/base/node_modules/@plotly/mathjax-v3/es5/tex-svg.js' : - '/base/node_modules/@plotly/mathjax-v2/MathJax.js?config=TeX-AMS_SVG'; + '/base/node_modules/@plotly/mathjax-v4/tex-svg.js'; loadScript(src, done); }); afterAll(destroyGraphDiv); - it('should maintain startup renderer & inlineMath after SVG rendering', function(done) { - if(mathjaxVersion === 2) { - window.MathJax.Hub.Config({ - tex2jax: { - inlineMath: [['|', '|']] - } - }); - - window.MathJax.Hub.setRenderer('CHTML'); - } - + it('should maintain original inlineMath & fontCache config values after SVG rendering', function(done) { // before plot - if(mathjaxVersion === 3) { - expect(window.MathJax.config.startup.output).toEqual('chtml'); - expect(window.MathJax.config.startup.tex.inlineMath).toEqual([['|', '|']]); - } - if(mathjaxVersion === 2) { - expect(window.MathJax.Hub.config.menuSettings.renderer).toEqual(''); - expect(window.MathJax.Hub.config.tex2jax.inlineMath).toEqual([['|', '|']]); - } + expect(window.MathJax.config.startup.output).toEqual('svg'); + expect(window.MathJax.config.tex.inlineMath).toEqual([['|', '|']]); + expect(window.MathJax.config.svg.fontCache).toEqual('none'); Plotly.newPlot(gd, { data: [{ @@ -69,19 +55,9 @@ describe('Test MathJax v' + mathjaxVersion + ' config test:', function() { }) .then(function() { // after plot - if(mathjaxVersion === 3) { - expect(window.MathJax.config.startup.output).toEqual('chtml'); - expect(window.MathJax.config.startup.tex.inlineMath).toEqual([['|', '|']]); - } - if(mathjaxVersion === 2) { - expect(window.MathJax.Hub.config.menuSettings.renderer).toEqual(''); - } - }) - .then(delay(1000)) // TODO: why we need this delay for mathjax v2 here? - .then(function() { - if(mathjaxVersion === 2) { - expect(window.MathJax.Hub.config.tex2jax.inlineMath).toEqual([['|', '|']]); - } + expect(window.MathJax.config.startup.output).toEqual('svg'); + expect(window.MathJax.config.tex.inlineMath).toEqual([['|', '|']]); + expect(window.MathJax.config.svg.fontCache).toEqual('none'); }) .then(done, done.fail); }); diff --git a/test/jasmine/bundle_tests/mathjax_test.js b/test/jasmine/bundle_tests/mathjax_test.js index 4b5d03a4b4b..2602ff3818c 100644 --- a/test/jasmine/bundle_tests/mathjax_test.js +++ b/test/jasmine/bundle_tests/mathjax_test.js @@ -10,10 +10,13 @@ var mathjaxVersion = __karma__.config.mathjaxVersion; describe('Test MathJax v' + mathjaxVersion + ':', function() { beforeAll(function(done) { - var src = mathjaxVersion === 3 ? + const src = mathjaxVersion === 3 ? '/base/node_modules/@plotly/mathjax-v3/es5/tex-svg.js' : - '/base/node_modules/@plotly/mathjax-v2/MathJax.js?config=TeX-AMS-MML_SVG'; + '/base/node_modules/@plotly/mathjax-v4/tex-svg.js'; + // TODO: `?config=` is not needed for MathJax v3 and onward, + // should we adjust these tests? + // N.B. we have to load MathJax "dynamically" as Karma // does not undefined the MathJax's `?config=` parameter. // diff --git a/test/jasmine/karma.conf.js b/test/jasmine/karma.conf.js index c061ff818eb..c551b0ae97e 100644 --- a/test/jasmine/karma.conf.js +++ b/test/jasmine/karma.conf.js @@ -69,7 +69,7 @@ if (argv.info) { '', 'Other options:', ' - `--info`: show this info message', - ' - `--mathjax3`: to load mathjax v3 in relevant test', + ' - `--mathjax3`: to load mathjax v3 in relevant test (otherwise mathjax v4 is loaded)', ' - `--Chrome` (alias `--chrome`): run test in (our custom) Chrome browser', ' - `--Firefox` (alias `--FF`, `--firefox`): run test in (our custom) Firefox browser', ' - `--nowatch (dflt: `false`, `true` on CI)`: run karma w/o `autoWatch` / multiple run mode', @@ -128,8 +128,8 @@ if (isFullSuite) { var pathToCustomMatchers = path.join(__dirname, 'assets', 'custom_matchers.js'); var pathToTopojsonDist = path.join(__dirname, '..', '..', 'topojson', 'dist'); -var pathToMathJax2 = path.join(__dirname, '..', '..', 'node_modules', '@plotly/mathjax-v2'); var pathToMathJax3 = path.join(__dirname, '..', '..', 'node_modules', '@plotly/mathjax-v3'); +var pathToMathJax4 = path.join(__dirname, '..', '..', 'node_modules', '@plotly/mathjax-v4'); var pathToVirtualWebgl = path.join(__dirname, '..', '..', 'node_modules', 'virtual-webgl', 'src', 'virtual-webgl.js'); var reporters = []; @@ -200,10 +200,10 @@ func.defaultConfig = { // N.B. the rest of this field is filled below files: [ pathToCustomMatchers, - // available to fetch from /base/node_modules/@plotly/mathjax-v2/ + // available to fetch from /base/node_modules/@plotly/mathjax-v3/ // more info: http://karma-runner.github.io/3.0/config/files.html - { pattern: pathToMathJax2 + '/**', included: false, watched: false, served: true }, { pattern: pathToMathJax3 + '/**', included: false, watched: false, served: true }, + { pattern: pathToMathJax4 + '/**', included: false, watched: false, served: true }, { pattern: pathToTopojsonDist + '/**', included: false, watched: false, served: true } ], @@ -308,7 +308,7 @@ func.defaultConfig = { tagPrefix: '@', skipTags: isCI ? 'noCI' : null, - mathjaxVersion: argv.mathjax3 ? 3 : 2, + mathjaxVersion: argv.mathjax3 ? 3 : 4, // See https://jasmine.github.io/api/3.4/Configuration.html jasmine: { diff --git a/test/plot-schema.json b/test/plot-schema.json index f4286815758..6a22cc6a378 100644 --- a/test/plot-schema.json +++ b/test/plot-schema.json @@ -340,7 +340,7 @@ "valType": "string" }, "typesetMath": { - "description": "Determines whether math should be typeset or not, when MathJax (either v2 or v3) is present on the page.", + "description": "Determines whether math should be typeset or not, when MathJax (either v3 or v4) is present on the page.", "dflt": true, "valType": "boolean" },