diff --git a/doc/source/whatsnew/v1.3.1.rst b/doc/source/whatsnew/v1.3.1.rst index 33f286fccccef..70bfc349607da 100644 --- a/doc/source/whatsnew/v1.3.1.rst +++ b/doc/source/whatsnew/v1.3.1.rst @@ -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`) .. --------------------------------------------------------------------------- diff --git a/pandas/io/formats/style.py b/pandas/io/formats/style.py index cb56ea33acad8..91a301b665f7c 100644 --- a/pandas/io/formats/style.py +++ b/pandas/io/formats/style.py @@ -1450,7 +1450,8 @@ 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. @@ -1458,6 +1459,16 @@ def set_sticky( 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" @@ -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 of and, if index names, the second + # if self._hide_columns then no 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 of each in both and + # if self._hide_index then no will exist in : no conflict + # but will exist in : 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)) diff --git a/pandas/tests/io/formats/style/test_html.py b/pandas/tests/io/formats/style/test_html.py index 495dc82f0e7bd..4e71cb4c46626 100644 --- a/pandas/tests/io/formats/style/test_html.py +++ b/pandas/tests/io/formats/style/test_html.py @@ -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])