33from pandas .io .formats .style import Styler
44from typing import Union , Optional , List , Any
55from itables import show
6+ from .styling import (
7+ TABLE_STYLING ,
8+ COVERAGE_THRESHOLDS ,
9+ get_coverage_tier_css_props ,
10+ )
611
712
8- # Define highlighting tiers as a list of dictionaries or tuples
9- # Each element defines: dist, props. Applied in order (later rules can override).
10- # Order: from least specific (largest dist) to most specific (smallest dist)
11- # or ensure the _apply_highlight_range logic correctly handles overlaps if props are different.
12- # Current logic: more specific (smaller dist) rules are applied last and override.
13+ # Define highlighting tiers using centralized color configuration
1314HIGHLIGHT_TIERS = [
14- {"dist" : 1.0 , "props" : "color:black;background-color:red;" },
15- {"dist" : 0.1 , "props" : "color:black;background-color:yellow;" },
16- {"dist" : 0.05 , "props" : "color:white;background-color:darkgreen;" },
15+ {"dist" : COVERAGE_THRESHOLDS ["poor" ], "props" : get_coverage_tier_css_props ("poor" )},
16+ {
17+ "dist" : COVERAGE_THRESHOLDS ["medium" ],
18+ "props" : get_coverage_tier_css_props ("medium" , "500" ),
19+ },
20+ {"dist" : COVERAGE_THRESHOLDS ["good" ], "props" : get_coverage_tier_css_props ("good" )},
1721]
1822
1923
@@ -27,19 +31,111 @@ def _apply_highlight_range(
2731 s_numeric = pd .to_numeric (
2832 s_col , errors = "coerce"
2933 ) # Convert to numeric, non-convertibles become NaN
34+
3035 # Apply style ONLY if value is WITHIN the current dist from level
31- # This means for tiered styling, the order of applying styles in the calling function matters.
32- # If a value falls into multiple dist categories, the LAST applied style for that dist will win.
33- condition = ( s_numeric >= level - dist ) & ( s_numeric <= level + dist )
36+ # Use absolute difference to determine which tier applies
37+ abs_diff = np . abs ( s_numeric - level )
38+ condition = abs_diff <= dist
3439 return np .where (condition , props , "" )
3540
3641
42+ def _determine_coverage_tier (value : float , level : float ) -> str :
43+ """
44+ Determine which coverage tier a value belongs to based on distance from level.
45+ Returns the most specific (smallest distance) tier that applies.
46+ """
47+ if pd .isna (value ):
48+ return ""
49+
50+ abs_diff = abs (value - level )
51+
52+ # Check tiers from most specific to least specific
53+ sorted_tiers = sorted (HIGHLIGHT_TIERS , key = lambda x : x ["dist" ])
54+
55+ for tier in sorted_tiers :
56+ if abs_diff <= tier ["dist" ]:
57+ return tier ["props" ]
58+
59+ return ""
60+
61+
62+ def _apply_base_table_styling (styler : Styler ) -> Styler :
63+ """
64+ Apply base styling to the table including headers, borders, and overall appearance.
65+ """
66+ # Define CSS styles for clean table appearance using centralized colors
67+ styles = [
68+ # Table-wide styling
69+ {
70+ "selector" : "table" ,
71+ "props" : [
72+ ("border-collapse" , "separate" ),
73+ ("border-spacing" , "0" ),
74+ ("width" , "100%" ),
75+ (
76+ "font-family" ,
77+ '"Segoe UI", -apple-system, BlinkMacSystemFont, "Roboto", sans-serif' ,
78+ ),
79+ ("font-size" , "14px" ),
80+ ("line-height" , "1.5" ),
81+ ("box-shadow" , "0 2px 8px rgba(0,0,0,0.1)" ),
82+ ("border-radius" , "8px" ),
83+ ("overflow" , "hidden" ),
84+ ],
85+ },
86+ # Header styling
87+ {
88+ "selector" : "thead th" ,
89+ "props" : [
90+ ("background-color" , TABLE_STYLING ["header_bg" ]),
91+ ("color" , TABLE_STYLING ["header_text" ]),
92+ ("font-weight" , "600" ),
93+ ("text-align" , "center" ),
94+ ("padding" , "12px 16px" ),
95+ ("border-bottom" , f'2px solid { TABLE_STYLING ["border" ]} ' ),
96+ ("position" , "sticky" ),
97+ ("top" , "0" ),
98+ ("z-index" , "10" ),
99+ ],
100+ },
101+ # Cell styling
102+ {
103+ "selector" : "tbody td" ,
104+ "props" : [
105+ ("padding" , "10px 16px" ),
106+ ("text-align" , "center" ),
107+ ("border-bottom" , f'1px solid { TABLE_STYLING ["border" ]} ' ),
108+ ("transition" , "background-color 0.2s ease" ),
109+ ],
110+ },
111+ # Row hover effect
112+ {
113+ "selector" : "tbody tr:hover td" ,
114+ "props" : [("background-color" , TABLE_STYLING ["hover_bg" ])],
115+ },
116+ # Caption styling
117+ {
118+ "selector" : "caption" ,
119+ "props" : [
120+ ("color" , TABLE_STYLING ["caption_color" ]),
121+ ("font-size" , "16px" ),
122+ ("font-weight" , "600" ),
123+ ("margin-bottom" , "16px" ),
124+ ("text-align" , "left" ),
125+ ("caption-side" , "top" ),
126+ ],
127+ },
128+ ]
129+
130+ return styler .set_table_styles (styles )
131+
132+
37133def color_coverage_columns (
38134 styler : Styler , level : float , coverage_cols : list [str ] = ["Coverage" ]
39135) -> Styler :
40136 """
41137 Applies tiered highlighting to specified coverage columns of a Styler object.
42- The order of application matters: more specific (narrower dist) rules are applied last to override .
138+ Uses non-overlapping logic to prevent CSS conflicts .
43139 """
44140 if not isinstance (styler , Styler ):
45141 raise TypeError ("Expected a pandas Styler object." )
@@ -54,26 +150,28 @@ def color_coverage_columns(
54150 if not valid_coverage_cols :
55151 return styler # No valid columns to style
56152
57- # Apply highlighting rules from the defined tiers
58- # The order in HIGHLIGHT_TIERS is important if props are meant to override.
59- # Pandas Styler.apply applies styles sequentially. If a cell matches multiple
60- # conditions from different .apply calls, the styles from later calls typically override
61- # or merge with earlier ones, depending on the CSS properties.
62- # For background-color, later calls will override.
63- current_styler = styler
64- for tier in HIGHLIGHT_TIERS :
65- current_styler = current_styler .apply (
66- _apply_highlight_range ,
67- level = level ,
68- dist = tier ["dist" ],
69- props = tier ["props" ],
70- subset = valid_coverage_cols ,
71- )
153+ # Apply base styling first
154+ current_styler = _apply_base_table_styling (styler )
72155
73- # Set font to bold for the coverage columns
156+ # Apply single tier styling to prevent conflicts
157+ def apply_coverage_tier_to_cell (s_col ):
158+ """Apply only the most appropriate coverage tier for each cell."""
159+ return s_col .apply (lambda x : _determine_coverage_tier (x , level ))
160+
161+ current_styler = current_styler .apply (
162+ apply_coverage_tier_to_cell , subset = valid_coverage_cols
163+ )
164+
165+ # Apply additional styling to coverage columns for emphasis
74166 current_styler = current_styler .set_properties (
75- ** {"font-weight" : "bold" }, subset = valid_coverage_cols
167+ ** {
168+ "text-align" : "center" ,
169+ "font-family" : "monospace" ,
170+ "font-size" : "13px" ,
171+ },
172+ subset = valid_coverage_cols ,
76173 )
174+
77175 return current_styler
78176
79177
0 commit comments