Skip to content

Commit

Permalink
ENH: Add Styler.to_typst() (#60733)
Browse files Browse the repository at this point in the history
* 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]>
  • Loading branch information
3w36zj6 and mroeschke authored Feb 1, 2025
1 parent ea7ff0e commit 9b03dd4
Show file tree
Hide file tree
Showing 7 changed files with 233 additions and 0 deletions.
2 changes: 2 additions & 0 deletions doc/source/reference/style.rst
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ Styler properties
Styler.template_html_style
Styler.template_html_table
Styler.template_latex
Styler.template_typst
Styler.template_string
Styler.loader

Expand Down Expand Up @@ -77,6 +78,7 @@ Style export and import

Styler.to_html
Styler.to_latex
Styler.to_typst
Styler.to_excel
Styler.to_string
Styler.export
Expand Down
1 change: 1 addition & 0 deletions doc/source/whatsnew/v3.0.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ Other enhancements
- :class:`pandas.api.typing.FrozenList` is available for typing the outputs of :attr:`MultiIndex.names`, :attr:`MultiIndex.codes` and :attr:`MultiIndex.levels` (:issue:`58237`)
- :class:`pandas.api.typing.SASReader` is available for typing the output of :func:`read_sas` (:issue:`55689`)
- :meth:`pandas.api.interchange.from_dataframe` now uses the `PyCapsule Interface <https://arrow.apache.org/docs/format/CDataInterface/PyCapsuleInterface.html>`_ if available, only falling back to the Dataframe Interchange Protocol if that fails (:issue:`60739`)
- Added :meth:`.Styler.to_typst` to write Styler objects to file, buffer or string in Typst format (:issue:`57617`)
- :class:`pandas.api.typing.NoDefault` is available for typing ``no_default``
- :func:`DataFrame.to_excel` now raises an ``UserWarning`` when the character count in a cell exceeds Excel's limitation of 32767 characters (:issue:`56954`)
- :func:`pandas.merge` now validates the ``how`` parameter input (merge type) (:issue:`59435`)
Expand Down
105 changes: 105 additions & 0 deletions pandas/io/formats/style.py
Original file line number Diff line number Diff line change
Expand Up @@ -1228,6 +1228,111 @@ def to_latex(
)
return save_to_buffer(latex, buf=buf, encoding=encoding)

@overload
def to_typst(
self,
buf: FilePath | WriteBuffer[str],
*,
encoding: str | None = ...,
sparse_index: bool | None = ...,
sparse_columns: bool | None = ...,
max_rows: int | None = ...,
max_columns: int | None = ...,
) -> None: ...

@overload
def to_typst(
self,
buf: None = ...,
*,
encoding: str | None = ...,
sparse_index: bool | None = ...,
sparse_columns: bool | None = ...,
max_rows: int | None = ...,
max_columns: int | None = ...,
) -> str: ...

@Substitution(buf=buffering_args, encoding=encoding_args)
def to_typst(
self,
buf: FilePath | WriteBuffer[str] | None = None,
*,
encoding: str | None = None,
sparse_index: bool | None = None,
sparse_columns: bool | None = None,
max_rows: int | None = None,
max_columns: int | None = None,
) -> str | None:
"""
Write Styler to a file, buffer or string in Typst format.
.. versionadded:: 3.0.0
Parameters
----------
%(buf)s
%(encoding)s
sparse_index : bool, optional
Whether to sparsify the display of a hierarchical index. Setting to False
will display each explicit level element in a hierarchical key for each row.
Defaults to ``pandas.options.styler.sparse.index`` value.
sparse_columns : bool, optional
Whether to sparsify the display of a hierarchical index. Setting to False
will display each explicit level element in a hierarchical key for each
column. Defaults to ``pandas.options.styler.sparse.columns`` value.
max_rows : int, optional
The maximum number of rows that will be rendered. Defaults to
``pandas.options.styler.render.max_rows``, which is None.
max_columns : int, optional
The maximum number of columns that will be rendered. Defaults to
``pandas.options.styler.render.max_columns``, which is None.
Rows and columns may be reduced if the number of total elements is
large. This value is set to ``pandas.options.styler.render.max_elements``,
which is 262144 (18 bit browser rendering).
Returns
-------
str or None
If `buf` is None, returns the result as a string. Otherwise returns `None`.
See Also
--------
DataFrame.to_typst : Write a DataFrame to a file,
buffer or string in Typst format.
Examples
--------
>>> df = pd.DataFrame({"A": [1, 2], "B": [3, 4]})
>>> df.style.to_typst() # doctest: +SKIP
.. code-block:: typst
#table(
columns: 3,
[], [A], [B],
[0], [1], [3],
[1], [2], [4],
)
"""
obj = self._copy(deepcopy=True)

