@@ -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+
505537def _get_level_lengths (index , hidden_elements = None ):
506538 """
507539 Given an index, find the level length for each element.
0 commit comments