Skip to content

BUG: Styler.set_sticky not handling display of index names. 1/2 #42622

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 6 commits into from
Jul 23, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions doc/source/whatsnew/v1.3.1.rst
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ Bug fixes
~~~~~~~~~
- Fixed bug in :meth:`DataFrame.transpose` dropping values when the DataFrame had an Extension Array dtype and a duplicate index (:issue:`42380`)
- Fixed bug in :meth:`DataFrame.to_xml` raising ``KeyError`` when called with ``index=False`` and an offset index (:issue:`42458`)
- Fixed bug in :meth:`.Styler.set_sticky` not handling index names correctly for single index columns case (:issue:`42537`)
- Fixed bug in :meth:`DataFrame.copy` failing to consolidate blocks in the result (:issue:`42579`)

.. ---------------------------------------------------------------------------
Expand Down
54 changes: 46 additions & 8 deletions pandas/io/formats/style.py
Original file line number Diff line number Diff line change
Expand Up @@ -1450,14 +1450,25 @@ def set_sticky(
Whether to make the index or column headers sticky.
pixel_size : int, optional
Required to configure the width of index cells or the height of column
header cells when sticking a MultiIndex. Defaults to 75 and 25 respectively.
header cells when sticking a MultiIndex (or with a named Index).
Defaults to 75 and 25 respectively.
levels : list of int
If ``axis`` is a MultiIndex the specific levels to stick. If ``None`` will
stick all levels.

Returns
-------
self : Styler

Notes
-----
This method uses the CSS 'position: sticky;' property to display. It is
designed to work with visible axes, therefore both:

- `styler.set_sticky(axis="index").hide_index()`
- `styler.set_sticky(axis="columns").hide_columns()`

may produce strange behaviour due to CSS controls with missing elements.
"""
if axis in [0, "index"]:
axis, obj, tag, pos = 0, self.data.index, "tbody", "left"
Expand All @@ -1469,15 +1480,42 @@ def set_sticky(
raise ValueError("`axis` must be one of {0, 1, 'index', 'columns'}")

if not isinstance(obj, pd.MultiIndex):
return self.set_table_styles(
[
# handling MultiIndexes requires different CSS
props = "position:sticky; background-color:white;"

if axis == 1:
# stick the first <tr> of <head> and, if index names, the second <tr>
# if self._hide_columns then no <thead><tr> here will exist: no conflict
styles: CSSStyles = [
{
"selector": f"{tag} th",
"props": f"position:sticky; {pos}:0px; background-color:white;",
"selector": "thead tr:first-child",
"props": props + "top:0px; z-index:2;",
}
],
overwrite=False,
)
]
if not self.index.names[0] is None:
styles[0]["props"] = (
props + f"top:0px; z-index:2; height:{pixel_size}px;"
)
styles.append(
{
"selector": "thead tr:nth-child(2)",
"props": props
+ f"top:{pixel_size}px; z-index:2; height:{pixel_size}px; ",
}
)
else:
# stick the first <th> of each <tr> in both <thead> and <tbody>
# if self._hide_index then no <th> will exist in <tbody>: no conflict
# but <th> will exist in <thead>: conflict with initial element
styles = [
{
"selector": "tr th:first-child",
"props": props + "left:0px; z-index:1;",
}
]

return self.set_table_styles(styles, overwrite=False)

else:
range_idx = list(range(obj.nlevels))

Expand Down
28 changes: 23 additions & 5 deletions pandas/tests/io/formats/style/test_html.py
Original file line number Diff line number Diff line change
Expand Up @@ -272,17 +272,35 @@ def test_caption_as_sequence(styler):

@pytest.mark.parametrize("index", [False, True])
@pytest.mark.parametrize("columns", [False, True])
def test_sticky_basic(styler, index, columns):
@pytest.mark.parametrize("index_name", [True, False])
def test_sticky_basic(styler, index, columns, index_name):
if index_name:
styler.index.name = "some text"
if index:
styler.set_sticky(axis=0)
if columns:
styler.set_sticky(axis=1)

res = styler.set_uuid("").to_html()
cs1 = "tbody th {\n position: sticky;\n left: 0px;\n background-color: white;\n}"
assert (cs1 in res) is index
cs2 = "thead th {\n position: sticky;\n top: 0px;\n background-color: white;\n}"
assert (cs2 in res) is columns

css_for_index = (
"tr th:first-child {\n position: sticky;\n background-color: white;\n "
"left: 0px;\n z-index: 1;\n}"
)
assert (css_for_index in res) is index

css_for_cols_1 = (
"thead tr:first-child {\n position: sticky;\n background-color: white;\n "
"top: 0px;\n z-index: 2;\n"
)
css_for_cols_1 += " height: 25px;\n}" if index_name else "}"
assert (css_for_cols_1 in res) is columns

css_for_cols_2 = (
"thead tr:nth-child(2) {\n position: sticky;\n background-color: white;\n "
"top: 25px;\n z-index: 2;\n height: 25px;\n}"
)
assert (css_for_cols_2 in res) is (index_name and columns)


@pytest.mark.parametrize("index", [False, True])
Expand Down