Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ENH: Add Styler.to_typst() #60733

Merged
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
Loading