From fbfd5c7f4d2f321c986a68fff506bbcd8818af20 Mon Sep 17 00:00:00 2001 From: nikhil Date: Sat, 25 Oct 2025 10:23:54 +0530 Subject: [PATCH 1/3] DOC: Dynamically resize mathtext symbol tables (#26143) - Replaced static column-based tables with responsive CSS grid layout - Added _get_sphinx_symbol_table() helper function in _mathtext.py - Symbol tables now dynamically wrap based on browser window width - Added HTML/CSS styling for improved mobile/tablet viewing - Symbols grouped by category with consistent ordering Closes #26143 --- doc/sphinxext/math_symbol_table.py | 246 ++++++++++++----------------- lib/matplotlib/_mathtext.py | 67 ++++++++ 2 files changed, 169 insertions(+), 144 deletions(-) diff --git a/doc/sphinxext/math_symbol_table.py b/doc/sphinxext/math_symbol_table.py index a143326ab75b..095c735fdc93 100644 --- a/doc/sphinxext/math_symbol_table.py +++ b/doc/sphinxext/math_symbol_table.py @@ -1,152 +1,110 @@ -import re -from docutils.parsers.rst import Directive - -from matplotlib import _mathtext, _mathtext_data - -bb_pattern = re.compile("Bbb[A-Z]") -scr_pattern = re.compile("scr[a-zA-Z]") -frak_pattern = re.compile("frak[A-Z]") - -symbols = [ - ["Lower-case Greek", - 4, - (r"\alpha", r"\beta", r"\gamma", r"\chi", r"\delta", r"\epsilon", - r"\eta", r"\iota", r"\kappa", r"\lambda", r"\mu", r"\nu", r"\omega", - r"\phi", r"\pi", r"\psi", r"\rho", r"\sigma", r"\tau", r"\theta", - r"\upsilon", r"\xi", r"\zeta", r"\digamma", r"\varepsilon", r"\varkappa", - r"\varphi", r"\varpi", r"\varrho", r"\varsigma", r"\vartheta")], - ["Upper-case Greek", - 4, - (r"\Delta", r"\Gamma", r"\Lambda", r"\Omega", r"\Phi", r"\Pi", r"\Psi", - r"\Sigma", r"\Theta", r"\Upsilon", r"\Xi")], - ["Hebrew", - 6, - (r"\aleph", r"\beth", r"\gimel", r"\daleth")], - ["Latin named characters", - 6, - r"""\aa \AA \ae \AE \oe \OE \O \o \thorn \Thorn \ss \eth \dh \DH""".split()], - ["Delimiters", - 5, - _mathtext.Parser._delims], - ["Big symbols", - 5, - _mathtext.Parser._overunder_symbols | _mathtext.Parser._dropsub_symbols], - ["Standard function names", - 5, - {fr"\{fn}" for fn in _mathtext.Parser._function_names}], - ["Binary operation symbols", - 4, - _mathtext.Parser._binary_operators], - ["Relation symbols", - 4, - _mathtext.Parser._relation_symbols], - ["Arrow symbols", - 4, - _mathtext.Parser._arrow_symbols], - ["Dot symbols", - 4, - r"""\cdots \vdots \ldots \ddots \adots \Colon \therefore \because""".split()], - ["Black-board characters", - 6, - [fr"\{symbol}" for symbol in _mathtext_data.tex2uni - if re.match(bb_pattern, symbol)]], - ["Script characters", - 6, - [fr"\{symbol}" for symbol in _mathtext_data.tex2uni - if re.match(scr_pattern, symbol)]], - ["Fraktur characters", - 6, - [fr"\{symbol}" for symbol in _mathtext_data.tex2uni - if re.match(frak_pattern, symbol)]], - ["Miscellaneous symbols", - 4, - r"""\neg \infty \forall \wp \exists \bigstar \angle \partial - \nexists \measuredangle \emptyset \sphericalangle \clubsuit - \varnothing \complement \diamondsuit \imath \Finv \triangledown - \heartsuit \jmath \Game \spadesuit \ell \hbar \vartriangle - \hslash \blacksquare \blacktriangle \sharp \increment - \prime \blacktriangledown \Im \flat \backprime \Re \natural - \circledS \P \copyright \circledR \S \yen \checkmark \$ - \cent \triangle \QED \sinewave \dag \ddag \perthousand \ac - \lambdabar \L \l \degree \danger \maltese \clubsuitopen - \i \hermitmatrix \sterling \nabla \mho""".split()], -] - - -def run(state_machine): - - def render_symbol(sym, ignore_variant=False): - if ignore_variant and sym not in (r"\varnothing", r"\varlrtriangle"): - sym = sym.replace(r"\var", "\\") - if sym.startswith("\\"): - sym = sym.lstrip("\\") - if sym not in (_mathtext.Parser._overunder_functions | - _mathtext.Parser._function_names): - sym = chr(_mathtext_data.tex2uni[sym]) - return f'\\{sym}' if sym in ('\\', '|', '+', '-', '*') else sym - - lines = [] - for category, columns, syms in symbols: - syms = sorted(syms, - # Sort by Unicode and place variants immediately - # after standard versions. - key=lambda sym: (render_symbol(sym, ignore_variant=True), - sym.startswith(r"\var")), - reverse=(category == "Hebrew")) # Hebrew is rtl - rendered_syms = [f"{render_symbol(sym)} ``{sym}``" for sym in syms] - columns = min(columns, len(syms)) - lines.append("**%s**" % category) - lines.append('') - max_width = max(map(len, rendered_syms)) - header = (('=' * max_width) + ' ') * columns - lines.append(header.rstrip()) - for part in range(0, len(rendered_syms), columns): - row = " ".join( - sym.rjust(max_width) for sym in rendered_syms[part:part + columns]) - lines.append(row) - lines.append(header.rstrip()) - lines.append('') - - state_machine.insert_input(lines, "Symbol table") - return [] - - -class MathSymbolTableDirective(Directive): +""" +Sphinx extension that generates the math symbol table documentation +for Matplotlib. +""" + +from __future__ import annotations +from textwrap import dedent +from docutils.statemachine import StringList +from docutils import nodes +from sphinx.util.docutils import SphinxDirective +from matplotlib import _mathtext + + +class MathSymbolTableDirective(SphinxDirective): + """Generate tables of math symbols grouped by category.""" + has_content = False - required_arguments = 0 - optional_arguments = 0 - final_argument_whitespace = False - option_spec = {} def run(self): - return run(self.state_machine) + # Build RST lines to be parsed. We include a small CSS style and + # simple HTML wrappers so the result is responsive in the browser. + lines: list[str] = [] + + # Add responsive CSS styling + style = dedent( + "\n".join( + [ + "", + "", + "", + ] + ) + ) + + # Insert the style as raw HTML block + lines.append(".. raw:: html") + lines.append("") + for style_line in style.splitlines(): + lines.append(" " + style_line) + lines.append("") + + # Get symbol categories from matplotlib mathtext internals + try: + categories = _mathtext._get_sphinx_symbol_table() + except Exception: + categories = [] + + for category, _, syms in categories: + # Ensure consistent ordering for reproducible output + syms_list = sorted(list(syms), key=lambda s: str(s)) + + lines.append(f"**{category}**") + lines.append("") + lines.append(".. raw:: html") + lines.append("") + lines.append('
') + lines.append(' ') + + for sym in syms_list: + s = str(sym) + # Use raw TeX inside \( ... \) so MathJax (Sphinx) renders it + tex = s + html_line = ( + '
' + f'\\({tex}\\)' + f'`{s}`' + "
" + ) + lines.append(html_line) + + lines.append("
") + lines.append(" ") + lines.append("") + + # Let Sphinx parse the lines so roles and references work + text = "\n".join(lines) + node = nodes.paragraph() + self.state.nested_parse(StringList(text.splitlines()), 0, node) + return [node] def setup(app): + """Register the Sphinx directive.""" app.add_directive("math_symbol_table", MathSymbolTableDirective) + return {"parallel_read_safe": True, "parallel_write_safe": True} - metadata = {'parallel_read_safe': True, 'parallel_write_safe': True} - return metadata - - -if __name__ == "__main__": - # Do some verification of the tables - - print("SYMBOLS NOT IN STIX:") - all_symbols = {} - for category, columns, syms in symbols: - if category == "Standard Function Names": - continue - for sym in syms: - if len(sym) > 1: - all_symbols[sym[1:]] = None - if sym[1:] not in _mathtext_data.tex2uni: - print(sym) - - # Add accents - all_symbols.update({v[1:]: k for k, v in _mathtext.Parser._accent_map.items()}) - all_symbols.update({v: v for v in _mathtext.Parser._wide_accents}) - print("SYMBOLS NOT IN TABLE:") - for sym, val in _mathtext_data.tex2uni.items(): - if sym not in all_symbols: - print(f"{sym} = {chr(val)}") diff --git a/lib/matplotlib/_mathtext.py b/lib/matplotlib/_mathtext.py index 2258c63e1bc2..68cd3901a41d 100644 --- a/lib/matplotlib/_mathtext.py +++ b/lib/matplotlib/_mathtext.py @@ -2840,3 +2840,70 @@ def substack(self, toks: ParseResults) -> T.Any: vlt = Vlist(stack) result = [Hlist([vlt])] return result + + + + + + +def _get_sphinx_symbol_table(): + """ + Return symbol categories for documentation generation. + + Returns + ------- + list of tuples + Each tuple contains (category_name, columns, symbol_set) + """ + import re + from matplotlib import _mathtext_data + + bb_pattern = re.compile("Bbb[A-Z]") + scr_pattern = re.compile("scr[a-zA-Z]") + frak_pattern = re.compile("frak[A-Z]") + + return [ + ["Lower-case Greek", 4, + {r"\alpha", r"\beta", r"\gamma", r"\chi", r"\delta", r"\epsilon", + r"\eta", r"\iota", r"\kappa", r"\lambda", r"\mu", r"\nu", r"\omega", + r"\phi", r"\pi", r"\psi", r"\rho", r"\sigma", r"\tau", r"\theta", + r"\upsilon", r"\xi", r"\zeta", r"\digamma", r"\varepsilon", r"\varkappa", + r"\varphi", r"\varpi", r"\varrho", r"\varsigma", r"\vartheta"}], + ["Upper-case Greek", 4, + {r"\Delta", r"\Gamma", r"\Lambda", r"\Omega", r"\Phi", r"\Pi", r"\Psi", + r"\Sigma", r"\Theta", r"\Upsilon", r"\Xi"}], + ["Hebrew", 6, + {r"\aleph", r"\beth", r"\gimel", r"\daleth"}], + ["Latin named characters", 6, + set(r"\aa \AA \ae \AE \oe \OE \O \o \thorn \Thorn \ss \eth \dh \DH".split())], + ["Delimiters", 5, Parser._delims], + ["Big symbols", 5, Parser._overunder_symbols | Parser._dropsub_symbols], + ["Standard function names", 5, + {fr"\{fn}" for fn in Parser._function_names}], + ["Binary operation symbols", 4, Parser._binary_operators], + ["Relation symbols", 4, Parser._relation_symbols], + ["Arrow symbols", 4, Parser._arrow_symbols], + ["Dot symbols", 4, + set(r"\cdots \vdots \ldots \ddots \adots \Colon \therefore \because".split())], + ["Black-board characters", 6, + {fr"\{symbol}" for symbol in _mathtext_data.tex2uni + if re.match(bb_pattern, symbol)}], + ["Script characters", 6, + {fr"\{symbol}" for symbol in _mathtext_data.tex2uni + if re.match(scr_pattern, symbol)}], + ["Fraktur characters", 6, + {fr"\{symbol}" for symbol in _mathtext_data.tex2uni + if re.match(frak_pattern, symbol)}], + ["Miscellaneous symbols", 4, + set(r"""\neg \infty \forall \wp \exists \bigstar \angle \partial + \nexists \measuredangle \emptyset \sphericalangle \clubsuit + \varnothing \complement \diamondsuit \imath \Finv \triangledown + \heartsuit \jmath \Game \spadesuit \ell \hbar \vartriangle + \hslash \blacksquare \blacktriangle \sharp \increment + \prime \blacktriangledown \Im \flat \backprime \Re \natural + \circledS \P \copyright \circledR \S \yen \checkmark \$ + \cent \triangle \QED \sinewave \dag \ddag \perthousand \ac + \lambdabar \L \l \degree \danger \maltese \clubsuitopen + \i \hermitmatrix \sterling \nabla \mho""".split())], + ] + From 06bb1e3f6e08cebd295e74c54831426fb401a6ce Mon Sep 17 00:00:00 2001 From: 0NIKHIL0 Date: Sat, 1 Nov 2025 13:15:27 +0530 Subject: [PATCH 2/3] Fix blank line whitespace issues in math_symbol_table.py --- doc/sphinxext/math_symbol_table.py | 34 +++++++++++++----------------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/doc/sphinxext/math_symbol_table.py b/doc/sphinxext/math_symbol_table.py index 095c735fdc93..0c67c4f7da71 100644 --- a/doc/sphinxext/math_symbol_table.py +++ b/doc/sphinxext/math_symbol_table.py @@ -2,7 +2,6 @@ Sphinx extension that generates the math symbol table documentation for Matplotlib. """ - from __future__ import annotations from textwrap import dedent from docutils.statemachine import StringList @@ -10,23 +9,22 @@ from sphinx.util.docutils import SphinxDirective from matplotlib import _mathtext - class MathSymbolTableDirective(SphinxDirective): """Generate tables of math symbols grouped by category.""" - + has_content = False def run(self): # Build RST lines to be parsed. We include a small CSS style and # simple HTML wrappers so the result is responsive in the browser. lines: list[str] = [] - + # Add responsive CSS styling style = dedent( "\n".join( [ "", - "", + "", "", ] ) ) - + # Insert the style as raw HTML block lines.append(".. raw:: html") lines.append("") for style_line in style.splitlines(): lines.append(" " + style_line) lines.append("") - + # Get symbol categories from matplotlib mathtext internals try: categories = _mathtext._get_sphinx_symbol_table() except Exception: categories = [] - + for category, _, syms in categories: # Ensure consistent ordering for reproducible output syms_list = sorted(list(syms), key=lambda s: str(s)) - + lines.append(f"**{category}**") lines.append("") lines.append(".. raw:: html") lines.append("") - lines.append('
') + lines.append('
') lines.append(' ') - + for sym in syms_list: s = str(sym) # Use raw TeX inside \( ... \) so MathJax (Sphinx) renders it tex = s html_line = ( - '
' - f'\\({tex}\\)' - f'`{s}`' + '
' + f'\\({tex}\\)' + f'`{s}`' "
" ) lines.append(html_line) - + lines.append("
") lines.append(" ") lines.append("") - + # Let Sphinx parse the lines so roles and references work text = "\n".join(lines) node = nodes.paragraph() self.state.nested_parse(StringList(text.splitlines()), 0, node) return [node] - def setup(app): """Register the Sphinx directive.""" app.add_directive("math_symbol_table", MathSymbolTableDirective) return {"parallel_read_safe": True, "parallel_write_safe": True} - From 89a0f515ea52dd374384d7f4803111f90707c9fd Mon Sep 17 00:00:00 2001 From: 0NIKHIL0 Date: Sat, 1 Nov 2025 13:18:34 +0530 Subject: [PATCH 3/3] Fix E302 style errors in math_symbol_table.py - add blank lines before class and function definitions --- doc/sphinxext/math_symbol_table.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/sphinxext/math_symbol_table.py b/doc/sphinxext/math_symbol_table.py index 0c67c4f7da71..941e5f2bdf43 100644 --- a/doc/sphinxext/math_symbol_table.py +++ b/doc/sphinxext/math_symbol_table.py @@ -9,6 +9,7 @@ from sphinx.util.docutils import SphinxDirective from matplotlib import _mathtext + class MathSymbolTableDirective(SphinxDirective): """Generate tables of math symbols grouped by category.""" @@ -100,6 +101,7 @@ def run(self): self.state.nested_parse(StringList(text.splitlines()), 0, node) return [node] + def setup(app): """Register the Sphinx directive.""" app.add_directive("math_symbol_table", MathSymbolTableDirective)