Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 20 additions & 5 deletions doc/index.qmd
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,26 @@ $$
for a nominal coverage level is $1-\alpha$.
The corresponding coverage results are highlighted according to the following color scheme:

* <span style="background-color: #00FF00; color: black; padding: 2px 5px; border-radius: 3px;">Green</span> if the deviation to the nominal level is below $5\%$
* <span style="background-color: #FFFF00; color: black; padding: 2px 5px; border-radius: 3px;">Yellow</span> if the deviation to the nominal level is above $5\%$ and below $10\%$
* <span style="background-color: #FF0000; color: black; padding: 2px 5px; border-radius: 3px;">Red</span> if the deviation to the nominal level is above $10\%$
```{python}
#| echo: false
#| output: asis
from utils.styling import get_coverage_tier_html_span

# Generate color legend using centralized configuration
good_span = get_coverage_tier_html_span("good")
medium_span = get_coverage_tier_html_span("medium")
poor_span = get_coverage_tier_html_span("poor")

from IPython.display import Markdown, display

markdown_output = f"""
* {good_span} if the deviation to the nominal level is below 5%
* {medium_span} if the deviation to the nominal level is above 5% and below 10%
* {poor_span} if the deviation to the nominal level is above 10%
"""

display(Markdown(markdown_output))
```

For simulations with multiple parameters of interest, usually pointwise and uniform coverage is assessed.

