Skip to content

Commit

Permalink
ENH: Styler column and row styles (#35607)
Browse files Browse the repository at this point in the history
  • Loading branch information
attack68 authored Nov 24, 2020
1 parent 76216ba commit de919ff
Show file tree
Hide file tree
Showing 4 changed files with 123 additions and 13 deletions.
34 changes: 30 additions & 4 deletions doc/source/user_guide/style.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -793,7 +793,8 @@
"source": [
"The next option you have are \"table styles\".\n",
"These are styles that apply to the table as a whole, but don't look at the data.\n",
"Certain stylings, including pseudo-selectors like `:hover` can only be used this way."
"Certain stylings, including pseudo-selectors like `:hover` can only be used this way.\n",
"These can also be used to set specific row or column based class selectors, as will be shown."
]
},
{
Expand Down Expand Up @@ -831,9 +832,32 @@
"The value for `props` should be a list of tuples of `('attribute', 'value')`.\n",
"\n",
"`table_styles` are extremely flexible, but not as fun to type out by hand.\n",
"We hope to collect some useful ones either in pandas, or preferable in a new package that [builds on top](#Extensibility) the tools here."
"We hope to collect some useful ones either in pandas, or preferable in a new package that [builds on top](#Extensibility) the tools here.\n",
"\n",
"`table_styles` can be used to add column and row based class descriptors. For large tables this can increase performance by avoiding repetitive individual css for each cell, and it can also simplify style construction in some cases.\n",
"If `table_styles` is given as a dictionary each key should be a specified column or index value and this will map to specific class CSS selectors of the given column or row.\n",
"\n",
"Note that `Styler.set_table_styles` will overwrite existing styles but can be chained by setting the `overwrite` argument to `False`."
]
},
{
"cell_type": "code",
"execution_count": null,
"outputs": [],
"source": [
"html = html.set_table_styles({\n",
" 'B': [dict(selector='', props=[('color', 'green')])],\n",
" 'C': [dict(selector='td', props=[('color', 'red')])], \n",
" }, overwrite=False)\n",
"html"
],
"metadata": {
"collapsed": false,
"pycharm": {
"name": "#%%\n"
}
}
},
{
"cell_type": "markdown",
"metadata": {},
Expand Down Expand Up @@ -922,10 +946,12 @@
"- DataFrame only `(use Series.to_frame().style)`\n",
"- The index and columns must be unique\n",
"- No large repr, and performance isn't great; this is intended for summary DataFrames\n",
"- You can only style the *values*, not the index or columns\n",
"- You can only style the *values*, not the index or columns (except with `table_styles` above)\n",
"- You can only apply styles, you can't insert new HTML entities\n",
"\n",
"Some of these will be addressed in the future.\n"
"Some of these will be addressed in the future.\n",
"Performance can suffer when adding styles to each cell in a large DataFrame.\n",
"It is recommended to apply table or column based styles where possible to limit overall HTML length, as well as setting a shorter UUID to avoid unnecessary repeated data transmission. \n"
]
},
{
Expand Down
1 change: 1 addition & 0 deletions doc/source/whatsnew/v1.2.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@ Other enhancements
- :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`)
- :meth:`DataFrame.hist` now supports time series (datetime) data (:issue:`32590`)
- :meth:`Styler.set_table_styles` now allows the direct styling of rows and columns and can be chained (:issue:`35607`)
- ``Styler`` now allows direct CSS class name addition to individual data cells (:issue:`36159`)
- :meth:`Rolling.mean()` and :meth:`Rolling.sum()` use Kahan summation to calculate the mean to avoid numerical problems (:issue:`10319`, :issue:`11645`, :issue:`13254`, :issue:`32761`, :issue:`36031`)
- :meth:`DatetimeIndex.searchsorted`, :meth:`TimedeltaIndex.searchsorted`, :meth:`PeriodIndex.searchsorted`, and :meth:`Series.searchsorted` with datetimelike dtypes will now try to cast string arguments (listlike and scalar) to the matching datetimelike type (:issue:`36346`)
Expand Down
79 changes: 70 additions & 9 deletions pandas/io/formats/style.py
Original file line number Diff line number Diff line change
Expand Up @@ -990,34 +990,95 @@ def set_caption(self, caption: str) -> "Styler":
self.caption = caption
return self

def set_table_styles(self, table_styles) -> "Styler":
def set_table_styles(self, table_styles, axis=0, overwrite=True) -> "Styler":
"""
Set the table styles on a Styler.
These are placed in a ``<style>`` tag before the generated HTML table.
This function can be used to style the entire table, columns, rows or
specific HTML selectors.
Parameters
----------
table_styles : list
Each individual table_style should be a dictionary with
``selector`` and ``props`` keys. ``selector`` should be a CSS
selector that the style will be applied to (automatically
prefixed by the table's UUID) and ``props`` should be a list of
tuples with ``(attribute, value)``.
table_styles : list or dict
If supplying a list, each individual table_style should be a
dictionary with ``selector`` and ``props`` keys. ``selector``
should be a CSS selector that the style will be applied to
(automatically prefixed by the table's UUID) and ``props``
should be a list of tuples with ``(attribute, value)``.
If supplying a dict, the dict keys should correspond to
column names or index values, depending upon the specified
`axis` argument. These will be mapped to row or col CSS
selectors. MultiIndex values as dict keys should be
in their respective tuple form. The dict values should be
a list as specified in the form with CSS selectors and
props that will be applied to the specified row or column.
.. versionchanged:: 1.2.0
axis : {0 or 'index', 1 or 'columns', None}, default 0
Apply to each column (``axis=0`` or ``'index'``), to each row
(``axis=1`` or ``'columns'``). Only used if `table_styles` is
dict.
.. versionadded:: 1.2.0
overwrite : boolean, default True
Styles are replaced if `True`, or extended if `False`. CSS
rules are preserved so most recent styles set will dominate
if selectors intersect.
.. versionadded:: 1.2.0
Returns
-------
self : Styler
Examples
--------
>>> df = pd.DataFrame(np.random.randn(10, 4))
>>> df = pd.DataFrame(np.random.randn(10, 4),
... columns=['A', 'B', 'C', 'D'])
>>> df.style.set_table_styles(
... [{'selector': 'tr:hover',
... 'props': [('background-color', 'yellow')]}]
... )
Adding column styling by name
>>> df.style.set_table_styles({
... 'A': [{'selector': '',
... 'props': [('color', 'red')]}],
... 'B': [{'selector': 'td',
... 'props': [('color', 'blue')]}]
... }, overwrite=False)
Adding row styling
>>> df.style.set_table_styles({
... 0: [{'selector': 'td:hover',
... 'props': [('font-size', '25px')]}]
... }, axis=1, overwrite=False)
"""
self.table_styles = table_styles
if is_dict_like(table_styles):
if axis in [0, "index"]:
obj, idf = self.data.columns, ".col"
else:
obj, idf = self.data.index, ".row"

table_styles = [
{
"selector": s["selector"] + idf + str(obj.get_loc(key)),
"props": s["props"],
}
for key, styles in table_styles.items()
for s in styles
]

if not overwrite and self.table_styles is not None:
self.table_styles.extend(table_styles)
else:
self.table_styles = table_styles
return self

def set_na_rep(self, na_rep: str) -> "Styler":
Expand Down
22 changes: 22 additions & 0 deletions pandas/tests/io/formats/test_style.py
Original file line number Diff line number Diff line change
Expand Up @@ -1712,6 +1712,28 @@ def test_set_data_classes(self, classes):
assert '<td class="data row1 col0" >2</td>' in s
assert '<td class="data row1 col1" >3</td>' in s

def test_chaining_table_styles(self):
# GH 35607
df = DataFrame(data=[[0, 1], [1, 2]], columns=["A", "B"])
styler = df.style.set_table_styles(
[{"selector": "", "props": [("background-color", "yellow")]}]
).set_table_styles(
[{"selector": ".col0", "props": [("background-color", "blue")]}],
overwrite=False,
)
assert len(styler.table_styles) == 2

def test_column_and_row_styling(self):
# GH 35607
df = DataFrame(data=[[0, 1], [1, 2]], columns=["A", "B"])
s = Styler(df, uuid_len=0)
s = s.set_table_styles({"A": [{"selector": "", "props": [("color", "blue")]}]})
assert "#T__ .col0 {\n color: blue;\n }" in s.render()
s = s.set_table_styles(
{0: [{"selector": "", "props": [("color", "blue")]}]}, axis=1
)
assert "#T__ .row0 {\n color: blue;\n }" in s.render()

def test_colspan_w3(self):
# GH 36223
df = DataFrame(data=[[1, 2]], columns=[["l0", "l0"], ["l1a", "l1b"]])
Expand Down

0 comments on commit de919ff

Please sign in to comment.