diff --git a/doc/source/whatsnew/v1.3.0.rst b/doc/source/whatsnew/v1.3.0.rst index 8deeb3cfae1d3..78caa3d0c15de 100644 --- a/doc/source/whatsnew/v1.3.0.rst +++ b/doc/source/whatsnew/v1.3.0.rst @@ -492,6 +492,7 @@ Other - :class:`Styler` rendered HTML output minor alterations to support w3 good code standard (:issue:`39626`) - Bug in :class:`Styler` where rendered HTML was missing a column class identifier for certain header cells (:issue:`39716`) - Bug in :meth:`Styler.background_gradient` where text-color was not determined correctly (:issue:`39888`) +- Bug in :class:`Styler` where multiple elements in CSS-selectors were not correctly added to ``table_styles`` (:issue:`39942`) - Bug in :meth:`DataFrame.equals`, :meth:`Series.equals`, :meth:`Index.equals` with object-dtype containing ``np.datetime64("NaT")`` or ``np.timedelta64("NaT")`` (:issue:`39650`) - Bug in :func:`pandas.util.show_versions` where console JSON output was not proper JSON (:issue:`39701`) diff --git a/pandas/io/formats/style.py b/pandas/io/formats/style.py index 854f41d6b4dc3..e50f5986098d3 100644 --- a/pandas/io/formats/style.py +++ b/pandas/io/formats/style.py @@ -19,6 +19,7 @@ Sequence, Tuple, Union, + cast, ) from uuid import uuid4 @@ -55,7 +56,10 @@ CSSPair = Tuple[str, Union[str, int, float]] CSSList = List[CSSPair] CSSProperties = Union[str, CSSList] -CSSStyles = List[Dict[str, CSSProperties]] +CSSStyles = List[Dict[str, CSSProperties]] # = List[CSSDict] +# class CSSDict(TypedDict): # available when TypedDict is valid in pandas +# selector: str +# props: CSSProperties try: from matplotlib import colors @@ -566,7 +570,7 @@ def _translate(self): "body": body, "uuid": uuid, "precision": precision, - "table_styles": table_styles, + "table_styles": _format_table_styles(table_styles), "caption": caption, "table_attributes": table_attr, } @@ -1904,25 +1908,14 @@ def _pseudo_css(self, uuid: str, name: str, row: int, col: int, text: str): ------- pseudo_css : List """ + selector_id = "#T_" + uuid + "row" + str(row) + "_col" + str(col) return [ { - "selector": "#T_" - + uuid - + "row" - + str(row) - + "_col" - + str(col) - + f":hover .{name}", + "selector": selector_id + f":hover .{name}", "props": [("visibility", "visible")], }, { - "selector": "#T_" - + uuid - + "row" - + str(row) - + "_col" - + str(col) - + f" .{name}::after", + "selector": selector_id + f" .{name}::after", "props": [("content", f'"{text}"')], }, ] @@ -2077,6 +2070,26 @@ def _maybe_convert_css_to_tuples(style: CSSProperties) -> CSSList: return style +def _format_table_styles(styles: CSSStyles) -> CSSStyles: + """ + looks for multiple CSS selectors and separates them: + [{'selector': 'td, th', 'props': 'a:v;'}] + ---> [{'selector': 'td', 'props': 'a:v;'}, + {'selector': 'th', 'props': 'a:v;'}] + """ + return [ + item + for sublist in [ + [ # this is a CSSDict when TypedDict is available to avoid cast. + {"selector": x, "props": style["props"]} + for x in cast(str, style["selector"]).split(",") + ] + for style in styles + ] + for item in sublist + ] + + def _non_reducing_slice(slice_): """ Ensure that a slice doesn't reduce to a Series or Scalar. diff --git a/pandas/tests/io/formats/style/test_style.py b/pandas/tests/io/formats/style/test_style.py index 01ed234f6e248..f0d1090899043 100644 --- a/pandas/tests/io/formats/style/test_style.py +++ b/pandas/tests/io/formats/style/test_style.py @@ -626,6 +626,19 @@ def test_table_styles(self): result = " ".join(styler.render().split()) assert "th { foo: bar; }" in result + def test_table_styles_multiple(self): + ctx = self.df.style.set_table_styles( + [ + {"selector": "th,td", "props": "color:red;"}, + {"selector": "tr", "props": "color:green;"}, + ] + )._translate()["table_styles"] + assert ctx == [ + {"selector": "th", "props": [("color", "red")]}, + {"selector": "td", "props": [("color", "red")]}, + {"selector": "tr", "props": [("color", "green")]}, + ] + def test_maybe_convert_css_to_tuples(self): expected = [("a", "b"), ("c", "d e")] assert _maybe_convert_css_to_tuples("a:b;c:d e;") == expected