Skip to content

Commit fbfd5c7

Browse files
committed
DOC: Dynamically resize mathtext symbol tables (matplotlib#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 matplotlib#26143
1 parent 86a476d commit fbfd5c7

File tree

2 files changed

+169
-144
lines changed

2 files changed

+169
-144
lines changed

doc/sphinxext/math_symbol_table.py

Lines changed: 102 additions & 144 deletions
Original file line numberDiff line numberDiff line change
@@ -1,152 +1,110 @@
1-
import re
2-
from docutils.parsers.rst import Directive
3-
4-
from matplotlib import _mathtext, _mathtext_data
5-
6-
bb_pattern = re.compile("Bbb[A-Z]")
7-
scr_pattern = re.compile("scr[a-zA-Z]")
8-
frak_pattern = re.compile("frak[A-Z]")
9-
10-
symbols = [
11-
["Lower-case Greek",
12-
4,
13-
(r"\alpha", r"\beta", r"\gamma", r"\chi", r"\delta", r"\epsilon",
14-
r"\eta", r"\iota", r"\kappa", r"\lambda", r"\mu", r"\nu", r"\omega",
15-
r"\phi", r"\pi", r"\psi", r"\rho", r"\sigma", r"\tau", r"\theta",
16-
r"\upsilon", r"\xi", r"\zeta", r"\digamma", r"\varepsilon", r"\varkappa",
17-
r"\varphi", r"\varpi", r"\varrho", r"\varsigma", r"\vartheta")],
18-
["Upper-case Greek",
19-
4,
20-
(r"\Delta", r"\Gamma", r"\Lambda", r"\Omega", r"\Phi", r"\Pi", r"\Psi",
21-
r"\Sigma", r"\Theta", r"\Upsilon", r"\Xi")],
22-
["Hebrew",
23-
6,
24-
(r"\aleph", r"\beth", r"\gimel", r"\daleth")],
25-
["Latin named characters",
26-
6,
27-
r"""\aa \AA \ae \AE \oe \OE \O \o \thorn \Thorn \ss \eth \dh \DH""".split()],
28-
["Delimiters",
29-
5,
30-
_mathtext.Parser._delims],
31-
["Big symbols",
32-
5,
33-
_mathtext.Parser._overunder_symbols | _mathtext.Parser._dropsub_symbols],
34-
["Standard function names",
35-
5,
36-
{fr"\{fn}" for fn in _mathtext.Parser._function_names}],
37-
["Binary operation symbols",
38-
4,
39-
_mathtext.Parser._binary_operators],
40-
["Relation symbols",
41-
4,
42-
_mathtext.Parser._relation_symbols],
43-
["Arrow symbols",
44-
4,
45-
_mathtext.Parser._arrow_symbols],
46-
["Dot symbols",
47-
4,
48-
r"""\cdots \vdots \ldots \ddots \adots \Colon \therefore \because""".split()],
49-
["Black-board characters",
50-
6,
51-
[fr"\{symbol}" for symbol in _mathtext_data.tex2uni
52-
if re.match(bb_pattern, symbol)]],
53-
["Script characters",
54-
6,
55-
[fr"\{symbol}" for symbol in _mathtext_data.tex2uni
56-
if re.match(scr_pattern, symbol)]],
57-
["Fraktur characters",
58-
6,
59-
[fr"\{symbol}" for symbol in _mathtext_data.tex2uni
60-
if re.match(frak_pattern, symbol)]],
61-
["Miscellaneous symbols",
62-
4,
63-
r"""\neg \infty \forall \wp \exists \bigstar \angle \partial
64-
\nexists \measuredangle \emptyset \sphericalangle \clubsuit
65-
\varnothing \complement \diamondsuit \imath \Finv \triangledown
66-
\heartsuit \jmath \Game \spadesuit \ell \hbar \vartriangle
67-
\hslash \blacksquare \blacktriangle \sharp \increment
68-
\prime \blacktriangledown \Im \flat \backprime \Re \natural
69-
\circledS \P \copyright \circledR \S \yen \checkmark \$
70-
\cent \triangle \QED \sinewave \dag \ddag \perthousand \ac
71-
\lambdabar \L \l \degree \danger \maltese \clubsuitopen
72-
\i \hermitmatrix \sterling \nabla \mho""".split()],
73-
]
74-
75-
76-
def run(state_machine):
77-
78-
def render_symbol(sym, ignore_variant=False):
79-
if ignore_variant and sym not in (r"\varnothing", r"\varlrtriangle"):
80-
sym = sym.replace(r"\var", "\\")
81-
if sym.startswith("\\"):
82-
sym = sym.lstrip("\\")
83-
if sym not in (_mathtext.Parser._overunder_functions |
84-
_mathtext.Parser._function_names):
85-
sym = chr(_mathtext_data.tex2uni[sym])
86-
return f'\\{sym}' if sym in ('\\', '|', '+', '-', '*') else sym
87-
88-
lines = []
89-
for category, columns, syms in symbols:
90-
syms = sorted(syms,
91-
# Sort by Unicode and place variants immediately
92-
# after standard versions.
93-
key=lambda sym: (render_symbol(sym, ignore_variant=True),
94-
sym.startswith(r"\var")),
95-
reverse=(category == "Hebrew")) # Hebrew is rtl
96-
rendered_syms = [f"{render_symbol(sym)} ``{sym}``" for sym in syms]
97-
columns = min(columns, len(syms))
98-
lines.append("**%s**" % category)
99-
lines.append('')
100-
max_width = max(map(len, rendered_syms))
101-
header = (('=' * max_width) + ' ') * columns
102-
lines.append(header.rstrip())
103-
for part in range(0, len(rendered_syms), columns):
104-
row = " ".join(
105-
sym.rjust(max_width) for sym in rendered_syms[part:part + columns])
106-
lines.append(row)
107-
lines.append(header.rstrip())
108-
lines.append('')
109-
110-
state_machine.insert_input(lines, "Symbol table")
111-
return []
112-
113-
114-
class MathSymbolTableDirective(Directive):
1+
"""
2+
Sphinx extension that generates the math symbol table documentation
3+
for Matplotlib.
4+
"""
5+
6+
from __future__ import annotations
7+
from textwrap import dedent
8+
from docutils.statemachine import StringList
9+
from docutils import nodes
10+
from sphinx.util.docutils import SphinxDirective
11+
from matplotlib import _mathtext
12+
13+
14+
class MathSymbolTableDirective(SphinxDirective):
15+
"""Generate tables of math symbols grouped by category."""
16+
11517
has_content = False
116-
required_arguments = 0
117-
optional_arguments = 0
118-
final_argument_whitespace = False
119-
option_spec = {}
12018

12119
def run(self):
122-
return run(self.state_machine)
20+
# Build RST lines to be parsed. We include a small CSS style and
21+
# simple HTML wrappers so the result is responsive in the browser.
22+
lines: list[str] = []
23+
24+
# Add responsive CSS styling
25+
style = dedent(
26+
"\n".join(
27+
[
28+
"",
29+
"<style>",
30+
".mpl-symbol-grid {",
31+
" display: grid;",
32+
" grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));",
33+
" gap: 8px;",
34+
" margin: 16px 0;",
35+
"}",
36+
".mpl-symbol-cell {",
37+
" display: flex;",
38+
" flex-direction: column;",
39+
" align-items: center;",
40+
" padding: 8px;",
41+
" border: 1px solid #ddd;",
42+
" border-radius: 4px;",
43+
"}",
44+
".mpl-symbol-cell .math {",
45+
" font-size: 1.2em;",
46+
" margin-bottom: 4px;",
47+
"}",
48+
".mpl-symbol-cell .label {",
49+
" font-family: monospace;",
50+
" font-size: 0.9em;",
51+
" color: #666;",
52+
"}",
53+
"</style>",
54+
"",
55+
]
56+
)
57+
)
58+
59+
# Insert the style as raw HTML block
60+
lines.append(".. raw:: html")
61+
lines.append("")
62+
for style_line in style.splitlines():
63+
lines.append(" " + style_line)
64+
lines.append("")
65+
66+
# Get symbol categories from matplotlib mathtext internals
67+
try:
68+
categories = _mathtext._get_sphinx_symbol_table()
69+
except Exception:
70+
categories = []
71+
72+
for category, _, syms in categories:
73+
# Ensure consistent ordering for reproducible output
74+
syms_list = sorted(list(syms), key=lambda s: str(s))
75+
76+
lines.append(f"**{category}**")
77+
lines.append("")
78+
lines.append(".. raw:: html")
79+
lines.append("")
80+
lines.append(' <div class="mpl-symbol-grid">')
81+
lines.append(' ')
82+
83+
for sym in syms_list:
84+
s = str(sym)
85+
# Use raw TeX inside \( ... \) so MathJax (Sphinx) renders it
86+
tex = s
87+
html_line = (
88+
' <div class="mpl-symbol-cell">'
89+
f'<span class="math">\\({tex}\\)</span>'
90+
f'<span class="label">`{s}`</span>'
91+
"</div>"
92+
)
93+
lines.append(html_line)
94+
95+
lines.append(" </div>")
96+
lines.append(" ")
97+
lines.append("")
98+
99+
# Let Sphinx parse the lines so roles and references work
100+
text = "\n".join(lines)
101+
node = nodes.paragraph()
102+
self.state.nested_parse(StringList(text.splitlines()), 0, node)
103+
return [node]
123104

124105

125106
def setup(app):
107+
"""Register the Sphinx directive."""
126108
app.add_directive("math_symbol_table", MathSymbolTableDirective)
109+
return {"parallel_read_safe": True, "parallel_write_safe": True}
127110

128-
metadata = {'parallel_read_safe': True, 'parallel_write_safe': True}
129-
return metadata
130-
131-
132-
if __name__ == "__main__":
133-
# Do some verification of the tables
134-
135-
print("SYMBOLS NOT IN STIX:")
136-
all_symbols = {}
137-
for category, columns, syms in symbols:
138-
if category == "Standard Function Names":
139-
continue
140-
for sym in syms:
141-
if len(sym) > 1:
142-
all_symbols[sym[1:]] = None
143-
if sym[1:] not in _mathtext_data.tex2uni:
144-
print(sym)
145-
146-
# Add accents
147-
all_symbols.update({v[1:]: k for k, v in _mathtext.Parser._accent_map.items()})
148-
all_symbols.update({v: v for v in _mathtext.Parser._wide_accents})
149-
print("SYMBOLS NOT IN TABLE:")
150-
for sym, val in _mathtext_data.tex2uni.items():
151-
if sym not in all_symbols:
152-
print(f"{sym} = {chr(val)}")

lib/matplotlib/_mathtext.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2840,3 +2840,70 @@ def substack(self, toks: ParseResults) -> T.Any:
28402840
vlt = Vlist(stack)
28412841
result = [Hlist([vlt])]
28422842
return result
2843+
2844+
2845+
2846+
2847+
2848+
2849+
def _get_sphinx_symbol_table():
2850+
"""
2851+
Return symbol categories for documentation generation.
2852+
2853+
Returns
2854+
-------
2855+
list of tuples
2856+
Each tuple contains (category_name, columns, symbol_set)
2857+
"""
2858+
import re
2859+
from matplotlib import _mathtext_data
2860+
2861+
bb_pattern = re.compile("Bbb[A-Z]")
2862+
scr_pattern = re.compile("scr[a-zA-Z]")
2863+
frak_pattern = re.compile("frak[A-Z]")
2864+
2865+
return [
2866+
["Lower-case Greek", 4,
2867+
{r"\alpha", r"\beta", r"\gamma", r"\chi", r"\delta", r"\epsilon",
2868+
r"\eta", r"\iota", r"\kappa", r"\lambda", r"\mu", r"\nu", r"\omega",
2869+
r"\phi", r"\pi", r"\psi", r"\rho", r"\sigma", r"\tau", r"\theta",
2870+
r"\upsilon", r"\xi", r"\zeta", r"\digamma", r"\varepsilon", r"\varkappa",
2871+
r"\varphi", r"\varpi", r"\varrho", r"\varsigma", r"\vartheta"}],
2872+
["Upper-case Greek", 4,
2873+
{r"\Delta", r"\Gamma", r"\Lambda", r"\Omega", r"\Phi", r"\Pi", r"\Psi",
2874+
r"\Sigma", r"\Theta", r"\Upsilon", r"\Xi"}],
2875+
["Hebrew", 6,
2876+
{r"\aleph", r"\beth", r"\gimel", r"\daleth"}],
2877+
["Latin named characters", 6,
2878+
set(r"\aa \AA \ae \AE \oe \OE \O \o \thorn \Thorn \ss \eth \dh \DH".split())],
2879+
["Delimiters", 5, Parser._delims],
2880+
["Big symbols", 5, Parser._overunder_symbols | Parser._dropsub_symbols],
2881+
["Standard function names", 5,
2882+
{fr"\{fn}" for fn in Parser._function_names}],
2883+
["Binary operation symbols", 4, Parser._binary_operators],
2884+
["Relation symbols", 4, Parser._relation_symbols],
2885+
["Arrow symbols", 4, Parser._arrow_symbols],
2886+
["Dot symbols", 4,
2887+
set(r"\cdots \vdots \ldots \ddots \adots \Colon \therefore \because".split())],
2888+
["Black-board characters", 6,
2889+
{fr"\{symbol}" for symbol in _mathtext_data.tex2uni
2890+
if re.match(bb_pattern, symbol)}],
2891+
["Script characters", 6,
2892+
{fr"\{symbol}" for symbol in _mathtext_data.tex2uni
2893+
if re.match(scr_pattern, symbol)}],
2894+
["Fraktur characters", 6,
2895+
{fr"\{symbol}" for symbol in _mathtext_data.tex2uni
2896+
if re.match(frak_pattern, symbol)}],
2897+
["Miscellaneous symbols", 4,
2898+
set(r"""\neg \infty \forall \wp \exists \bigstar \angle \partial
2899+
\nexists \measuredangle \emptyset \sphericalangle \clubsuit
2900+
\varnothing \complement \diamondsuit \imath \Finv \triangledown
2901+
\heartsuit \jmath \Game \spadesuit \ell \hbar \vartriangle
2902+
\hslash \blacksquare \blacktriangle \sharp \increment
2903+
\prime \blacktriangledown \Im \flat \backprime \Re \natural
2904+
\circledS \P \copyright \circledR \S \yen \checkmark \$
2905+
\cent \triangle \QED \sinewave \dag \ddag \perthousand \ac
2906+
\lambdabar \L \l \degree \danger \maltese \clubsuitopen
2907+
\i \hermitmatrix \sterling \nabla \mho""".split())],
2908+
]
2909+

0 commit comments

Comments
 (0)