if sparse_index is None:
sparse_index = get_option("styler.sparse.index")
if sparse_columns is None:
sparse_columns = get_option("styler.sparse.columns")

text = obj._render_typst(
sparse_columns=sparse_columns,
sparse_index=sparse_index,
max_rows=max_rows,
max_cols=max_columns,
)
return save_to_buffer(
text, buf=buf, encoding=(encoding if buf is not None else None)
)

@overload
def to_html(
self,
Expand Down
16 changes: 16 additions & 0 deletions pandas/io/formats/style_render.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ class StylerRenderer:
template_html_table = env.get_template("html_table.tpl")
template_html_style = env.get_template("html_style.tpl")
template_latex = env.get_template("latex.tpl")
template_typst = env.get_template("typst.tpl")
template_string = env.get_template("string.tpl")

def __init__(
Expand Down Expand Up @@ -232,6 +233,21 @@ def _render_latex(
d.update(kwargs)
return self.template_latex.render(**d)

def _render_typst(
self,
sparse_index: bool,
sparse_columns: bool,
max_rows: int | None = None,
max_cols: int | None = None,
**kwargs,
) -> str:
"""
Render a Styler in typst format
"""
d = self._render(sparse_index, sparse_columns, max_rows, max_cols)
d.update(kwargs)
return self.template_typst.render(**d)

def _render_string(
self,
sparse_index: bool,
Expand Down
12 changes: 12 additions & 0 deletions pandas/io/formats/templates/typst.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#table(
columns: {{ head[0] | length }},
{% for r in head %}
{% for c in r %}[{% if c["is_visible"] %}{{ c["display_value"] }}{% endif %}],{% if not loop.last %} {% endif%}{% endfor %}

{% endfor %}

{% for r in body %}
{% for c in r %}[{% if c["is_visible"] %}{{ c["display_value"] }}{% endif %}],{% if not loop.last %} {% endif%}{% endfor %}

{% endfor %}
)
96 changes: 96 additions & 0 deletions pandas/tests/io/formats/style/test_to_typst.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
from textwrap import dedent

import pytest

from pandas import (
DataFrame,
Series,
)

pytest.importorskip("jinja2")
from pandas.io.formats.style import Styler


@pytest.fixture
def df():
return DataFrame(
{"A": [0, 1], "B": [-0.61, -1.22], "C": Series(["ab", "cd"], dtype=object)}
)


@pytest.fixture
def styler(df):
return Styler(df, uuid_len=0, precision=2)


def test_basic_table(styler):
result = styler.to_typst()
expected = dedent(
"""\
#table(
columns: 4,
[], [A], [B], [C],
[0], [0], [-0.61], [ab],
[1], [1], [-1.22], [cd],
)"""
)
assert result == expected


def test_concat(styler):
result = styler.concat(styler.data.agg(["sum"]).style).to_typst()
expected = dedent(
"""\
#table(
columns: 4,
[], [A], [B], [C],
[0], [0], [-0.61], [ab],
[1], [1], [-1.22], [cd],
[sum], [1], [-1.830000], [abcd],
)"""
)
assert result == expected


def test_concat_recursion(styler):
df = styler.data
styler1 = styler
styler2 = Styler(df.agg(["sum"]), uuid_len=0, precision=3)
styler3 = Styler(df.agg(["sum"]), uuid_len=0, precision=4)
result = styler1.concat(styler2.concat(styler3)).to_typst()
expected = dedent(
"""\
#table(
columns: 4,
[], [A], [B], [C],
[0], [0], [-0.61], [ab],
[1], [1], [-1.22], [cd],
[sum], [1], [-1.830], [abcd],
[sum], [1], [-1.8300], [abcd],
)"""
)
assert result == expected


def test_concat_chain(styler):
df = styler.data
styler1 = styler
styler2 = Styler(df.agg(["sum"]), uuid_len=0, precision=3)
styler3 = Styler(df.agg(["sum"]), uuid_len=0, precision=4)
result = styler1.concat(styler2).concat(styler3).to_typst()
expected = dedent(
"""\
#table(
columns: 4,
[], [A], [B], [C],
[0], [0], [-0.61], [ab],
[1], [1], [-1.22], [cd],
[sum], [1], [-1.830], [abcd],
[sum], [1], [-1.8300], [abcd],
)"""
)
assert result == expected
1 change: 1 addition & 0 deletions scripts/validate_docstrings.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
"Styler.template_html_style",
"Styler.template_html_table",
"Styler.template_latex",
"Styler.template_typst",
"Styler.template_string",
"Styler.loader",
"errors.InvalidComparison",
Expand Down

0 comments on commit 9b03dd4

Please sign in to comment.