Skip to content

Commit 8dc0d07

Browse files
3w36zj6mroeschke
authored andcommitted
ENH: Add Styler.to_typst() (pandas-dev#60733)
* ENH: Add `to_typst` method to `Styler` * TST: Add `Styler.to_typst()` test cases * STY: Apply Ruff suggestions * DOC: Update What's new * DOC: Update reference * CI: Add `Styler.template_typst` to validation ignore list * DOC: Update docstring format for `Styler.to_typst()` example * DOC: Update versionadded for `Styler.to_typst()` to 3.0.0 in documentation --------- Co-authored-by: Matthew Roeschke <[email protected]>
1 parent 2f3b0ed commit 8dc0d07

File tree

6 files changed

+232
-0
lines changed

6 files changed

+232
-0
lines changed

doc/source/reference/style.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ Styler properties
2727
Styler.template_html_style
2828
Styler.template_html_table
2929
Styler.template_latex
30+
Styler.template_typst
3031
Styler.template_string
3132
Styler.loader
3233

@@ -76,6 +77,7 @@ Style export and import
7677

7778
Styler.to_html
7879
Styler.to_latex
80+
Styler.to_typst
7981
Styler.to_excel
8082
Styler.to_string
8183
Styler.export

pandas/io/formats/style.py

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1174,6 +1174,111 @@ def to_latex(
11741174
)
11751175
return save_to_buffer(latex, buf=buf, encoding=encoding)
11761176

1177+
@overload
1178+
def to_typst(
1179+
self,
1180+
buf: FilePath | WriteBuffer[str],
1181+
*,
1182+
encoding: str | None = ...,
1183+
sparse_index: bool | None = ...,
1184+
sparse_columns: bool | None = ...,
1185+
max_rows: int | None = ...,
1186+
max_columns: int | None = ...,
1187+
) -> None: ...
1188+
1189+
@overload
1190+
def to_typst(
1191+
self,
1192+
buf: None = ...,
1193+
*,
1194+
encoding: str | None = ...,
1195+
sparse_index: bool | None = ...,
1196+
sparse_columns: bool | None = ...,
1197+
max_rows: int | None = ...,
1198+
max_columns: int | None = ...,
1199+
) -> str: ...
1200+
1201+
@Substitution(buf=buffering_args, encoding=encoding_args)
1202+
def to_typst(
1203+
self,
1204+
buf: FilePath | WriteBuffer[str] | None = None,
1205+
*,
1206+
encoding: str | None = None,
1207+
sparse_index: bool | None = None,
1208+
sparse_columns: bool | None = None,
1209+
max_rows: int | None = None,
1210+
max_columns: int | None = None,
1211+
) -> str | None:
1212+
"""
1213+
Write Styler to a file, buffer or string in Typst format.
1214+
1215+
.. versionadded:: 3.0.0
1216+
1217+
Parameters
1218+
----------
1219+
%(buf)s
1220+
%(encoding)s
1221+
sparse_index : bool, optional
1222+
Whether to sparsify the display of a hierarchical index. Setting to False
1223+
will display each explicit level element in a hierarchical key for each row.
1224+
Defaults to ``pandas.options.styler.sparse.index`` value.
1225+
sparse_columns : bool, optional
1226+
Whether to sparsify the display of a hierarchical index. Setting to False
1227+
will display each explicit level element in a hierarchical key for each
1228+
column. Defaults to ``pandas.options.styler.sparse.columns`` value.
1229+
max_rows : int, optional
1230+
The maximum number of rows that will be rendered. Defaults to
1231+
``pandas.options.styler.render.max_rows``, which is None.
1232+
max_columns : int, optional
1233+
The maximum number of columns that will be rendered. Defaults to
1234+
``pandas.options.styler.render.max_columns``, which is None.
1235+
1236+
Rows and columns may be reduced if the number of total elements is
1237+
large. This value is set to ``pandas.options.styler.render.max_elements``,
1238+
which is 262144 (18 bit browser rendering).
1239+
1240+
Returns
1241+
-------
1242+
str or None
1243+
If `buf` is None, returns the result as a string. Otherwise returns `None`.
1244+
1245+
See Also
1246+
--------
1247+
DataFrame.to_typst : Write a DataFrame to a file,
1248+
buffer or string in Typst format.
1249+
1250+
Examples
1251+
--------
1252+
>>> df = pd.DataFrame({"A": [1, 2], "B": [3, 4]})
1253+
>>> df.style.to_typst() # doctest: +SKIP
1254+
1255+
.. code-block:: typst
1256+
1257+
#table(
1258+
columns: 3,
1259+
[], [A], [B],
1260+
1261+
[0], [1], [3],
1262+
[1], [2], [4],
1263+
)
1264+
"""
1265+
obj = self._copy(deepcopy=True)
1266+
1267+
if sparse_index is None:
1268+
sparse_index = get_option("styler.sparse.index")
1269+
if sparse_columns is None:
1270+
sparse_columns = get_option("styler.sparse.columns")
1271+
1272+
text = obj._render_typst(
1273+
sparse_columns=sparse_columns,
1274+
sparse_index=sparse_index,
1275+
max_rows=max_rows,
1276+
max_cols=max_columns,
1277+
)
1278+
return save_to_buffer(
1279+
text, buf=buf, encoding=(encoding if buf is not None else None)
1280+
)
1281+
11771282
@overload
11781283
def to_html(
11791284
self,

pandas/io/formats/style_render.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ class StylerRenderer:
7575
template_html_table = env.get_template("html_table.tpl")
7676
template_html_style = env.get_template("html_style.tpl")
7777
template_latex = env.get_template("latex.tpl")
78+
template_typst = env.get_template("typst.tpl")
7879
template_string = env.get_template("string.tpl")
7980

8081
def __init__(
@@ -224,6 +225,21 @@ def _render_latex(
224225
d.update(kwargs)
225226
return self.template_latex.render(**d)
226227

228+
def _render_typst(
229+
self,
230+
sparse_index: bool,
231+
sparse_columns: bool,
232+
max_rows: int | None = None,
233+
max_cols: int | None = None,
234+
**kwargs,
235+
) -> str:
236+
"""
237+
Render a Styler in typst format
238+
"""
239+
d = self._render(sparse_index, sparse_columns, max_rows, max_cols)
240+
d.update(kwargs)
241+
return self.template_typst.render(**d)
242+
227243
def _render_string(
228244
self,
229245
sparse_index: bool,
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#table(
2+
columns: {{ head[0] | length }},
3+
{% for r in head %}
4+
{% for c in r %}[{% if c["is_visible"] %}{{ c["display_value"] }}{% endif %}],{% if not loop.last %} {% endif%}{% endfor %}
5+
6+
{% endfor %}
7+
8+
{% for r in body %}
9+
{% for c in r %}[{% if c["is_visible"] %}{{ c["display_value"] }}{% endif %}],{% if not loop.last %} {% endif%}{% endfor %}
10+
11+
{% endfor %}
12+
)
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
from textwrap import dedent
2+
3+
import pytest
4+
5+
from pandas import (
6+
DataFrame,
7+
Series,
8+
)
9+
10+
pytest.importorskip("jinja2")
11+
from pandas.io.formats.style import Styler
12+
13+
14+
@pytest.fixture
15+
def df():
16+
return DataFrame(
17+
{"A": [0, 1], "B": [-0.61, -1.22], "C": Series(["ab", "cd"], dtype=object)}
18+
)
19+
20+
21+
@pytest.fixture
22+
def styler(df):
23+
return Styler(df, uuid_len=0, precision=2)
24+
25+
26+
def test_basic_table(styler):
27+
result = styler.to_typst()
28+
expected = dedent(
29+
"""\
30+
#table(
31+
columns: 4,
32+
[], [A], [B], [C],
33+
34+
[0], [0], [-0.61], [ab],
35+
[1], [1], [-1.22], [cd],
36+
)"""
37+
)
38+
assert result == expected
39+
40+
41+
def test_concat(styler):
42+
result = styler.concat(styler.data.agg(["sum"]).style).to_typst()
43+
expected = dedent(
44+
"""\
45+
#table(
46+
columns: 4,
47+
[], [A], [B], [C],
48+
49+
[0], [0], [-0.61], [ab],
50+
[1], [1], [-1.22], [cd],
51+
[sum], [1], [-1.830000], [abcd],
52+
)"""
53+
)
54+
assert result == expected
55+
56+
57+
def test_concat_recursion(styler):
58+
df = styler.data
59+
styler1 = styler
60+
styler2 = Styler(df.agg(["sum"]), uuid_len=0, precision=3)
61+
styler3 = Styler(df.agg(["sum"]), uuid_len=0, precision=4)
62+
result = styler1.concat(styler2.concat(styler3)).to_typst()
63+
expected = dedent(
64+
"""\
65+
#table(
66+
columns: 4,
67+
[], [A], [B], [C],
68+
69+
[0], [0], [-0.61], [ab],
70+
[1], [1], [-1.22], [cd],
71+
[sum], [1], [-1.830], [abcd],
72+
[sum], [1], [-1.8300], [abcd],
73+
)"""
74+
)
75+
assert result == expected
76+
77+
78+
def test_concat_chain(styler):
79+
df = styler.data
80+
styler1 = styler
81+
styler2 = Styler(df.agg(["sum"]), uuid_len=0, precision=3)
82+
styler3 = Styler(df.agg(["sum"]), uuid_len=0, precision=4)
83+
result = styler1.concat(styler2).concat(styler3).to_typst()
84+
expected = dedent(
85+
"""\
86+
#table(
87+
columns: 4,
88+
[], [A], [B], [C],
89+
90+
[0], [0], [-0.61], [ab],
91+
[1], [1], [-1.22], [cd],
92+
[sum], [1], [-1.830], [abcd],
93+
[sum], [1], [-1.8300], [abcd],
94+
)"""
95+
)
96+
assert result == expected

scripts/validate_docstrings.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
"Styler.template_html_style",
4848
"Styler.template_html_table",
4949
"Styler.template_latex",
50+
"Styler.template_typst",
5051
"Styler.template_string",
5152
"Styler.loader",
5253
"errors.InvalidComparison",

0 commit comments

Comments
 (0)