From a0215857379b6ff2fa76cb9b496ef1e4597452b5 Mon Sep 17 00:00:00 2001 From: shwinnn Date: Tue, 26 Jan 2021 11:11:51 +0000 Subject: [PATCH] Support multiple column levels (#509) * Support multiple column levels * Make repeated column titles span multiple columns * Select table data with iloc rather than loc * Include header breaks from header levels * remove unnecessary listcomp * remove space from comment * Go from itertools.accumlate to numpy.cumsum for python 2.7 compatibility * remove numpy dependency * Simplify running total --- dash_bootstrap_components/_table.py | 62 ++++++++++++++++++++++++++++- 1 file changed, 60 insertions(+), 2 deletions(-) diff --git a/dash_bootstrap_components/_table.py b/dash_bootstrap_components/_table.py index e5d5ae27a..53222ef81 100644 --- a/dash_bootstrap_components/_table.py +++ b/dash_bootstrap_components/_table.py @@ -1,3 +1,7 @@ +from functools import reduce +from itertools import groupby +from operator import add + import dash_html_components as html @@ -70,15 +74,69 @@ def _generate_table_from_df( ) elif isinstance(header, dict): df = df.rename(columns=header) + + # Get the actual headers + n_levels = df.columns.nlevels + header_values = [ + list(df.columns.get_level_values(level)) + for level in range(n_levels) + ] + + # The sizes of consecutive header groups at each level + header_spans = [ + [len(list(group)) for _, group in groupby(level_values)] + for level_values in header_values + ] + + # The positions of header changes for each level as an integer + header_breaks = [ + [sum(level_spans[:i]) for i in range(1, len(level_spans) + 1)] + for level_spans in header_spans + ] + + # Include breaks from higher levels + header_breaks = [ + sorted(set(reduce(add, header_breaks[:level])).union({0})) + for level in range(1, n_levels + 1) + ] + + # Go from header break positions back to cell spans + header_spans = [ + reversed( + [ + level_breaks[i] - level_breaks[i - 1] + for i in range(len(level_breaks) - 1, 0, -1) + ] + ) + for level_breaks in header_breaks + ] + table = [ - html.Thead(html.Tr(children=[html.Th(col) for col in df.columns])) + html.Thead( + [ + html.Tr( + children=[ + html.Th( + header_values[level][pos], + colSpan=span, + ) + for pos, span in zip( + header_breaks[level], header_spans[level] + ) + ] + ) + for level in range(n_levels) + ] + ) ] else: table = [] table.append( html.Tbody( [ - html.Tr([html.Td(df.iloc[i][col]) for col in df.columns]) + html.Tr( + [html.Td(df.iloc[i, j]) for j in range(len(df.columns))] + ) for i in range(len(df)) ] )