Skip to content

Commit a6ddae7

Browse files
Backport PR #42622: BUG: Styler.set_sticky not handling display of index names. 1/2 (#42695)
Co-authored-by: attack68 <[email protected]>
1 parent 904277d commit a6ddae7

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
@@ -1452,14 +1452,25 @@ def set_sticky(
14521452
Whether to make the index or column headers sticky.
14531453
pixel_size : int, optional
14541454
Required to configure the width of index cells or the height of column
1455-
header cells when sticking a MultiIndex. Defaults to 75 and 25 respectively.
1455+
header cells when sticking a MultiIndex (or with a named Index).
1456+
Defaults to 75 and 25 respectively.
14561457
levels : list of int
14571458
If ``axis`` is a MultiIndex the specific levels to stick. If ``None`` will
14581459
stick all levels.
14591460
14601461
Returns
14611462
-------
14621463
self : Styler
1464+
1465+
Notes
1466+
-----
1467+
This method uses the CSS 'position: sticky;' property to display. It is
1468+
designed to work with visible axes, therefore both:
1469+
1470+
- `styler.set_sticky(axis="index").hide_index()`
1471+
- `styler.set_sticky(axis="columns").hide_columns()`
1472+
1473+
may produce strange behaviour due to CSS controls with missing elements.
14631474
"""
14641475
if axis in [0, "index"]:
14651476
axis, obj, tag, pos = 0, self.data.index, "tbody", "left"
@@ -1471,15 +1482,42 @@ def set_sticky(
14711482
raise ValueError("`axis` must be one of {0, 1, 'index', 'columns'}")
14721483

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

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)