From adb8b756ec1bbbb11c6be96b76ed84dba945ad8c Mon Sep 17 00:00:00 2001 From: "JHM Darbyshire (iMac)" Date: Tue, 20 Jul 2021 12:16:41 +0200 Subject: [PATCH 1/5] amend tests, to include index_name checking --- pandas/io/formats/style.py | 52 ++++++++++++++++++---- pandas/tests/io/formats/style/test_html.py | 28 +++++++++--- 2 files changed, 67 insertions(+), 13 deletions(-) diff --git a/pandas/io/formats/style.py b/pandas/io/formats/style.py index cb56ea33acad8..03757679a42f7 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,40 @@ 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 = [ { - "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"] += f"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]) From 7f458d691172acc1d0793048205f0d399d59961c Mon Sep 17 00:00:00 2001 From: "JHM Darbyshire (iMac)" Date: Tue, 20 Jul 2021 12:30:43 +0200 Subject: [PATCH 2/5] whats new 1.3.1 --- doc/source/whatsnew/v1.3.1.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/whatsnew/v1.3.1.rst b/doc/source/whatsnew/v1.3.1.rst index 2ce146660f98c..1801295fb5fc0 100644 --- a/doc/source/whatsnew/v1.3.1.rst +++ b/doc/source/whatsnew/v1.3.1.rst @@ -33,7 +33,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`) .. --------------------------------------------------------------------------- From 04fc2c4e3820133288d909ba355ffa3fd9f689cb Mon Sep 17 00:00:00 2001 From: "JHM Darbyshire (iMac)" Date: Wed, 21 Jul 2021 08:25:27 +0200 Subject: [PATCH 3/5] mypy fixes --- pandas/io/formats/style.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/pandas/io/formats/style.py b/pandas/io/formats/style.py index 03757679a42f7..c9d5c68277a10 100644 --- a/pandas/io/formats/style.py +++ b/pandas/io/formats/style.py @@ -1486,14 +1486,16 @@ def set_sticky( 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 = [ + styles: CSSStyles = [ { "selector": "thead tr:first-child", "props": props + "top:0px; z-index:2;", } ] if not self.index.names[0] is None: - styles[0]["props"] += f"height:{pixel_size}px;" + styles[0]["props"] = ( + props + f"top:0px; z-index:2; height:{pixel_size}px;" + ) styles.append( { "selector": "thead tr:nth-child(2)", @@ -1756,9 +1758,7 @@ def hide_index(self, subset: Subset | None = None) -> Styler: subset = non_reducing_slice(subset_) hide = self.data.loc[subset] hrows = self.index.get_indexer_for(hide.index) - # error: Incompatible types in assignment (expression has type - # "ndarray", variable has type "Sequence[int]") - self.hidden_rows = hrows # type: ignore[assignment] + self.hidden_rows = hrows return self def hide_columns(self, subset: Subset | None = None) -> Styler: @@ -1841,9 +1841,7 @@ def hide_columns(self, subset: Subset | None = None) -> Styler: subset = non_reducing_slice(subset_) hide = self.data.loc[subset] hcols = self.columns.get_indexer_for(hide.columns) - # error: Incompatible types in assignment (expression has type - # "ndarray", variable has type "Sequence[int]") - self.hidden_columns = hcols # type: ignore[assignment] + self.hidden_columns = hcols return self # ----------------------------------------------------------------------- From c6a3a3ceb8a007162ae7dc5c4df384730e2a4979 Mon Sep 17 00:00:00 2001 From: attack68 <24256554+attack68@users.noreply.github.com> Date: Fri, 23 Jul 2021 19:20:42 +0200 Subject: [PATCH 4/5] fix mypy --- pandas/io/formats/style.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pandas/io/formats/style.py b/pandas/io/formats/style.py index c9d5c68277a10..c25af2b0e330b 100644 --- a/pandas/io/formats/style.py +++ b/pandas/io/formats/style.py @@ -1758,7 +1758,9 @@ def hide_index(self, subset: Subset | None = None) -> Styler: subset = non_reducing_slice(subset_) hide = self.data.loc[subset] hrows = self.index.get_indexer_for(hide.index) - self.hidden_rows = hrows + # error: Incompatible types in assignment (expression has type + # "ndarray", variable has type "Sequence[int]") + self.hidden_rows = hrows # type: ignore[assignment] return self def hide_columns(self, subset: Subset | None = None) -> Styler: From 40b3a72b25263100f6a5a7cd4a7b73055960b94e Mon Sep 17 00:00:00 2001 From: attack68 <24256554+attack68@users.noreply.github.com> Date: Fri, 23 Jul 2021 19:22:31 +0200 Subject: [PATCH 5/5] fix mypy --- pandas/io/formats/style.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pandas/io/formats/style.py b/pandas/io/formats/style.py index c25af2b0e330b..91a301b665f7c 100644 --- a/pandas/io/formats/style.py +++ b/pandas/io/formats/style.py @@ -1843,7 +1843,9 @@ def hide_columns(self, subset: Subset | None = None) -> Styler: subset = non_reducing_slice(subset_) hide = self.data.loc[subset] hcols = self.columns.get_indexer_for(hide.columns) - self.hidden_columns = hcols + # error: Incompatible types in assignment (expression has type + # "ndarray", variable has type "Sequence[int]") + self.hidden_columns = hcols # type: ignore[assignment] return self # -----------------------------------------------------------------------