3
3
from pandas .io .formats .style import Styler
4
4
from typing import Union , Optional , List , Any
5
5
from itables import show
6
+ from .styling import (
7
+ TABLE_STYLING ,
8
+ COVERAGE_THRESHOLDS ,
9
+ get_coverage_tier_css_props ,
10
+ )
6
11
7
12
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
13
14
HIGHLIGHT_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" )},
17
21
]
18
22
19
23
@@ -27,19 +31,111 @@ def _apply_highlight_range(
27
31
s_numeric = pd .to_numeric (
28
32
s_col , errors = "coerce"
29
33
) # Convert to numeric, non-convertibles become NaN
34
+
30
35
# 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
34
39
return np .where (condition , props , "" )
35
40
36
41
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
+
37
133
def color_coverage_columns (
38
134
styler : Styler , level : float , coverage_cols : list [str ] = ["Coverage" ]
39
135
) -> Styler :
40
136
"""
41
137
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 .
43
139
"""
44
140
if not isinstance (styler , Styler ):
45
141
raise TypeError ("Expected a pandas Styler object." )
@@ -54,26 +150,28 @@ def color_coverage_columns(
54
150
if not valid_coverage_cols :
55
151
return styler # No valid columns to style
56
152
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 )
72
155
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
74
166
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 ,
76
173
)
174
+
77
175
return current_styler
78
176
79
177
0 commit comments