From da83c16f4c14db5def9e13a32d0932686645ae2f Mon Sep 17 00:00:00 2001 From: "JHM Darbyshire (MBP)" Date: Thu, 30 Dec 2021 21:29:25 +0100 Subject: [PATCH 01/17] move idx_lengths to dict save --- pandas/io/formats/style_render.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/pandas/io/formats/style_render.py b/pandas/io/formats/style_render.py index a6b8913b23d9d..9beeed5ed11aa 100644 --- a/pandas/io/formats/style_render.py +++ b/pandas/io/formats/style_render.py @@ -257,13 +257,18 @@ def _translate( head = self._translate_header(sparse_cols, max_cols) d.update({"head": head}) + idx_lengths = _get_level_lengths( + self.index, sparse_index, max_rows, self.hidden_rows + ) + d.update({"index_lengths": idx_lengths}) + self.cellstyle_map: DefaultDict[tuple[CSSPair, ...], list[str]] = defaultdict( list ) self.cellstyle_map_index: DefaultDict[ tuple[CSSPair, ...], list[str] ] = defaultdict(list) - body = self._translate_body(sparse_index, max_rows, max_cols) + body = self._translate_body(idx_lengths, max_rows, max_cols) d.update({"body": body}) ctx_maps = { @@ -515,7 +520,7 @@ def _generate_index_names_row(self, iter: tuple, max_cols: int, col_lengths: dic return index_names + column_blanks - def _translate_body(self, sparsify_index: bool, max_rows: int, max_cols: int): + def _translate_body(self, idx_lengths: dict, max_rows: int, max_cols: int): """ Build each within table as a list @@ -538,10 +543,6 @@ def _translate_body(self, sparsify_index: bool, max_rows: int, max_cols: int): The associated HTML elements needed for template rendering. """ # for sparsifying a MultiIndex - idx_lengths = _get_level_lengths( - self.index, sparsify_index, max_rows, self.hidden_rows - ) - rlabels = self.data.index.tolist() if not isinstance(self.data.index, MultiIndex): rlabels = [[x] for x in rlabels] From edd39ea314f6d03da53ea2b048430fa9ef93cdae Mon Sep 17 00:00:00 2001 From: "JHM Darbyshire (MBP)" Date: Thu, 30 Dec 2021 22:29:35 +0100 Subject: [PATCH 02/17] cline looping --- pandas/io/formats/style_render.py | 12 ++++++++++++ pandas/io/formats/templates/latex_table.tpl | 7 ++++--- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/pandas/io/formats/style_render.py b/pandas/io/formats/style_render.py index 9beeed5ed11aa..a8c740b1a14fd 100644 --- a/pandas/io/formats/style_render.py +++ b/pandas/io/formats/style_render.py @@ -790,6 +790,18 @@ def _translate_latex(self, d: dict) -> None: body.append(row_body_headers + row_body_cells) d["body"] = body + d["clines"] = defaultdict(list) + rlabels, row_count = self.data.index.tolist(), 0 + for r, _ in enumerate(rlabels): + if r not in self.hidden_rows: + row_count += 1 + for idx_lvl in range(index_levels): + idx_len = d["index_lengths"].get((idx_lvl, r), None) + if idx_len is not None: # sparsified entry + d["clines"][row_count - 1 + idx_len].append( + fr"\cline{{{idx_lvl+1}-{index_levels}}}" + ) + def format( self, formatter: ExtFormatter | None = None, diff --git a/pandas/io/formats/templates/latex_table.tpl b/pandas/io/formats/templates/latex_table.tpl index 52387f03b6ce9..c91b15952fc5c 100644 --- a/pandas/io/formats/templates/latex_table.tpl +++ b/pandas/io/formats/templates/latex_table.tpl @@ -31,16 +31,17 @@ \{{toprule}} {% endif %} {% for row in head %} -{% for c in row %}{%- if not loop.first %} & {% endif %}{{parse_header(c, multirow_align, multicol_align, siunitx, convert_css)}}{% endfor %} \\ +{% for col in row %}{%- if not loop.first %} & {% endif %}{{parse_header(col, multirow_align, multicol_align, siunitx, convert_css)}}{% endfor %} \\ {% endfor %} {% set midrule = parse_table(table_styles, 'midrule') %} {% if midrule is not none %} \{{midrule}} {% endif %} {% for row in body %} -{% for c in row %}{% if not loop.first %} & {% endif %} - {%- if c.type == 'th' %}{{parse_header(c, multirow_align, multicol_align, False, convert_css)}}{% else %}{{parse_cell(c.cellstyle, c.display_value, convert_css)}}{% endif %} +{% for col in row %}{% if not loop.first %} & {% endif %} + {%- if col.type == 'th' %}{{parse_header(col, multirow_align, multicol_align, False, convert_css)}}{% else %}{{parse_cell(col.cellstyle, col.display_value, convert_css)}}{% endif %} {%- endfor %} \\ +{% for cline in clines[loop.index] %}{% if not loop.first %} {% endif %}{{ cline }}{% endfor %} {% endfor %} {% set bottomrule = parse_table(table_styles, 'bottomrule') %} {% if bottomrule is not none %} From 551ddba2a332817489150d89b09f2462c505da40 Mon Sep 17 00:00:00 2001 From: "JHM Darbyshire (MBP)" Date: Thu, 30 Dec 2021 22:30:00 +0100 Subject: [PATCH 03/17] cline looping --- pandas/io/formats/style_render.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/io/formats/style_render.py b/pandas/io/formats/style_render.py index a8c740b1a14fd..13a0b71c1f7b6 100644 --- a/pandas/io/formats/style_render.py +++ b/pandas/io/formats/style_render.py @@ -799,7 +799,7 @@ def _translate_latex(self, d: dict) -> None: idx_len = d["index_lengths"].get((idx_lvl, r), None) if idx_len is not None: # sparsified entry d["clines"][row_count - 1 + idx_len].append( - fr"\cline{{{idx_lvl+1}-{index_levels}}}" + f"\\cline{{{idx_lvl+1}-{index_levels}}}" ) def format( From 578d9aacb22260d91ee14e32f3258aaf79c1ab1e Mon Sep 17 00:00:00 2001 From: "JHM Darbyshire (MBP)" Date: Fri, 31 Dec 2021 08:43:07 +0100 Subject: [PATCH 04/17] cline looping --- pandas/io/formats/style.py | 17 ++++++++++ pandas/io/formats/style_render.py | 37 +++++++++++++-------- pandas/io/formats/templates/latex_table.tpl | 5 ++- 3 files changed, 44 insertions(+), 15 deletions(-) diff --git a/pandas/io/formats/style.py b/pandas/io/formats/style.py index acfa8e955a335..eb5d75d041e1c 100644 --- a/pandas/io/formats/style.py +++ b/pandas/io/formats/style.py @@ -494,6 +494,7 @@ def to_latex( position: str | None = None, position_float: str | None = None, hrules: bool | None = None, + clines: str | None = None, label: str | None = None, caption: str | tuple | None = None, sparse_index: bool | None = None, @@ -542,6 +543,21 @@ def to_latex( Defaults to ``pandas.options.styler.latex.hrules``, which is `False`. .. versionchanged:: 1.4.0 + clines : str, optional + Use to control adding \\cline commands for the index labels separation. + Possible values are: + + - `None`: no cline commands are added (default). + - `"all-data"`: a cline is added for every index value the width of the + table, including data entries. + - `"all-index"`: as above with lines extending only the width of the + index entries. + - `"skip-last-data": a cline is added for each index value except the last + level (which is never sparsified), extending the widtn of the table. + - `"skip-last-index"`: as above with lines extending only the width of the + index entries. + + .. versionadded:: 1.4.0 label : str, optional The LaTeX label included as: \\label{