Skip to content
Open
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
231 changes: 145 additions & 86 deletions diagrams/dasharo_forks.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,77 +57,135 @@ def count_closed_prs(repo, state, date):
return 0


def add_labels_merged(bars):
for bar in bars:
height = bar.get_height()
if height <= 0:
continue
font_s = 14
plt.annotate(
f"{height}",
xy=(bar.get_x() + bar.get_width() / 2, bar.get_y() + height / 2),
xytext=(0, 3), # 3 points vertical offset
textcoords="offset points",
ha="center",
va="bottom",
color="white",
fontsize=font_s,
fontweight="bold",
)


def add_labels_closed(bars, offset=0):
for bar in bars:
height = bar.get_height()
logging.info("height:{}, x:{}, y:{}".format(height, bar.get_x(), bar.get_y()))
if height <= 0:
continue
font_s = 14
plt.annotate(
f"{height}",
xy=(bar.get_x() + bar.get_width() / 2, bar.get_y() + height / 2 - offset),
xytext=(0, 3), # 3 points vertical offset
textcoords="offset points",
ha="center",
va="bottom",
color="white",
fontsize=font_s,
fontweight="bold",
)


def add_labels_open(bars, offset=0):
for bar in bars:
height = bar.get_height()
if height <= 0:
continue
font_s = 14
logging.info("height:{}, x:{}, y:{}".format(height, bar.get_x(), bar.get_y()))
plt.annotate(
f"{height}",
xy=(bar.get_x() + bar.get_width() / 2, bar.get_y() + height / 2 - offset),
xytext=(0, 3), # 3 points vertical offset
textcoords="offset points",
ha="center",
va="bottom",
color="black",
fontsize=font_s,
fontweight="bold",
)


dates = [
"2023-03",
"2023-07",
"2023-09",
"2023-12",
"2024-03",
"2024-06",
"2024-09",
"2024-12",
"2025-03",
"2025-06",
]
def add_labels(bars_merged, bars_closed, bars_open, min_height_internal=8):
"""
Smart labeling that avoids overlaps between adjacent segments.
Processes all bars together to coordinate positioning.
"""
# Store label information for collision detection
label_positions = []

# Process each date column (assuming all bar lists have same length)
for i in range(len(bars_merged)):
bars_in_stack = [
(bars_merged[i], "merged", "white", "#1a5a0f"),
(bars_closed[i], "closed", "white", "#000000"),
(bars_open[i], "open", "white", "#0066cc"),
]

# Calculate positions for this stack
stack_labels = []

for bar, bar_type, internal_color, external_color in bars_in_stack:
height = bar.get_height()
if height <= 0: # Skip zero height bars completely
continue

x_center = bar.get_x() + bar.get_width() / 2

# Determine initial positioning
if height >= min_height_internal:
# Internal label
y_pos = bar.get_y() + height / 2
is_external = False
color = internal_color
font_size = 13
else:
y_pos = bar.get_y() + height + 2
is_external = True
color = external_color
font_size = 11

stack_labels.append(
{
"bar": bar,
"height": height,
"x": x_center,
"y": y_pos,
"is_external": is_external,
"color": color,
"font_size": font_size,
"bar_type": bar_type,
"text": str(int(height)),
}
)

# Resolve collisions in this stack
stack_labels = resolve_stack_collisions(stack_labels)

# Add the resolved labels to the plot
for label_info in stack_labels:
plt.annotate(
label_info["text"],
xy=(label_info["x"], label_info["y"]),
ha="center",
va="center" if not label_info["is_external"] else "bottom",
color=label_info["color"],
fontsize=label_info["font_size"],
fontweight="bold",
)


def resolve_stack_collisions(stack_labels):
"""
Resolve label collisions within a single stack by considering both internal and external labels.
"""
if len(stack_labels) <= 1:
return stack_labels

# Sort labels by their original y position (bottom to top)
stack_labels.sort(key=lambda x: x["y"])

# Minimum spacing between any two labels
min_spacing = 6

# Check for collisions between ALL adjacent labels (internal and external)
for i in range(1, len(stack_labels)):
prev_label = stack_labels[i - 1]
curr_label = stack_labels[i]

# Calculate the visual bounds of each label
prev_top = prev_label["y"] + (
6 if prev_label["is_external"] else 7
) # Approximate label height
curr_bottom = curr_label["y"] - (
3 if curr_label["is_external"] else 7
) # Approximate label height

# Check for overlap
if prev_top > curr_bottom:
# There's an overlap! We need to resolve it

# Strategy: if current label is internal and would overlap with external above,
# move current label to external position
if not curr_label["is_external"] and prev_label["is_external"]:
# Move current label outside
curr_label["y"] = curr_label["bar"].get_y() + curr_label["height"] + 4
curr_label["is_external"] = True
# Update color for external positioning
if curr_label["bar_type"] == "merged":
curr_label["color"] = "#1a5a0f"
elif curr_label["bar_type"] == "closed":
curr_label["color"] = "#000000"
else: # open
curr_label["color"] = "#0066cc"

