Skip to content

ENH: add set_td_classes method for CSS class addition to data cells #36159

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Sep 13, 2020
2 changes: 1 addition & 1 deletion doc/source/whatsnew/v1.2.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ Other enhancements
- Added :meth:`~DataFrame.set_flags` for setting table-wide flags on a ``Series`` or ``DataFrame`` (:issue:`28394`)
- :class:`Index` with object dtype supports division and multiplication (:issue:`34160`)
- :meth:`DataFrame.explode` and :meth:`Series.explode` now support exploding of sets (:issue:`35614`)
-
- `Styler` now allows direct CSS class name addition to individual data cells (:issue:`36159`)

.. _whatsnew_120.api_breaking.python:

Expand Down
48 changes: 47 additions & 1 deletion pandas/io/formats/style.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,8 @@ def __init__(
self.cell_ids = cell_ids
self.na_rep = na_rep

self.cell_context: Dict[str, Any] = {}

# display_funcs maps (row, col) -> formatting function

def default_display_func(x):
Expand Down Expand Up @@ -262,7 +264,7 @@ def format_attr(pair):
idx_lengths = _get_level_lengths(self.index)
col_lengths = _get_level_lengths(self.columns, hidden_columns)

cell_context = dict()
cell_context = self.cell_context

n_rlvls = self.data.index.nlevels
n_clvls = self.data.columns.nlevels
Expand Down Expand Up @@ -499,6 +501,49 @@ def format(self, formatter, subset=None, na_rep: Optional[str] = None) -> "Style
self._display_funcs[(i, j)] = formatter
return self

def set_data_classes(self, classes: DataFrame) -> "Styler":
"""
Add string based CSS class names to data cells that will appear in the
`Styler` HTML result.

Parameters
----------
classes : DataFrame
DataFrame containing strings that will be translated to CSS classes. Empty
strings, None, or NaN values will be ignored. DataFrame must have
identical rows and columns to the underlying `Styler` data.

Returns
-------
self : Styler

Examples
--------
>>> df = pd.DataFrame(data=[[1, 2, 3], [4, 5, 6]], columns=['A', 'B', 'C'])
>>> classes = pd.DataFrame([
... ['min-num red', '', 'blue'],
... ['red', None, 'blue max-num']
... ], index=df.index, columns=df.columns)
>>> df.style.set_data_classes(classes)
"""
if not (
self.columns.equals(classes.columns) and self.index.equals(classes.index)
):
raise ValueError(
"CSS classes DataFrame must have identical column and index labelling "
"to underlying."
)

mask = (classes.isna()) | (classes.eq(""))
self.cell_context["data"] = {
r: {c: [classes.iloc[r, c]]}
for r, rn in enumerate(classes.index)
for c, cn in enumerate(classes.columns)
if not mask.iloc[r, c]
}

return self

def render(self, **kwargs) -> str:
"""
Render the built up styles to HTML.
Expand Down Expand Up @@ -609,6 +654,7 @@ def clear(self) -> None:
Returns None.
"""
self.ctx.clear()
self.cell_context = {}
self._todo = []

def _compute(self):
Expand Down
14 changes: 14 additions & 0 deletions pandas/tests/io/formats/test_style.py
Original file line number Diff line number Diff line change
Expand Up @@ -1691,6 +1691,20 @@ def test_no_cell_ids(self):
s = styler.render() # render twice to ensure ctx is not updated
assert s.find('<td class="data row0 col0" >') != -1

def test_set_data_classes(self):
# GH 36159
df = pd.DataFrame(data=[[0, 1], [2, 3]])
classes = pd.DataFrame(
data=[["test-class", ""], [np.nan, None]],
columns=df.columns,
index=df.index,
)
s = Styler(df, uuid="_", cell_ids=False).set_data_classes(classes).render()
assert '<td class="data row0 col0 test-class" >0</td>' in s
assert '<td class="data row0 col1" >1</td>' in s
assert '<td class="data row1 col0" >2</td>' in s
assert '<td class="data row1 col1" >3</td>' in s


@td.skip_if_no_mpl
class TestStylerMatplotlibDep:
Expand Down