@@ -164,14 +164,16 @@ def _render_html(
164
164
html_style_tpl = self .template_html_style ,
165
165
)
166
166
167
- def _render_latex (self , sparse_index : bool , sparse_columns : bool , ** kwargs ) -> str :
167
+ def _render_latex (
168
+ self , sparse_index : bool , sparse_columns : bool , clines : str | None , ** kwargs
169
+ ) -> str :
168
170
"""
169
171
Render a Styler in latex format
170
172
"""
171
173
self ._compute ()
172
174
173
175
d = self ._translate (sparse_index , sparse_columns , blank = "" )
174
- self ._translate_latex (d )
176
+ self ._translate_latex (d , clines = clines )
175
177
176
178
self .template_latex .globals ["parse_wrap" ] = _parse_latex_table_wrapping
177
179
self .template_latex .globals ["parse_table" ] = _parse_latex_table_styles
@@ -257,13 +259,19 @@ def _translate(
257
259
head = self ._translate_header (sparse_cols , max_cols )
258
260
d .update ({"head" : head })
259
261
262
+ # for sparsifying a MultiIndex and for use with latex clines
263
+ idx_lengths = _get_level_lengths (
264
+ self .index , sparse_index , max_rows , self .hidden_rows
265
+ )
266
+ d .update ({"index_lengths" : idx_lengths })
267
+
260
268
self .cellstyle_map : DefaultDict [tuple [CSSPair , ...], list [str ]] = defaultdict (
261
269
list
262
270
)
263
271
self .cellstyle_map_index : DefaultDict [
264
272
tuple [CSSPair , ...], list [str ]
265
273
] = defaultdict (list )
266
- body = self ._translate_body (sparse_index , max_rows , max_cols )
274
+ body = self ._translate_body (idx_lengths , max_rows , max_cols )
267
275
d .update ({"body" : body })
268
276
269
277
ctx_maps = {
@@ -515,7 +523,7 @@ def _generate_index_names_row(self, iter: tuple, max_cols: int, col_lengths: dic
515
523
516
524
return index_names + column_blanks
517
525
518
- def _translate_body (self , sparsify_index : bool , max_rows : int , max_cols : int ):
526
+ def _translate_body (self , idx_lengths : dict , max_rows : int , max_cols : int ):
519
527
"""
520
528
Build each <tr> within table <body> as a list
521
529
@@ -537,11 +545,6 @@ def _translate_body(self, sparsify_index: bool, max_rows: int, max_cols: int):
537
545
body : list
538
546
The associated HTML elements needed for template rendering.
539
547
"""
540
- # for sparsifying a MultiIndex
541
- idx_lengths = _get_level_lengths (
542
- self .index , sparsify_index , max_rows , self .hidden_rows
543
- )
544
-
545
548
rlabels = self .data .index .tolist ()
546
549
if not isinstance (self .data .index , MultiIndex ):
547
550
rlabels = [[x ] for x in rlabels ]
@@ -738,7 +741,7 @@ def _generate_body_row(
738
741
739
742
return index_headers + data
740
743
741
- def _translate_latex (self , d : dict ) -> None :
744
+ def _translate_latex (self , d : dict , clines : str | None ) -> None :
742
745
r"""
743
746
Post-process the default render dict for the LaTeX template format.
744
747
@@ -749,10 +752,10 @@ def _translate_latex(self, d: dict) -> None:
749
752
or multirow sparsification (so that \multirow and \multicol work correctly).
750
753
"""
751
754
index_levels = self .index .nlevels
752
- visible_index_levels = index_levels - sum (self .hide_index_ )
755
+ visible_index_level_n = index_levels - sum (self .hide_index_ )
753
756
d ["head" ] = [
754
757
[
755
- {** col , "cellstyle" : self .ctx_columns [r , c - visible_index_levels ]}
758
+ {** col , "cellstyle" : self .ctx_columns [r , c - visible_index_level_n ]}
756
759
for c , col in enumerate (row )
757
760
if col ["is_visible" ]
758
761
]
@@ -790,6 +793,39 @@ def _translate_latex(self, d: dict) -> None:
790
793
body .append (row_body_headers + row_body_cells )
791
794
d ["body" ] = body
792
795
796
+ # clines are determined from info on index_lengths and hidden_rows and input
797
+ # to a dict defining which row clines should be added in the template.
798
+ if clines not in [
799
+ None ,
800
+ "all;data" ,
801
+ "all;index" ,
802
+ "skip-last;data" ,
803
+ "skip-last;index" ,
804
+ ]:
805
+ raise ValueError (
806
+ f"`clines` value of { clines } is invalid. Should either be None or one "
807
+ f"of 'all;data', 'all;index', 'skip-last;data', 'skip-last;index'."
808
+ )
809
+ elif clines is not None :
810
+ data_len = len (row_body_cells ) if "data" in clines else 0
811
+
812
+ d ["clines" ] = defaultdict (list )
813
+ visible_row_indexes : list [int ] = [
814
+ r for r in range (len (self .data .index )) if r not in self .hidden_rows
815
+ ]
816
+ visible_index_levels : list [int ] = [
817
+ i for i in range (index_levels ) if not self .hide_index_ [i ]
818
+ ]
819
+ for rn , r in enumerate (visible_row_indexes ):
820
+ for lvln , lvl in enumerate (visible_index_levels ):
821
+ if lvl == index_levels - 1 and "skip-last" in clines :
822
+ continue
823
+ idx_len = d ["index_lengths" ].get ((lvl , r ), None )
824
+ if idx_len is not None : # i.e. not a sparsified entry
825
+ d ["clines" ][rn + idx_len ].append (
826
+ f"\\ cline{{{ lvln + 1 } -{ len (visible_index_levels )+ data_len } }}"
827
+ )
828
+
793
829
def format (
794
830
self ,
795
831
formatter : ExtFormatter | None = None ,
0 commit comments