# Ensure minimum spacing between external labels
if curr_label["is_external"] and prev_label["is_external"]:
if curr_label["y"] - prev_label["y"] < min_spacing:
curr_label["y"] = prev_label["y"] + min_spacing

# If previous is internal and current is external, adjust current position
elif not prev_label["is_external"] and curr_label["is_external"]:
# Calculate minimum safe position for external label
safe_y = prev_label["y"] + 6 + 3
bar_top = curr_label["bar"].get_y() + curr_label["height"]
base_external_y = bar_top + 4

# Use the higher of the two positions
curr_label["y"] = max(safe_y, base_external_y)

return stack_labels


def gather_data(repo, dates, differences=False):
Expand All @@ -153,9 +211,7 @@ def gather_data(repo, dates, differences=False):
return data


def plot_pr_statistics(
repo, dates, data, title, filename, label_offsets, cap_to_zero=False
):
def plot_pr_statistics(repo, dates, data, title, filename, cap_to_zero=False):
"""
Plots PR statistics for a given repository.

Expand All @@ -165,7 +221,6 @@ def plot_pr_statistics(
data (dict): Dictionary containing PR data.
title (str): Title of the plot.
filename (str): Filename to save the plot.
label_offsets (tuple): Offsets for merged, closed, and open PR labels.
"""
plt.figure(figsize=(10, 6))

Expand Down Expand Up @@ -200,9 +255,7 @@ def plot_pr_statistics(
plt.ylabel("Number of PRs", fontsize=16, fontweight="bold", color="#272727")

# Add labels to the bars
add_labels_merged(bars_merged)
add_labels_closed(bars_closed, offset=label_offsets[0])
add_labels_open(bars_open, offset=label_offsets[1])
add_labels(bars_merged, bars_closed, bars_open)

# Add legend
plt.legend(fontsize=12)
Expand All @@ -217,6 +270,20 @@ def plot_pr_statistics(
plt.close()


dates = [
"2023-03",
"2023-07",
"2023-09",
"2023-12",
"2024-03",
"2024-06",
"2024-09",
"2024-12",
"2025-03",
"2025-06",
"2025-08",
]

# Plot for Dasharo/coreboot
repo_coreboot = "Dasharo/coreboot"
data_coreboot = gather_data(repo_coreboot, dates)
Expand All @@ -226,7 +293,6 @@ def plot_pr_statistics(
data_coreboot,
"PR Statistics for Dasharo/coreboot downstream",
"img/dug_10/dasharo_coreboot.png",
label_offsets=(30, 5),
)

# Plot for Dasharo/edk2
Expand All @@ -238,7 +304,6 @@ def plot_pr_statistics(
data_edk2,
"PR Statistics for Dasharo/edk2 fork",
"img/dug_10/dasharo_edk2.png",
label_offsets=(7, 1),
)

# Plot for Dasharo/open-source-firmware-validation
Expand All @@ -250,7 +315,6 @@ def plot_pr_statistics(
data_osfv,
"PR Statistics for OSFV repository",
"img/dug_10/dasharo_prs_osfv_total.png",
label_offsets=(7, 1),
)
data_osfv = gather_data(repo_osfv, dates, differences=True)
plot_pr_statistics(
Expand All @@ -259,7 +323,6 @@ def plot_pr_statistics(
data_osfv,
"PR Statistics for OSFV repository - increments",
"img/dug_10/dasharo_prs_osfv_diff.png",
label_offsets=(7, 1),
)

# Plot for Dasharo/osfv-scripts
Expand All @@ -271,7 +334,6 @@ def plot_pr_statistics(
data_osfv_cli,
"PR Statistics for osfv_cli repository",
"img/dug_10/dasharo_prs_osfv_cli_total.png",
label_offsets=(5, 1),
cap_to_zero=True,
)
data_osfv_cli = gather_data(repo_osfv_cli, dates, differences=True)
Expand All @@ -281,7 +343,6 @@ def plot_pr_statistics(
data_osfv_cli,
"PR Statistics for osfv_cli repository - increments",
"img/dug_10/dasharo_prs_osfv_cli_diff.png",
label_offsets=(0.7, 0.5),
cap_to_zero=True,
)

Expand All @@ -294,7 +355,6 @@ def plot_pr_statistics(
data_meta_dts,
"PR Statistics for meta-dts repository",
"img/dug_10/dasharo_prs_meta_dts.png",
label_offsets=(7, 1),
)

# Plot for Dasharo/meta-dts
Expand All @@ -306,5 +366,4 @@ def plot_pr_statistics(
data_dts_scripts,
"PR Statistics for dts-scripts repository",
"img/dug_10/dasharo_prs_dts_scripts.png",
label_offsets=(7, 1),
)
Loading