Skip to content

CLN: Styler Types for CSS variables on ctx object. #39660

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 22 commits into from
Feb 19, 2021
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion doc/source/whatsnew/v0.20.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -374,7 +374,7 @@ For example, after running the following, ``styled.xlsx`` renders as below:
df.iloc[0, 2] = np.nan
df
styled = (df.style
.applymap(lambda val: 'color: %s' % 'red' if val < 0 else 'black')
.applymap(lambda val: 'color:red;' if val < 0 else 'color:black;')
.highlight_max())
styled.to_excel('styled.xlsx', engine='openpyxl')

Expand Down
3 changes: 2 additions & 1 deletion pandas/io/formats/excel.py
Original file line number Diff line number Diff line change
Expand Up @@ -760,7 +760,8 @@ def _generate_body(self, coloffset: int) -> Iterable[ExcelCell]:
series = self.df.iloc[:, colidx]
for i, val in enumerate(series):
if styles is not None:
xlstyle = self.style_converter(";".join(styles[i, colidx]))
css = ";".join([a + ":" + str(v) for (a, v) in styles[i, colidx]])
xlstyle = self.style_converter(css)
yield ExcelCell(self.rowcounter + i, colidx + coloffset, val, xlstyle)

def get_formatted_cells(self) -> Iterable[ExcelCell]:
Expand Down
51 changes: 19 additions & 32 deletions pandas/io/formats/style.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,10 @@
from pandas.core.indexing import maybe_numeric_slice, non_reducing_slice

jinja2 = import_optional_dependency("jinja2", extra="DataFrame.style requires jinja2.")
CSSSequence = Sequence[Tuple[str, Union[str, int, float]]]
CSSProperties = Union[str, CSSSequence]

CSSPair = Tuple[str, Union[str, int, float]]
CSSList = List[CSSPair]
CSSProperties = Union[str, CSSList]
CSSStyles = List[Dict[str, CSSProperties]]

try:
Expand Down Expand Up @@ -184,7 +186,7 @@ def __init__(
# assign additional default vars
self.hidden_index: bool = False
self.hidden_columns: Sequence[int] = []
self.ctx: DefaultDict[Tuple[int, int], List[str]] = defaultdict(list)
self.ctx: DefaultDict[Tuple[int, int], CSSList] = defaultdict(list)
self.cell_context: Dict[str, Any] = {}
self._todo: List[Tuple[Callable, Tuple, Dict]] = []
self.tooltips: Optional[_Tooltips] = None
Expand Down Expand Up @@ -404,7 +406,8 @@ def _translate(self):
clabels = [[x] for x in clabels]
clabels = list(zip(*clabels))

cellstyle_map = defaultdict(list)
cellstyle_map: DefaultDict[Tuple[CSSPair, ...], List[str]] = defaultdict(list)

head = []

for r in range(n_clvls):
Expand Down Expand Up @@ -514,25 +517,21 @@ def _translate(self):
}

# only add an id if the cell has a style
props = []
props: CSSList = []
if self.cell_ids or (r, c) in ctx:
row_dict["id"] = "_".join(cs[1:])
for x in ctx[r, c]:
# have to handle empty styles like ['']
if x.count(":"):
props.append(tuple(x.split(":")))
else:
props.append(("", ""))
props.extend(ctx[r, c])

# add custom classes from cell context
cs.extend(cell_context.get("data", {}).get(r, {}).get(c, []))
row_dict["class"] = " ".join(cs)

row_es.append(row_dict)
cellstyle_map[tuple(props)].append(f"row{r}_col{c}")
if props: # (), [] won't be in cellstyle_map, cellstyle respectively
cellstyle_map[tuple(props)].append(f"row{r}_col{c}")
body.append(row_es)

cellstyle = [
cellstyle: List[Dict[str, Union[CSSList, List[str]]]] = [
{"props": list(props), "selectors": selectors}
for props, selectors in cellstyle_map.items()
]
Expand Down Expand Up @@ -738,19 +737,14 @@ def render(self, **kwargs) -> str:
self._compute()
# TODO: namespace all the pandas keys
d = self._translate()
# filter out empty styles, every cell will have a class
# but the list of props may just be [['', '']].
# so we have the nested anys below
trimmed = [x for x in d["cellstyle"] if any(any(y) for y in x["props"])]
d["cellstyle"] = trimmed
d.update(kwargs)
return self.template.render(**d)

def _update_ctx(self, attrs: DataFrame) -> None:
"""
Update the state of the Styler.
Update the state of the Styler for data cells.

Collects a mapping of {index_label: ['<property>: <value>']}.
Collects a mapping of {index_label: [('<property>', '<value>'), ..]}.

Parameters
----------
Expand All @@ -759,20 +753,13 @@ def _update_ctx(self, attrs: DataFrame) -> None:
Whitespace shouldn't matter and the final trailing ';' shouldn't
matter.
"""
coli = {k: i for i, k in enumerate(self.columns)}
rowi = {k: i for i, k in enumerate(self.index)}
for jj in range(len(attrs.columns)):
cn = attrs.columns[jj]
j = coli[cn]
for cn in attrs.columns:
for rn, c in attrs[[cn]].itertuples():
if not c:
continue
c = c.rstrip(";")
if not c:
continue
i = rowi[rn]
for pair in c.split(";"):
self.ctx[(i, j)].append(pair)
css_list = _maybe_convert_css_to_tuples(c)
i, j = self.index.get_loc(rn), self.columns.get_loc(cn)
self.ctx[(i, j)].extend(css_list)

def _copy(self, deepcopy: bool = False) -> Styler:
styler = Styler(
Expand Down Expand Up @@ -2068,7 +2055,7 @@ def _maybe_wrap_formatter(
raise TypeError(msg)


def _maybe_convert_css_to_tuples(style: CSSProperties) -> CSSSequence:
def _maybe_convert_css_to_tuples(style: CSSProperties) -> CSSList:
"""
Convert css-string to sequence of tuples format if needed.
'color:red; border:1px solid black;' -> [('color', 'red'),
Expand Down
Loading