Expand Down Expand Up @@ -247,5 +264,3 @@ fig.show()
```

:::

:::
147 changes: 146 additions & 1 deletion doc/styles.css
Original file line number Diff line number Diff line change
@@ -1 +1,146 @@
/* css styles */
/* Import Google Fonts */
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap');

/* Root font variables */
:root {
--font-family-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
--font-family-mono: 'JetBrains Mono', 'SF Mono', Monaco, Inconsolata, 'Roboto Mono', 'Source Code Pro', monospace;
}

/* Base typography */
body {
font-family: var(--font-family-sans);
font-weight: 400;
line-height: 1.6;
font-feature-settings: 'kern' 1, 'liga' 1, 'calt' 1;
}

/* Headings */
h1,
h2,
h3,
h4,
h5,
h6 {
font-family: var(--font-family-sans);
font-weight: 600;
line-height: 1.3;
letter-spacing: -0.025em;
}

h1 {
font-weight: 700;
font-size: 2.25rem;
}

h2 {
font-weight: 600;
font-size: 1.875rem;
}

h3 {
font-weight: 600;
font-size: 1.5rem;
}

h4 {
font-weight: 500;
font-size: 1.25rem;
}

/* Code and pre-formatted text */
code,
pre,
.sourceCode {
font-family: var(--font-family-mono);
font-weight: 400;
font-feature-settings: 'liga' 1, 'calt' 1;
}

/* Inline code */
code:not(pre code) {
font-size: 0.875em;
font-weight: 500;
padding: 0.125rem 0.25rem;
background-color: rgba(175, 184, 193, 0.2);
border-radius: 0.25rem;
}

/* Code blocks */
pre {
font-size: 0.875rem;
line-height: 1.5;
padding: 1rem;
border-radius: 0.5rem;
overflow-x: auto;
}

/* Navigation and UI elements */
.navbar-brand,
.nav-link {
font-family: var(--font-family-sans);
font-weight: 500;
}

.sidebar .nav-link {
font-weight: 400;
}

.sidebar .nav-link.active {
font-weight: 500;
}

/* Tables */
table {
font-family: var(--font-family-sans);
font-variant-numeric: tabular-nums;
}

th {
font-weight: 600;
}

/* Math equations - ensure good readability */
.math {
font-family: 'STIX Two Math', 'Times New Roman', serif;
}

/* Buttons and interactive elements */
.btn {
font-family: var(--font-family-sans);
font-weight: 500;
letter-spacing: 0.025em;
}

/* Improve readability for long text */
.content {
max-width: none;
}

p {
margin-bottom: 1.25rem;
}

/* List styling */
ul,
ol {
margin-bottom: 1.25rem;
}

li {
margin-bottom: 0.5rem;
}

/* Better spacing for equations */
.math.display {
margin: 1.5rem 0;
}

/* Blockquotes */
blockquote {
font-style: italic;
border-left: 4px solid #e9ecef;
padding-left: 1rem;
margin-left: 0;
color: #6c757d;
}
156 changes: 127 additions & 29 deletions doc/utils/style_tables.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,21 @@
from pandas.io.formats.style import Styler
from typing import Union, Optional, List, Any
from itables import show
from .styling import (
TABLE_STYLING,
COVERAGE_THRESHOLDS,
get_coverage_tier_css_props,
)


# Define highlighting tiers as a list of dictionaries or tuples
# Each element defines: dist, props. Applied in order (later rules can override).
# Order: from least specific (largest dist) to most specific (smallest dist)
# or ensure the _apply_highlight_range logic correctly handles overlaps if props are different.
# Current logic: more specific (smaller dist) rules are applied last and override.
# Define highlighting tiers using centralized color configuration
HIGHLIGHT_TIERS = [
{"dist": 1.0, "props": "color:black;background-color:red;"},
{"dist": 0.1, "props": "color:black;background-color:yellow;"},
{"dist": 0.05, "props": "color:white;background-color:darkgreen;"},
{"dist": COVERAGE_THRESHOLDS["poor"], "props": get_coverage_tier_css_props("poor")},
{
"dist": COVERAGE_THRESHOLDS["medium"],
"props": get_coverage_tier_css_props("medium", "500"),
},
{"dist": COVERAGE_THRESHOLDS["good"], "props": get_coverage_tier_css_props("good")},
]


Expand All @@ -27,19 +31,111 @@ def _apply_highlight_range(
s_numeric = pd.to_numeric(
s_col, errors="coerce"
) # Convert to numeric, non-convertibles become NaN

# Apply style ONLY if value is WITHIN the current dist from level
# This means for tiered styling, the order of applying styles in the calling function matters.
# If a value falls into multiple dist categories, the LAST applied style for that dist will win.
condition = (s_numeric >= level - dist) & (s_numeric <= level + dist)
# Use absolute difference to determine which tier applies
abs_diff = np.abs(s_numeric - level)
condition = abs_diff <= dist
return np.where(condition, props, "")


def _determine_coverage_tier(value: float, level: float) -> str:
"""
Determine which coverage tier a value belongs to based on distance from level.
Returns the most specific (smallest distance) tier that applies.
"""
if pd.isna(value):
return ""

abs_diff = abs(value - level)

# Check tiers from most specific to least specific
sorted_tiers = sorted(HIGHLIGHT_TIERS, key=lambda x: x["dist"])

for tier in sorted_tiers:
if abs_diff <= tier["dist"]:
return tier["props"]

return ""


def _apply_base_table_styling(styler: Styler) -> Styler:
"""
Apply base styling to the table including headers, borders, and overall appearance.
"""
# Define CSS styles for clean table appearance using centralized colors
styles = [
# Table-wide styling
{
"selector": "table",
"props": [
("border-collapse", "separate"),
("border-spacing", "0"),
("width", "100%"),
(
"font-family",
'"Segoe UI", -apple-system, BlinkMacSystemFont, "Roboto", sans-serif',
),
("font-size", "14px"),
("line-height", "1.5"),
("box-shadow", "0 2px 8px rgba(0,0,0,0.1)"),
("border-radius", "8px"),
("overflow", "hidden"),
],
},
# Header styling
{
"selector": "thead th",
"props": [
("background-color", TABLE_STYLING["header_bg"]),
("color", TABLE_STYLING["header_text"]),
("font-weight", "600"),
("text-align", "center"),
("padding", "12px 16px"),
("border-bottom", f'2px solid {TABLE_STYLING["border"]}'),
("position", "sticky"),
("top", "0"),
("z-index", "10"),
],
},
# Cell styling
{
"selector": "tbody td",
"props": [
("padding", "10px 16px"),
("text-align", "center"),
("border-bottom", f'1px solid {TABLE_STYLING["border"]}'),
("transition", "background-color 0.2s ease"),
],
},
# Row hover effect
{
"selector": "tbody tr:hover td",
"props": [("background-color", TABLE_STYLING["hover_bg"])],
},
# Caption styling
{
"selector": "caption",
"props": [
("color", TABLE_STYLING["caption_color"]),
("font-size", "16px"),
("font-weight", "600"),
("margin-bottom", "16px"),
("text-align", "left"),
("caption-side", "top"),
],
},
]

return styler.set_table_styles(styles)


def color_coverage_columns(
styler: Styler, level: float, coverage_cols: list[str] = ["Coverage"]
) -> Styler:
"""
Applies tiered highlighting to specified coverage columns of a Styler object.
The order of application matters: more specific (narrower dist) rules are applied last to override.
Uses non-overlapping logic to prevent CSS conflicts.
"""
if not isinstance(styler, Styler):
raise TypeError("Expected a pandas Styler object.")
Expand All @@ -54,26 +150,28 @@ def color_coverage_columns(
if not valid_coverage_cols:
return styler # No valid columns to style

# Apply highlighting rules from the defined tiers
# The order in HIGHLIGHT_TIERS is important if props are meant to override.
# Pandas Styler.apply applies styles sequentially. If a cell matches multiple
# conditions from different .apply calls, the styles from later calls typically override
# or merge with earlier ones, depending on the CSS properties.
# For background-color, later calls will override.
current_styler = styler
for tier in HIGHLIGHT_TIERS:
current_styler = current_styler.apply(
_apply_highlight_range,
level=level,
dist=tier["dist"],
props=tier["props"],
subset=valid_coverage_cols,
)
# Apply base styling first
current_styler = _apply_base_table_styling(styler)

# Set font to bold for the coverage columns
# Apply single tier styling to prevent conflicts
def apply_coverage_tier_to_cell(s_col):
"""Apply only the most appropriate coverage tier for each cell."""
return s_col.apply(lambda x: _determine_coverage_tier(x, level))

current_styler = current_styler.apply(
apply_coverage_tier_to_cell, subset=valid_coverage_cols
)

# Apply additional styling to coverage columns for emphasis
current_styler = current_styler.set_properties(
**{"font-weight": "bold"}, subset=valid_coverage_cols
**{
"text-align": "center",
"font-family": "monospace",
"font-size": "13px",
},
subset=valid_coverage_cols,
)

return current_styler


Expand Down
Loading