Skip to content

Commit 41c90cf

Browse files
authored
BUG: Styler.set_sticky not handling display of index names. 1/2 (#42622)
1 parent cb3b4e4 commit 41c90cf

File tree

3 files changed

+70
-13
lines changed

3 files changed

+70
-13
lines changed

doc/source/whatsnew/v1.3.1.rst

+1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ Bug fixes
3838
~~~~~~~~~
3939
- Fixed bug in :meth:`DataFrame.transpose` dropping values when the DataFrame had an Extension Array dtype and a duplicate index (:issue:`42380`)
4040
- Fixed bug in :meth:`DataFrame.to_xml` raising ``KeyError`` when called with ``index=False`` and an offset index (:issue:`42458`)
41+
- Fixed bug in :meth:`.Styler.set_sticky` not handling index names correctly for single index columns case (:issue:`42537`)
4142
- Fixed bug in :meth:`DataFrame.copy` failing to consolidate blocks in the result (:issue:`42579`)
4243

4344
.. ---------------------------------------------------------------------------

pandas/io/formats/style.py

+46-8
Original file line numberDiff line numberDiff line change
@@ -1450,14 +1450,25 @@ def set_sticky(
14501450
Whether to make the index or column headers sticky.
14511451
pixel_size : int, optional
14521452
Required to configure the width of index cells or the height of column
1453-
header cells when sticking a MultiIndex. Defaults to 75 and 25 respectively.
1453+
header cells when sticking a MultiIndex (or with a named Index).
1454+
Defaults to 75 and 25 respectively.
14541455
levels : list of int
14551456
If ``axis`` is a MultiIndex the specific levels to stick. If ``None`` will
14561457
stick all levels.
14571458
14581459
Returns
14591460
-------
14601461
self : Styler
1462+
1463+
Notes
1464+
-----
1465+
This method uses the CSS 'position: sticky;' property to display. It is
1466+
designed to work with visible axes, therefore both:
1467+
1468+
- `styler.set_sticky(axis="index").hide_index()`
1469+
- `styler.set_sticky(axis="columns").hide_columns()`
1470+
1471+
may produce strange behaviour due to CSS controls with missing elements.
14611472
"""
14621473
if axis in [0, "index"]:
14631474
axis, obj, tag, pos = 0, self.data.index, "tbody", "left"
@@ -1469,15 +1480,42 @@ def set_sticky(
14691480
raise ValueError("`axis` must be one of {0, 1, 'index', 'columns'}")
14701481

14711482
if not isinstance(obj, pd.MultiIndex):
1472-
return self.set_table_styles(
1473-
[
1483+
# handling MultiIndexes requires different CSS
1484+
props = "position:sticky; background-color:white;"
1485+
1486+
if axis == 1:
1487+
# stick the first <tr> of <head> and, if index names, the second <tr>
1488+
# if self._hide_columns then no <thead><tr> here will exist: no conflict
1489+
styles: CSSStyles = [
14741490
{
1475-
"selector": f"{tag} th",
1476-
"props": f"position:sticky; {pos}:0px; background-color:white;",
1491+
"selector": "thead tr:first-child",
1492+
"props": props + "top:0px; z-index:2;",
14771493
}
1478-
],
1479-
overwrite=False,
1480-
)
1494+
]
1495+
if not self.index.names[0] is None:
1496+
styles[0]["props"] = (
1497+
props + f"top:0px; z-index:2; height:{pixel_size}px;"
1498+
)
1499+
styles.append(
1500+
{
1501+
"selector": "thead tr:nth-child(2)",
1502+
"props": props
1503+
+ f"top:{pixel_size}px; z-index:2; height:{pixel_size}px; ",
1504+
}
1505+
)
1506+
else:
1507+
# stick the first <th> of each <tr> in both <thead> and <tbody>
1508+
# if self._hide_index then no <th> will exist in <tbody>: no conflict
1509+
# but <th> will exist in <thead>: conflict with initial element
1510+
styles = [
1511+
{
1512+
"selector": "tr th:first-child",
1513+
"props": props + "left:0px; z-index:1;",
1514+
}
1515+
]
1516+
1517+
return self.set_table_styles(styles, overwrite=False)
1518+
14811519
else:
14821520
range_idx = list(range(obj.nlevels))
14831521

pandas/tests/io/formats/style/test_html.py

+23-5
Original file line numberDiff line numberDiff line change
@@ -272,17 +272,35 @@ def test_caption_as_sequence(styler):
272272

273273
@pytest.mark.parametrize("index", [False, True])
274274
@pytest.mark.parametrize("columns", [False, True])
275-
def test_sticky_basic(styler, index, columns):
275+
@pytest.mark.parametrize("index_name", [True, False])
276+
def test_sticky_basic(styler, index, columns, index_name):
277+
if index_name:
278+
styler.index.name = "some text"
276279
if index:
277280
styler.set_sticky(axis=0)
278281
if columns:
279282
styler.set_sticky(axis=1)
280283

281284
res = styler.set_uuid("").to_html()
282-
cs1 = "tbody th {\n position: sticky;\n left: 0px;\n background-color: white;\n}"
283-
assert (cs1 in res) is index
284-
cs2 = "thead th {\n position: sticky;\n top: 0px;\n background-color: white;\n}"
285-
assert (cs2 in res) is columns
285+
286+
css_for_index = (
287+
"tr th:first-child {\n position: sticky;\n background-color: white;\n "
288+
"left: 0px;\n z-index: 1;\n}"
289+
)
290+
assert (css_for_index in res) is index
291+
292+
css_for_cols_1 = (
293+
"thead tr:first-child {\n position: sticky;\n background-color: white;\n "
294+
"top: 0px;\n z-index: 2;\n"
295+
)
296+
css_for_cols_1 += " height: 25px;\n}" if index_name else "}"
297+
assert (css_for_cols_1 in res) is columns
298+
299+
css_for_cols_2 = (
300+
"thead tr:nth-child(2) {\n position: sticky;\n background-color: white;\n "
301+
"top: 25px;\n z-index: 2;\n height: 25px;\n}"
302+
)
303+
assert (css_for_cols_2 in res) is (index_name and columns)
286304

287305

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

0 commit comments

Comments
 (0)