Skip to content

Commit 9090117

Browse files
attack68JulianWgs
authored andcommitted
CLN: refactor Styler._translate into composite translate functions (pandas-dev#40898)
1 parent 057e65e commit 9090117

File tree

2 files changed

+263
-157
lines changed

2 files changed

+263
-157
lines changed

pandas/io/formats/style_render.py

Lines changed: 170 additions & 138 deletions
Original file line numberDiff line numberDiff line change
@@ -173,181 +173,192 @@ def _translate(self):
173173
BLANK_CLASS = "blank"
174174
BLANK_VALUE = " "
175175

176-
# mapping variables
177-
ctx = self.ctx # td css styles from apply() and applymap()
178-
cell_context = self.cell_context # td css classes from set_td_classes()
179-
cellstyle_map: DefaultDict[tuple[CSSPair, ...], list[str]] = defaultdict(list)
180-
181-
# copied attributes
182-
hidden_index = self.hidden_index
183-
hidden_columns = self.hidden_columns
184-
185176
# construct render dict
186177
d = {
187178
"uuid": self.uuid,
188179
"table_styles": _format_table_styles(self.table_styles or []),
189180
"caption": self.caption,
190181
}
191182

183+
head = self._translate_header(
184+
BLANK_CLASS, BLANK_VALUE, INDEX_NAME_CLASS, COL_HEADING_CLASS
185+
)
186+
d.update({"head": head})
187+
188+
self.cellstyle_map: DefaultDict[tuple[CSSPair, ...], list[str]] = defaultdict(
189+
list
190+
)
191+
body = self._translate_body(DATA_CLASS, ROW_HEADING_CLASS)
192+
d.update({"body": body})
193+
194+
cellstyle: list[dict[str, CSSList | list[str]]] = [
195+
{"props": list(props), "selectors": selectors}
196+
for props, selectors in self.cellstyle_map.items()
197+
]
198+
d.update({"cellstyle": cellstyle})
199+
200+
table_attr = self.table_attributes
201+
use_mathjax = get_option("display.html.use_mathjax")
202+
if not use_mathjax:
203+
table_attr = table_attr or ""
204+
if 'class="' in table_attr:
205+
table_attr = table_attr.replace('class="', 'class="tex2jax_ignore ')
206+
else:
207+
table_attr += ' class="tex2jax_ignore"'
208+
d.update({"table_attributes": table_attr})
209+
210+
if self.tooltips:
211+
d = self.tooltips._translate(self.data, self.uuid, d)
212+
213+
return d
214+
215+
def _translate_header(
216+
self, blank_class, blank_value, index_name_class, col_heading_class
217+
):
218+
"""
219+
Build each <tr> within table <head>, using the structure:
220+
+----------------------------+---------------+---------------------------+
221+
| index_blanks ... | column_name_0 | column_headers (level_0) |
222+
1) | .. | .. | .. |
223+
| index_blanks ... | column_name_n | column_headers (level_n) |
224+
+----------------------------+---------------+---------------------------+
225+
2) | index_names (level_0 to level_n) ... | column_blanks ... |
226+
+----------------------------+---------------+---------------------------+
227+
"""
192228
# for sparsifying a MultiIndex
193-
idx_lengths = _get_level_lengths(self.index)
194-
col_lengths = _get_level_lengths(self.columns, hidden_columns)
229+
col_lengths = _get_level_lengths(self.columns, self.hidden_columns)
195230

196-
n_rlvls = self.data.index.nlevels
197-
n_clvls = self.data.columns.nlevels
198-
rlabels = self.data.index.tolist()
199231
clabels = self.data.columns.tolist()
200-
201-
if n_rlvls == 1:
202-
rlabels = [[x] for x in rlabels]
203-
if n_clvls == 1:
232+
if self.data.columns.nlevels == 1:
204233
clabels = [[x] for x in clabels]
205234
clabels = list(zip(*clabels))
206235

207236
head = []
208-
for r in range(n_clvls):
209-
# Blank for Index columns...
210-
row_es = [
211-
{
212-
"type": "th",
213-
"value": BLANK_VALUE,
214-
"display_value": BLANK_VALUE,
215-
"is_visible": not hidden_index,
216-
"class": " ".join([BLANK_CLASS]),
217-
}
218-
] * (n_rlvls - 1)
219-
220-
# ... except maybe the last for columns.names
237+
# 1) column headers
238+
for r in range(self.data.columns.nlevels):
239+
index_blanks = [
240+
_element("th", blank_class, blank_value, not self.hidden_index)
241+
] * (self.data.index.nlevels - 1)
242+
221243
name = self.data.columns.names[r]
222-
cs = [
223-
BLANK_CLASS if name is None else INDEX_NAME_CLASS,
224-
f"level{r}",
244+
column_name = [
245+
_element(
246+
"th",
247+
f"{blank_class if name is None else index_name_class} level{r}",
248+
name if name is not None else blank_value,
249+
not self.hidden_index,
250+
)
225251
]
226-
name = BLANK_VALUE if name is None else name
227-
row_es.append(
228-
{
229-
"type": "th",
230-
"value": name,
231-
"display_value": name,
232-
"class": " ".join(cs),
233-
"is_visible": not hidden_index,
234-
}
235-
)
236252

237253
if clabels:
238-
for c, value in enumerate(clabels[r]):
239-
es = {
240-
"type": "th",
241-
"value": value,
242-
"display_value": value,
243-
"class": f"{COL_HEADING_CLASS} level{r} col{c}",
244-
"is_visible": _is_visible(c, r, col_lengths),
245-
}
246-
colspan = col_lengths.get((r, c), 0)
247-
if colspan > 1:
248-
es["attributes"] = f'colspan="{colspan}"'
249-
row_es.append(es)
250-
head.append(row_es)
254+
column_headers = [
255+
_element(
256+
"th",
257+
f"{col_heading_class} level{r} col{c}",
258+
value,
259+
_is_visible(c, r, col_lengths),
260+
attributes=(
261+
f'colspan="{col_lengths.get((r, c), 0)}"'
262+
if col_lengths.get((r, c), 0) > 1
263+
else ""
264+
),
265+
)
266+
for c, value in enumerate(clabels[r])
267+
]
268+
head.append(index_blanks + column_name + column_headers)
251269

270+
# 2) index names
252271
if (
253272
self.data.index.names
254273
and com.any_not_none(*self.data.index.names)
255-
and not hidden_index
274+
and not self.hidden_index
256275
):
257-
index_header_row = []
276+
index_names = [
277+
_element(
278+
"th",
279+
f"{index_name_class} level{c}",
280+
blank_value if name is None else name,
281+
True,
282+
)
283+
for c, name in enumerate(self.data.index.names)
284+
]
258285

259-
for c, name in enumerate(self.data.index.names):
260-
cs = [INDEX_NAME_CLASS, f"level{c}"]
261-
name = "" if name is None else name
262-
index_header_row.append(
263-
{"type": "th", "value": name, "class": " ".join(cs)}
286+
column_blanks = [
287+
_element(
288+
"th",
289+
f"{blank_class} col{c}",
290+
blank_value,
291+
c not in self.hidden_columns,
264292
)
293+
for c in range(len(clabels[0]))
294+
]
295+
head.append(index_names + column_blanks)
265296

266-
index_header_row.extend(
267-
[
268-
{
269-
"type": "th",
270-
"value": BLANK_VALUE,
271-
"class": " ".join([BLANK_CLASS, f"col{c}"]),
272-
}
273-
for c in range(len(clabels[0]))
274-
if c not in hidden_columns
275-
]
276-
)
297+
return head
277298

278-
head.append(index_header_row)
279-
d.update({"head": head})
299+
def _translate_body(self, data_class, row_heading_class):
300+
"""
301+
Build each <tr> in table <body> in the following format:
302+
+--------------------------------------------+---------------------------+
303+
| index_header_0 ... index_header_n | data_by_column |
304+
+--------------------------------------------+---------------------------+
305+
306+
Also add elements to the cellstyle_map for more efficient grouped elements in
307+
<style></style> block
308+
"""
309+
# for sparsifying a MultiIndex
310+
idx_lengths = _get_level_lengths(self.index)
311+
312+
rlabels = self.data.index.tolist()
313+
if self.data.index.nlevels == 1:
314+
rlabels = [[x] for x in rlabels]
280315

281316
body = []
282317
for r, row_tup in enumerate(self.data.itertuples()):
283-
row_es = []
284-
for c, value in enumerate(rlabels[r]):
285-
rid = [
286-
ROW_HEADING_CLASS,
287-
f"level{c}",
288-
f"row{r}",
289-
]
290-
es = {
291-
"type": "th",
292-
"is_visible": (_is_visible(r, c, idx_lengths) and not hidden_index),
293-
"value": value,
294-
"display_value": value,
295-
"id": "_".join(rid[1:]),
296-
"class": " ".join(rid),
297-
}
298-
rowspan = idx_lengths.get((c, r), 0)
299-
if rowspan > 1:
300-
es["attributes"] = f'rowspan="{rowspan}"'
301-
row_es.append(es)
318+
index_headers = [
319+
_element(
320+
"th",
321+
f"{row_heading_class} level{c} row{r}",
322+
value,
323+
(_is_visible(r, c, idx_lengths) and not self.hidden_index),
324+
id=f"level{c}_row{r}",
325+
attributes=(
326+
f'rowspan="{idx_lengths.get((c, r), 0)}"'
327+
if idx_lengths.get((c, r), 0) > 1
328+
else ""
329+
),
330+
)
331+
for c, value in enumerate(rlabels[r])
332+
]
302333

334+
data = []
303335
for c, value in enumerate(row_tup[1:]):
304-
formatter = self._display_funcs[(r, c)]
305-
row_dict = {
306-
"type": "td",
307-
"value": value,
308-
"display_value": formatter(value),
309-
"is_visible": (c not in hidden_columns),
310-
"attributes": "",
311-
}
312-
313-
# only add an id if the cell has a style
314-
props: CSSList = []
315-
if self.cell_ids or (r, c) in ctx:
316-
row_dict["id"] = f"row{r}_col{c}"
317-
props.extend(ctx[r, c])
318-
319336
# add custom classes from cell context
320337
cls = ""
321-
if (r, c) in cell_context:
322-
cls = " " + cell_context[r, c]
323-
row_dict["class"] = f"{DATA_CLASS} row{r} col{c}{cls}"
324-
325-
row_es.append(row_dict)
326-
if props: # (), [] won't be in cellstyle_map, cellstyle respectively
327-
cellstyle_map[tuple(props)].append(f"row{r}_col{c}")
328-
body.append(row_es)
329-
d.update({"body": body})
330-
331-
cellstyle: list[dict[str, CSSList | list[str]]] = [
332-
{"props": list(props), "selectors": selectors}
333-
for props, selectors in cellstyle_map.items()
334-
]
335-
d.update({"cellstyle": cellstyle})
338+
if (r, c) in self.cell_context:
339+
cls = " " + self.cell_context[r, c]
340+
341+
data_element = _element(
342+
"td",
343+
f"{data_class} row{r} col{c}{cls}",
344+
value,
345+
(c not in self.hidden_columns),
346+
attributes="",
347+
display_value=self._display_funcs[(r, c)](value),
348+
)
336349

337-
table_attr = self.table_attributes
338-
use_mathjax = get_option("display.html.use_mathjax")
339-
if not use_mathjax:
340-
table_attr = table_attr or ""
341-
if 'class="' in table_attr:
342-
table_attr = table_attr.replace('class="', 'class="tex2jax_ignore ')
343-
else:
344-
table_attr += ' class="tex2jax_ignore"'
345-
d.update({"table_attributes": table_attr})
350+
# only add an id if the cell has a style
351+
if self.cell_ids or (r, c) in self.ctx:
352+
data_element["id"] = f"row{r}_col{c}"
353+
if (r, c) in self.ctx and self.ctx[r, c]: # only add if non-empty
354+
self.cellstyle_map[tuple(self.ctx[r, c])].append(
355+
f"row{r}_col{c}"
356+
)
346357

347-
if self.tooltips:
348-
d = self.tooltips._translate(self.data, self.uuid, d)
358+
data.append(data_element)
349359

350-
return d
360+
body.append(index_headers + data)
361+
return body
351362

352363
def format(
353364
self,
@@ -502,6 +513,27 @@ def format(
502513
return self
503514

504515

516+
def _element(
517+
html_element: str,
518+
html_class: str,
519+
value: Any,
520+
is_visible: bool,
521+
**kwargs,
522+
) -> dict:
523+
"""
524+
Template to return container with information for a <td></td> or <th></th> element.
525+
"""
526+
if "display_value" not in kwargs:
527+
kwargs["display_value"] = value
528+
return {
529+
"type": html_element,
530+
"value": value,
531+
"class": html_class,
532+
"is_visible": is_visible,
533+
**kwargs,
534+
}
535+
536+
505537
def _get_level_lengths(index, hidden_elements=None):
506538
"""
507539
Given an index, find the level length for each element.

0 commit comments

Comments
 (0)