Skip to content
Open
Changes from all 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
87 changes: 50 additions & 37 deletions pandas/io/formats/style_render.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,11 @@ class StylerRenderer:
Base class to process rendering a Styler with a specified jinja2 template.
"""

loader = jinja2.PackageLoader("pandas", "io/formats/templates")
import os

loader = jinja2.FileSystemLoader(
os.path.join(os.path.dirname(__file__), "templates")
)
env = jinja2.Environment(loader=loader, trim_blocks=True)
template_html = env.get_template("html.tpl")
template_html_table = env.get_template("html_table.tpl")
Expand Down Expand Up @@ -834,10 +838,7 @@ def _generate_body_row(

data_element = _element(
"td",
(
f"{self.css['data']} {self.css['row']}{r} "
f"{self.css['col']}{c}{cls}"
),
(f"{self.css['data']} {self.css['row']}{r} {self.css['col']}{c}{cls}"),
value,
data_element_visible,
attributes="",
Expand Down Expand Up @@ -956,7 +957,7 @@ def concatenated_visible_rows(obj):
idx_len = d["index_lengths"].get((lvl, r), None)
if idx_len is not None: # i.e. not a sparsified entry
d["clines"][rn + idx_len].append(
f"\\cline{{{lvln+1}-{len(visible_index_levels)+data_len}}}"
f"\\cline{{{lvln + 1}-{len(visible_index_levels) + data_len}}}"
)

def format(
Expand Down Expand Up @@ -1211,7 +1212,7 @@ def format(
data = self.data.loc[subset]

if not isinstance(formatter, dict):
formatter = {col: formatter for col in data.columns}
formatter = dict.fromkeys(data.columns, formatter)

cis = self.columns.get_indexer_for(data.columns)
ris = self.index.get_indexer_for(data.index)
Expand Down Expand Up @@ -1397,7 +1398,7 @@ def format_index(
return self # clear the formatter / revert to default and avoid looping

if not isinstance(formatter, dict):
formatter = {level: formatter for level in levels_}
formatter = dict.fromkeys(levels_, formatter)
else:
formatter = {
obj._get_level_number(level): formatter_
Expand Down Expand Up @@ -1540,7 +1541,7 @@ def relabel_index(

>>> df = pd.DataFrame({"samples": np.random.rand(10)})
>>> styler = df.loc[np.random.randint(0, 10, 3)].style
>>> styler.relabel_index([f"sample{i+1} ({{}})" for i in range(3)])
>>> styler.relabel_index([f"sample{i + 1} ({{}})" for i in range(3)])
... # doctest: +SKIP
samples
sample1 (5) 0.315811
Expand Down Expand Up @@ -1694,7 +1695,7 @@ def format_index_names(
return self # clear the formatter / revert to default and avoid looping

if not isinstance(formatter, dict):
formatter = {level: formatter for level in levels_}
formatter = dict.fromkeys(levels_, formatter)
else:
formatter = {
obj._get_level_number(level): formatter_
Expand Down Expand Up @@ -2384,21 +2385,28 @@ def _parse_latex_cell_styles(
"""
if convert_css:
latex_styles = _parse_latex_css_conversion(latex_styles)
for command, options in latex_styles[::-1]: # in reverse for most recent style
formatter = {
"--wrap": f"{{\\{command}--to_parse {display_value}}}",
"--nowrap": f"\\{command}--to_parse {display_value}",
"--lwrap": f"{{\\{command}--to_parse}} {display_value}",
"--rwrap": f"\\{command}--to_parse{{{display_value}}}",
"--dwrap": f"{{\\{command}--to_parse}}{{{display_value}}}",
}
display_value = f"\\{command}{options} {display_value}"
for command, options in reversed(latex_styles):
options_str = options if isinstance(options, str) else str(options)
found_arg = None
for arg in ["--nowrap", "--wrap", "--lwrap", "--rwrap", "--dwrap"]:
if arg in str(options):
display_value = formatter[arg].replace(
"--to_parse", _parse_latex_options_strip(value=options, arg=arg)
)
if arg in options_str:
found_arg = arg
break # only ever one purposeful entry

