Skip to content

Commit 004f38d

Browse files
feat(plotnine): implement sn-curve-basic (#7523)
## Implementation: `sn-curve-basic` - python/plotnine Implements the **python/plotnine** version of `sn-curve-basic`. **File:** `plots/sn-curve-basic/implementations/python/plotnine.py` **Parent Issue:** #3826 --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/anyplot/actions/runs/26168552769)* --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Markus Neusinger <2921697+MarkusNeusinger@users.noreply.github.com>
1 parent fff4e9d commit 004f38d

2 files changed

Lines changed: 119 additions & 116 deletions

File tree

plots/sn-curve-basic/implementations/python/plotnine.py

Lines changed: 32 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
""" anyplot.ai
22
sn-curve-basic: S-N Curve (Wöhler Curve)
33
Library: plotnine 0.15.4 | Python 3.13.13
4-
Quality: 85/100 | Updated: 2026-05-20
4+
Quality: 91/100 | Updated: 2026-05-20
55
"""
66

77
import os
@@ -11,12 +11,15 @@
1111
from plotnine import (
1212
aes,
1313
annotate,
14+
element_blank,
1415
element_line,
1516
element_rect,
1617
element_text,
1718
geom_hline,
1819
geom_line,
1920
geom_point,
21+
geom_ribbon,
22+
geom_rug,
2023
ggplot,
2124
labs,
2225
scale_shape_manual,
@@ -43,10 +46,10 @@
4346
yield_strength = 350
4447
endurance_limit = 250
4548

46-
# Generate realistic S-N curve data with scatter
4749
# Using Basquin equation: S = A * N^b
48-
A = 1200 # Fatigue strength coefficient
49-
b = -0.12 # Fatigue strength exponent
50+
A = 1200
51+
b = -0.12
52+
sigma_logN = 0.3 # Log-normal scatter in cycles
5053

5154
stress_levels = np.array([500, 450, 400, 375, 350, 325, 300, 280, 270, 260, 255])
5255

@@ -57,7 +60,7 @@
5760
for s in stress_levels:
5861
n_expected = (s / A) ** (1 / b)
5962
n_tests = np.random.randint(3, 6)
60-
scatter = np.random.lognormal(0, 0.3, n_tests)
63+
scatter = np.random.lognormal(0, sigma_logN, n_tests)
6164
n_values = n_expected * scatter
6265
cycles.extend(n_values)
6366
stress.extend([s] * n_tests)
@@ -72,57 +75,62 @@
7275

7376
df = pd.DataFrame({"cycles": cycles, "stress": stress, "type": point_type})
7477

75-
# Create Basquin fit line
78+
# Basquin fit line with ±2σ scatter band (converts log-N scatter to stress bounds)
7679
fit_cycles = np.logspace(2, 7, 100)
7780
fit_stress = A * fit_cycles**b
78-
df_fit = pd.DataFrame({"cycles": fit_cycles, "stress": fit_stress})
81+
fit_stress_upper = fit_stress * np.exp(2 * sigma_logN * abs(b))
82+
fit_stress_lower = fit_stress * np.exp(-2 * sigma_logN * abs(b))
83+
df_fit = pd.DataFrame(
84+
{"cycles": fit_cycles, "stress": fit_stress, "stress_upper": fit_stress_upper, "stress_lower": fit_stress_lower}
85+
)
7986

8087
anyplot_theme = theme(
88+
figure_size=(8, 4.5),
8189
plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),
8290
panel_background=element_rect(fill=PAGE_BG),
8391
panel_grid_major=element_line(color=INK, size=0.3, alpha=0.10),
8492
panel_grid_minor=element_line(color=INK, size=0.2, alpha=0.05),
85-
panel_border=element_rect(color=INK_SOFT, fill=None),
93+
panel_border=element_blank(),
94+
axis_line_x=element_blank(),
95+
axis_line_y=element_line(color=INK_SOFT),
8696
axis_title=element_text(color=INK, size=10),
8797
axis_text=element_text(color=INK_SOFT, size=8),
88-
axis_line=element_line(color=INK_SOFT),
8998
plot_title=element_text(color=INK, size=12),
9099
legend_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT),
91100
legend_text=element_text(color=INK_SOFT, size=8),
92101
legend_title=element_text(color=INK, size=9),
102+
plot_margin=0.05,
93103
)
94104

