Skip to content

REF: Styler.export #43318

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

Merged
merged 24 commits into from
Oct 23, 2021
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions doc/source/user_guide/style.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -1380,8 +1380,12 @@
"metadata": {},
"outputs": [],
"source": [
"style1 = df2.style.applymap(style_negative, props='color:red;')\\\n",
" .applymap(lambda v: 'opacity: 20%;' if (v < 0.3) and (v > -0.3) else None)"
"style1 = df2.style\\\n",
" .applymap(style_negative, props='color:red;')\\\n",
" .applymap(lambda v: 'opacity: 20%;' if (v < 0.3) and (v > -0.3) else None)\\\n",
" .set_table_styles([{\"selector\": \"th\", \"props\": \"color: blue;\"}])\\\n",
" .hide_index()\n",
"style1"
]
},
{
Expand Down
1 change: 1 addition & 0 deletions doc/source/whatsnew/v1.4.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ Styler
- :meth:`Styler.to_html` omits CSSStyle rules for hidden table elements (:issue:`43619`)
- Custom CSS classes can now be directly specified without string replacement (:issue:`43686`)
- Bug where row trimming failed to reflect hidden rows (:issue:`43703`)
- Update and expand the export and use mechanics (:issue:`40675`)

Formerly Styler relied on ``display.html.use_mathjax``, which has now been replaced by ``styler.html.mathjax``.

Expand Down
95 changes: 82 additions & 13 deletions pandas/io/formats/style.py
Original file line number Diff line number Diff line change
Expand Up @@ -1742,9 +1742,9 @@ def set_table_attributes(self, attributes: str) -> Styler:
self.table_attributes = attributes
return self

def export(self) -> list[tuple[Callable, tuple, dict]]:
def export(self) -> dict[str, Any]:
"""
Export the styles applied to the current ``Styler``.
Export the styles applied to the current Styler.

Can be applied to a second Styler with ``Styler.use``.

Expand All @@ -1754,30 +1754,99 @@ def export(self) -> list[tuple[Callable, tuple, dict]]:

See Also
--------
Styler.use: Set the styles on the current ``Styler``.
"""
return self._todo
Styler.use: Set the styles on the current Styler.
Styler.copy: Create a copy of the current Styler.

def use(self, styles: list[tuple[Callable, tuple, dict]]) -> Styler:
"""
Set the styles on the current ``Styler``.
Notes
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i see, prob should followup with an example of this usage, e.g. export/use for the doc-strings.

-----
This method is designed to copy non-data dependent attributes of
one Styler to another. It differs from ``Styler.copy`` where data and
data dependent attributes are also copied.

The following items are exported since they are not generally data dependent:

- Styling functions added by the ``apply`` and ``applymap``
- Whether axes and names are hidden from the display, if unambiguous.
- Table attributes
- Table styles

The following attributes are considered data dependent and therefore not
exported:

- Caption
- UUID
- Tooltips
- Any hidden rows or columns identified by Index labels
- Any formatting applied using ``Styler.format``
- Any CSS classes added using ``Styler.set_td_classes``
"""
return {
"apply": copy.copy(self._todo),
"table_attributes": self.table_attributes,
"table_styles": copy.copy(self.table_styles),
"hide_index": all(self.hide_index_),
"hide_columns": all(self.hide_columns_),
"hide_index_names": self.hide_index_names,
"hide_column_names": self.hide_column_names,
"css": copy.copy(self.css),
}

def use(self, styles: dict[str, Any]) -> Styler:
"""
Set the styles on the current Styler.

Possibly uses styles from ``Styler.export``.

Parameters
----------
styles : list
List of style functions.
styles : dict(str, Any)
List of attributes to add to Styler. Dict keys should contain only:
- "apply": list of styler functions, typically added with ``apply`` or
``applymap``.
- "table_attributes": HTML attributes, typically added with
``set_table_attributes``.
- "table_styles": CSS selectors and properties, typically added with
``set_table_styles``.
- "hide_index": whether the index is hidden, typically added with
``hide_index``, or a boolean list for hidden levels.
- "hide_columns": whether column headers are hidden, typically added with
``hide_columns``, or a boolean list for hidden levels.
- "hide_index_names": whether index names are hidden.
- "hide_column_names": whether column header names are hidden.
- "css": the css class names used.

Returns
-------
self : Styler

See Also
--------
Styler.export : Export the styles to applied to the current ``Styler``.
"""
self._todo.extend(styles)
Styler.export : Export the non data dependent attributes to the current Styler.
"""
self._todo.extend(styles.get("apply", []))
table_attributes: str = self.table_attributes or ""
obj_table_atts: str = (
""
if styles.get("table_attributes") is None
else str(styles.get("table_attributes"))
)
self.set_table_attributes((table_attributes + " " + obj_table_atts).strip())
if styles.get("table_styles"):
self.set_table_styles(styles.get("table_styles"), overwrite=False)

for obj in ["index", "columns"]:
hide_obj = styles.get("hide_" + obj)
if hide_obj is not None:
if isinstance(hide_obj, bool):
n = getattr(self, obj).nlevels
setattr(self, "hide_" + obj + "_", [hide_obj] * n)
else:
setattr(self, "hide_" + obj + "_", hide_obj)

self.hide_index_names = styles.get("hide_index_names", False)
self.hide_column_names = styles.get("hide_column_names", False)
if styles.get("css"):
self.css = styles.get("css") # type: ignore[assignment]
return self

def set_uuid(self, uuid: str) -> Styler:
Expand Down
35 changes: 35 additions & 0 deletions pandas/tests/io/formats/style/test_style.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ def mi_styler(mi_df):
@pytest.fixture
def mi_styler_comp(mi_styler):
# comprehensively add features to mi_styler
mi_styler = mi_styler._copy(deepcopy=True)
mi_styler.css = {**mi_styler.css, **{"row": "ROW", "col": "COL"}}
mi_styler.uuid_len = 5
mi_styler.uuid = "abcde"
Expand Down Expand Up @@ -257,6 +258,10 @@ def test_copy(comprehensive, render, deepcopy, mi_styler, mi_styler_comp):
"cellstyle_map", # render time vars..
"cellstyle_map_columns",
"cellstyle_map_index",
"template_latex", # render templates are class level
"template_html",
"template_html_style",
"template_html_table",
]
if not deepcopy: # check memory locations are equal for all included attributes
for attr in [a for a in styler.__dict__ if (not callable(a) and a not in excl)]:
Expand Down Expand Up @@ -311,6 +316,10 @@ def test_clear(mi_styler_comp):
"cellstyle_map_index", # execution time only
"precision", # deprecated
"na_rep", # deprecated
"template_latex", # render templates are class level
"template_html",
"template_html_style",
"template_html_table",
]
# tests vars are not same vals on obj and clean copy before clear (except for excl)
for attr in [a for a in styler.__dict__ if not (callable(a) or a in excl)]:
Expand All @@ -324,6 +333,32 @@ def test_clear(mi_styler_comp):
assert all(res) if hasattr(res, "__iter__") else res


def test_export(mi_styler_comp, mi_styler):
exp_attrs = [
"_todo",
"hide_index_",
"hide_index_names",
"hide_columns_",
"hide_column_names",
"table_attributes",
"table_styles",
"css",
]
for attr in exp_attrs:
check = getattr(mi_styler, attr) == getattr(mi_styler_comp, attr)
assert not (
all(check) if (hasattr(check, "__iter__") and len(check) > 0) else check
)

export = mi_styler_comp.export()
used = mi_styler.use(export)
for attr in exp_attrs:
check = getattr(used, attr) == getattr(mi_styler_comp, attr)
assert all(check) if (hasattr(check, "__iter__") and len(check) > 0) else check

used.to_html()


def test_hide_raises(mi_styler):
msg = "`subset` and `level` cannot be passed simultaneously"
with pytest.raises(ValueError, match=msg):
Expand Down