if found_arg is None:
display_value = f"\\{command}{options} {display_value}"
else:
parsed_opt = _parse_latex_options_strip(value=options, arg=found_arg)
if found_arg == "--wrap":
display_value = f"{{\\{command}{parsed_opt} {display_value}}}"
elif found_arg == "--nowrap":
display_value = f"\\{command}{parsed_opt} {display_value}"
elif found_arg == "--lwrap":
display_value = f"{{\\{command}{parsed_opt}}} {display_value}"
elif found_arg == "--rwrap":
display_value = f"\\{command}{parsed_opt}{{{display_value}}}"
elif found_arg == "--dwrap":
display_value = f"{{\\{command}{parsed_opt}}}{{{display_value}}}"
return display_value


Expand All @@ -2409,7 +2417,7 @@ def _parse_latex_header_span(
wrap: bool = False,
convert_css: bool = False,
) -> str:
r"""
"""
Refactor the cell `display_value` if a 'colspan' or 'rowspan' attribute is present.

'rowspan' and 'colspan' do not occur simultaneously. If they are detected then
Expand All @@ -2431,25 +2439,30 @@ def _parse_latex_header_span(
display_val = _parse_latex_cell_styles(
cell["cellstyle"], cell["display_value"], convert_css
)
if "attributes" in cell:
attrs = cell["attributes"]
if 'colspan="' in attrs:
colspan = attrs[attrs.find('colspan="') + 9 :] # len('colspan="') = 9
colspan = int(colspan[: colspan.find('"')])
if "naive-l" == multicol_align:
out = f"{{{display_val}}}" if wrap else f"{display_val}"
attrs = cell.get("attributes")
if attrs:
# Use find only once and cache results
col_idx = attrs.find('colspan="')
if col_idx != -1:
start = col_idx + 9
end = attrs.find('"', start)
colspan = int(attrs[start:end])
if multicol_align == "naive-l":
out = f"{{{display_val}}}" if wrap else display_val
blanks = " & {}" if wrap else " &"
return out + blanks * (colspan - 1)
elif "naive-r" == multicol_align:
out = f"{{{display_val}}}" if wrap else f"{display_val}"
elif multicol_align == "naive-r":
out = f"{{{display_val}}}" if wrap else display_val
blanks = "{} & " if wrap else "& "
return blanks * (colspan - 1) + out
return f"\\multicolumn{{{colspan}}}{{{multicol_align}}}{{{display_val}}}"
elif 'rowspan="' in attrs:
row_idx = attrs.find('rowspan="')
if row_idx != -1:
if multirow_align == "naive":
return display_val
rowspan = attrs[attrs.find('rowspan="') + 9 :]
rowspan = int(rowspan[: rowspan.find('"')])
start = row_idx + 9
end = attrs.find('"', start)
rowspan = int(attrs[start:end])
return f"\\multirow[{multirow_align}]{{{rowspan}}}{{*}}{{{display_val}}}"
if wrap:
return f"{{{display_val}}}"
Expand Down Expand Up @@ -2503,7 +2516,7 @@ def color(value, user_arg, command, comm_arg):
if value[0] == "#" and len(value) == 7: # color is hex code
return command, f"[HTML]{{{value[1:].upper()}}}{arg}"
if value[0] == "#" and len(value) == 4: # color is short hex code
val = f"{value[1].upper()*2}{value[2].upper()*2}{value[3].upper()*2}"
val = f"{value[1].upper() * 2}{value[2].upper() * 2}{value[3].upper() * 2}"
return command, f"[HTML]{{{val}}}{arg}"
elif value[:3] == "rgb": # color is rgb or rgba
r = re.findall("(?<=\\()[0-9\\s%]+(?=,)", value)[0].strip()
Expand Down