95105
plot = (
96106
ggplot()
107+
# ±2σ scatter band via geom_ribbon — idiomatic plotnine uncertainty visualization
108+
+ geom_ribbon(df_fit, aes(x="cycles", ymin="stress_lower", ymax="stress_upper"), fill=OKABE_ITO[0], alpha=0.12)
97109
# Basquin fit line
98110
+ geom_line(df_fit, aes(x="cycles", y="stress"), color=OKABE_ITO[0], size=1.2, alpha=0.85)
99111
# Data points — shape mapped to type for runout vs failure distinction
100-
+ geom_point(df, aes(x="cycles", y="stress", shape="type"), color=OKABE_ITO[0], size=2.5, alpha=0.75)
101-
# Reference lines with Okabe-Ito colors
112+
+ geom_point(df, aes(x="cycles", y="stress", shape="type"), color=OKABE_ITO[0], size=4.2, alpha=0.80)
113+
# geom_rug exposes marginal data density along both axes — distinctive plotnine feature
114+
+ geom_rug(df, aes(x="cycles", y="stress"), color=OKABE_ITO[0], alpha=0.35, size=0.5)
115+
# Reference lines with Okabe-Ito colors; endurance limit slightly thicker as focal point
102116
+ geom_hline(yintercept=ultimate_strength, linetype="dashed", color=OKABE_ITO[1], size=0.9, alpha=0.85)
103117
+ geom_hline(yintercept=yield_strength, linetype="dashed", color=OKABE_ITO[2], size=0.9, alpha=0.85)
104-
+ geom_hline(yintercept=endurance_limit, linetype="dashed", color=OKABE_ITO[3], size=0.9, alpha=0.85)
105-
# Reference line labels with matching colors
118+
+ geom_hline(yintercept=endurance_limit, linetype="dashed", color=OKABE_ITO[3], size=1.2, alpha=0.90)
119+
# Reference line labels with matching colors; shifted right to reduce left-edge crowding
106120
+ annotate(
107121
"text",
108-
x=1.2e2,
109-
y=ultimate_strength + 18,
122+
x=3e2,
123+
y=ultimate_strength + 20,
110124
label="Ultimate Strength (550 MPa)",
111-
size=8,
125+
size=10,
112126
color=OKABE_ITO[1],
113127
ha="left",
114128
)
115129
+ annotate(
116-
"text", x=1.2e2, y=yield_strength + 18, label="Yield Strength (350 MPa)", size=8, color=OKABE_ITO[2], ha="left"
130+
"text", x=3e2, y=yield_strength + 20, label="Yield Strength (350 MPa)", size=10, color=OKABE_ITO[2], ha="left"
117131
)
118132
+ annotate(
119-
"text",
120-
x=1.2e2,
121-
y=endurance_limit - 22,
122-
label="Endurance Limit (250 MPa)",
123-
size=8,
124-
color=OKABE_ITO[3],
125-
ha="left",
133+
"text", x=3e2, y=endurance_limit - 25, label="Endurance Limit (250 MPa)", size=10, color=OKABE_ITO[3], ha="left"
126134
)
127135
# Logarithmic scales
128136
+ scale_x_log10()
@@ -136,7 +144,6 @@
136144
title="sn-curve-basic · python · plotnine · anyplot.ai",
137145
)
138146
+ theme_minimal()
139-
+ theme(figure_size=(8, 4.5))
140147
+ anyplot_theme
141148
)
142149

0 commit comments

Comments
 (0)