This commit is contained in:
bolade
2025-11-21 12:49:36 +01:00
parent 79daa8cea1
commit cc9e526fda
6 changed files with 193 additions and 105 deletions
+82 -25
View File
@@ -1310,15 +1310,19 @@ class GraphGenerator:
self,
data: list[list],
columns: list[str],
vo2_max_value: float = None,
category: str = None,
cell_colors: list[list[str]] = None,
save_as_base64: bool = True,
) -> str:
"""
Generate VO2 Max table as an image with optimized sizing.
Generate VO2 Max table as an image with optimized sizing, highlighting the patient's category.
Args:
data: List of rows (each row is a list of values)
columns: List of column headers
vo2_max_value: Patient's VO2 max value (for title and arrow)
category: Category that the patient falls into (e.g., 'Good', 'Excellent')
cell_colors: Optional matrix of cell colors
save_as_base64: If True, return base64 string
@@ -1327,12 +1331,11 @@ class GraphGenerator:
"""
import io
# Fixed optimal sizing for VO2 Max table (8 columns, 1 data row)
fig, ax = plt.subplots(figsize=(16, 2.5))
ax.axis("off")
from matplotlib.patches import FancyArrowPatch, RegularPolygon
# Even column widths for VO2 Max table
col_widths = [1.0 / len(columns)] * len(columns)
# Fixed optimal sizing for VO2 Max table (7 columns, 1 data row)
fig, ax = plt.subplots(figsize=(14, 3))
ax.axis("off")
# Create table
table = ax.table(
@@ -1340,33 +1343,87 @@ class GraphGenerator:
colLabels=columns,
cellLoc="center",
loc="center",
colColours=["#4dd0e1"] * len(columns),
colWidths=col_widths,
bbox=[0, 0, 1, 1],
)
# Style the table
table.auto_set_font_size(False)
table.set_fontsize(11)
table.scale(1, 3.0)
table.scale(1, 2.5)
# Apply cell colors
if cell_colors:
for i, row_colors in enumerate(cell_colors):
for j, color in enumerate(row_colors):
if color and j < len(columns):
cell = table[(i + 1, j)]
cell.set_facecolor(color)
# Header row styling (cyan background)
for i in range(len(columns)):
cell = table[(0, i)]
cell.set_facecolor("#7dd3fc") # cyan-300 equivalent
cell.set_text_props(weight="bold", color="black", fontsize=12)
cell.set_edgecolor("#9ca3af") # gray-400
cell.set_linewidth(1)
# Style all cells
for (row, col), cell in table.get_celld().items():
if row == 0:
cell.set_text_props(weight="bold", fontsize=12)
cell.set_edgecolor("#333333")
cell.set_linewidth(1.5)
# Find the column index for the category (if provided)
category_index = None
if category and category in columns:
category_index = columns.index(category)
# Data row styling
for i in range(len(data[0])):
cell = table[(1, i)]
if i == 0: # Age column
cell.set_facecolor("#a5f3fc") # cyan-200
cell.set_text_props(weight="semibold", color="black", fontsize=11)
else:
cell.set_edgecolor("#666666")
cell.set_linewidth(1.0)
cell.set_text_props(fontsize=10)
cell.set_facecolor("#f3f4f6") # gray-100
cell.set_text_props(color="black", fontsize=10)
# Bold the cell that corresponds to the patient's category
if category_index is not None and i == category_index:
cell.set_text_props(weight="bold", color="black", fontsize=11)
cell.set_edgecolor("#9ca3af") # gray-400
cell.set_linewidth(1)
# Add arrow indicator below the category column
if category_index is not None:
# Calculate position
cell_width = 1.0 / len(columns)
arrow_x = (category_index + 0.5) * cell_width
# Draw arrow pointing up
arrow = FancyArrowPatch(
(arrow_x, -0.15),
(arrow_x, -0.05),
arrowstyle="->",
mutation_scale=20,
linewidth=2,
color="black",
transform=ax.transAxes,
)
ax.add_patch(arrow)
# Add triangle at the top
triangle = RegularPolygon(
(arrow_x, -0.05),
3,
radius=0.02,
orientation=np.pi / 2,
color="black",
transform=ax.transAxes,
)
ax.add_patch(triangle)
# Set title - calculate approximate percentile
if vo2_max_value is not None:
if category == "Superior":
percentile = "100th percentile"
else:
percentile_map = {
"Very Poor": "1st-10th percentile",
"Poor": "10th-20th percentile",
"Fair": "20th-40th percentile",
"Good": "40th-60th percentile",
"Excellent": "60th-80th percentile",
}
percentile = percentile_map.get(category, "N/A")
title = f"VO2 Max - {vo2_max_value:.1f} ({percentile})"
ax.set_title(title, fontsize=14, fontweight="bold", pad=20)
if save_as_base64:
buf = io.BytesIO()